java - Las grabaciones JDO confirmadas no se aplican a GAE local HRD, o posiblemente a transacciones reutilizadas
google-app-engine memcached (1)
Estoy usando JDO 2.3 en el motor de aplicaciones. Estaba usando el almacén de datos Maestro / Esclavo para pruebas locales y recientemente pasé al uso del almacén de datos de HRD para pruebas locales, y partes de mi aplicación se están rompiendo (lo cual es de esperar). Una parte de la aplicación que se está rompiendo es donde envía muchas escrituras rápidamente, es por el límite de 1 segundo, está fallando con una excepción de modificación concurrente.
Bueno, eso también es de esperar, así que el navegador vuelve a intentar escribir más tarde cuando fallan (tal vez no sea el mejor truco, pero solo estoy tratando de hacerlo funcionar rápidamente).
Pero está sucediendo algo extraño. Algunas de las escrituras que deberían tener éxito (las que NO OBTIENEN la excepción de modificación simultánea) también están fallando, aunque la fase de confirmación se complete y la solicitud devuelva mi código de éxito. Puedo ver en el registro que las solicitudes reintentadas funcionan bien, pero estas otras solicitudes que parecen haberse cometido en el primer intento, supongo, nunca se "aplicaron". Pero por lo que leí sobre la fase Aplicar, escribir nuevamente a esa misma entidad debería forzar la aplicación ... pero no es así.
El código sigue. Algunas cosas a tener en cuenta:
- Estoy intentando usar el almacenamiento en caché JDO automático . Así que aquí es donde JDO usa Memcache bajo las sábanas. Esto no funciona a menos que envuelva todo en una transacción.
- lo único que hacen las solicitudes es leer una cadena de una entidad, modificar parte de la cadena y guardarla nuevamente en la entidad. Si estas solicitudes no estuvieran en transacciones, por supuesto tendrías el problema de "lectura sucia". Pero con las transacciones, se supone que el aislamiento está en el nivel de "serializable", así que no veo qué está sucediendo aquí.
- la entidad que se está modificando es una entidad raíz (no en un grupo)
- Tengo transacciones de grupos cruzados habilitadas
El código relevante (esta es una versión simplificada):
PersistenceManager pm = PMF.getManager();
Transaction tx = pm.currentTransaction();
String responsetext = "";
try {
tx.begin();
// I have extra calls to "makePersistent" because I found that relying
// on pm.close didn''t always write the objects to cache, maybe that
// was only a DataNucleus 1.x issue though
Key userkey = obtainUserKeyFromCookie();
User u = pm.getObjectById(User.class, userkey);
pm.makePersistent(u); // to make sure it gets cached for next time
Key mapkey = obtainMapKeyFromQueryString();
// this is NOT a java.util.Map, just FYI
Map currentmap = pm.getObjectById(Map.class, mapkey);
Text mapData = currentmap.getMapData(); // mapData is JSON stored in the entity
Text newMapData = parseModifyAndReturn(mapData); // transform the map
currentmap.setMapData(newMapData); // mutate the Map object
pm.makePersistent(currentmap); // make sure to persist so there is a cache hit
tx.commit();
responsetext = "OK";
} catch (JDOCanRetryException jdoe) {
// log jdoe
responsetext = "RETRY";
} catch (Exception e) {
// log e
responsetext = "ERROR";
} finally {
if (tx.isActive()) {
tx.rollback();
}
pm.close();
}
resp.getWriter().println(responsetext);
ACTUALIZACIÓN : estoy bastante seguro de que sé por qué sucede esto, pero aún otorgaré la recompensa a cualquiera que pueda confirmarlo.
Básicamente, creo que el problema es que las transacciones realmente no se implementan en la versión local del almacén de datos. Referencias
https://groups.google.com/forum/?fromgroups=#!topic/google-appengine-java/gVMS1dFSpcU https://groups.google.com/forum/?fromgroups=#!topic/google-appengine-java/deGasFdIO-M https://groups.google.com/forum/?hl=en&fromgroups=#!msg/google-appengine-java/4YuNb6TVD6I/gSttMmHYwo0J
Debido a que las transacciones no se implementan, la reversión es esencialmente no operativa. Por lo tanto, obtengo una lectura sucia cuando dos transacciones intentan modificar el registro al mismo tiempo. En otras palabras, A lee los datos y B lee los datos al mismo tiempo. A intenta modificar los datos, y B intenta modificar una parte diferente de los datos. A escribe en el almacén de datos, luego B escribe, borrando los cambios de A. Luego B es "revertido" por el motor de la aplicación, pero como los retrocesos no funcionan cuando se ejecuta en el almacén de datos local, los cambios de B se mantienen y los de A no. Mientras tanto, como B es el hilo que arrojó la excepción, el cliente reintenta B, pero no vuelve a intentar A (ya que A supuestamente fue la transacción que tuvo éxito).
Tal vez malas noticias para ti, dejé JDO y uso Objectify y en algunos lugares directamente datanucleus. Tengo un control perfecto sobre mi persistencia, que es una mejor opción de rendimiento y diseño (si piensas a largo plazo).
Debido a que el db es no-sql, existen cambios estructurales contra JPA, JDO y suposiciones estándar:
Usando la API nativa de datanucleus puedes hacer cosas que no están en el estándar JPA ni en Objectify: el ejemplo que utilicé fue para crear dinámicamente columnas
La transacción no está presente en GAE, hay algo que a veces puede parecer una transacción (grupos de entidades). Así que usar la API nativa evitará que hagas gimnasia tan imprevisible.
Intentar conducir un automóvil con un joystick podría funcionar, pero seguramente hay cosas nuevas que aprender. En mi opinión, vale la pena aprender de la manera nativa