Sto utilizzando JDO 2.3 sul motore dell'app. Stavo usando il datastore Master/Slave per i test locali e di recente sono passato all'utilizzo del datastore HRD per i test locali, e parti della mia app si stanno rompendo (cosa che ci si aspetta). Una parte dell'app che si sta rompendo è quella in cui invia molte scritture rapidamente - a causa del limite di 1 secondo, non riesce con un'eccezione di modifica simultanea.Le scritture JDO impegnate non si applicano all'URD locale GAE o alla transazione eventualmente riutilizzata
Ok, quindi è anche prevedibile, quindi ho il browser di riprovare più tardi le scritture quando falliscono (forse non è il miglior trucco ma sto solo cercando di farlo funzionare rapidamente).
Ma sta succedendo una cosa strana. Alcune delle scritture che dovrebbero avere successo (quelle che NON ottengono l'eccezione di modifica simultanea) stanno fallendo, anche se la fase di commit è completa e la richiesta restituisce il mio codice di successo. Posso vedere dal registro che le richieste riesaminate funzionano correttamente, ma queste altre richieste che sembrano essersi commesse al primo tentativo sono, suppongo, mai "applicate". Ma da quello che ho letto sulla fase di Applica, scrivere di nuovo a quella stessa entità dovrebbe forzare l'applicazione ... ma non è così.
Il codice segue. Alcune cose da notare:
- sto cercando di usare automatic JDO caching. Quindi è qui che JDO utilizza memcache sotto le copertine. Questo in realtà non funziona se non si avvolge tutto in una transazione.
- tutte le richieste stanno eseguendo la lettura di una stringa da un'entità, la modifica di parte della stringa e il salvataggio di tale stringa nell'entità. Se queste richieste non fossero nelle transazioni, avresti ovviamente il problema "dirty read". Ma con le transazioni, l'isolamento dovrebbe essere al livello di "serializzabile", quindi non vedo cosa sta succedendo qui.
- dell'entità modificato è un'entità principale (non in un gruppo)
- Ho transazioni del gruppo abilitato
Il codice corrispondente (questa è una versione semplificata):
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);
UPDATE: Sono abbastanza sicuro di sapere perché questo sta accadendo, ma assegnerò comunque la generosità a chiunque possa confermarlo.
Fondamentalmente, il problema è che le transazioni non sono realmente implementate nella versione locale del datastore. Riferimenti:
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
perché le transazioni non sono implementati, rollback è essenzialmente un no-op. Pertanto, ottengo una lettura sporca quando due transazioni cercano di modificare il record allo stesso tempo. In altre parole, A legge i dati e B legge i dati allo stesso tempo. Un tentativo di modificare i dati e B tenta di modificare una parte diversa dei dati. A scrive sul datastore, quindi B scrive, cancellando i cambiamenti di A. Quindi B viene "ripristinato" dal motore dell'app, ma poiché i rollback non funzionano quando sono in esecuzione sul datastore locale, le modifiche di B rimangono e A non lo fa. Nel frattempo, poiché B è il thread che ha generato l'eccezione, il client riprova B, ma non riprova A (poiché A era presumibilmente la transazione riuscita).
Hai mai pensato di ridisegnare il tuo archivio dati e come lo usi, per evitare di persistere nello stesso gruppo di entità più di una volta al secondo? In alternativa, hai provato a passare il persistere nel datastore alle attività in coda e a sistemare le cose per rispettare il limite di frequenza di scrittura del gruppo di entità 1/s? –
Ci ho pensato. Ma prima di farlo, mi piacerebbe capire perché questo particolare bug sta accadendo ... la mia preoccupazione è che fondamentalmente non capisco qualcosa riguardo alle HRD o alle app engine/transazioni jdo o qualcosa del genere, o che mi sia sfuggito qualcosa in la documentazione, e mi morderà più tardi, perché ho almeno altri 25 servizi a cui devo aggiungere transazioni (la memorizzazione nella cache di JDO non funzionerà se gli accessi ai datastore non sono in una transazione) – eeeeaaii
FWIW, usando il plugin corrente (GAE) JDO v2.x), non vedo l'esigenza che l'accesso sia in una transazione affinché la cache L2 funzioni; se un oggetto viene letto, allora viene memorizzato nella cache L2 e, se non lo è, deve essere segnalato (ovviamente il vecchio plugin non è supportato, quindi riferisci solo se è corrente). – DataNucleus