2012-11-27 11 views
28

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:

  1. 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.
  2. 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.
  3. dell'entità modificato è un'entità principale (non in un gruppo)
  4. 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).

+0

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? –

+0

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

+0

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

risposta

1

Forse brutte notizie per te, ho lasciato JDO e sto usando Objectify e in alcuni punti direttamente datanucleus.Ho un perfetto controllo sulla mia persistenza, che è una scelta di prestazioni e design migliore (se pensi a lungo termine).

Perché il db è no-SQL, ci sono cambiamenti strutturali contro JPA, JDO e ipotesi standard:

Uso del DataNucleus API native si possono fare cose che non sono nella norma JPA e neppure in oggettivare: L'esempio Ho usato è stato quello di creare colonne dinamicamente

La transazione non è presente in GAE, c'è qualcosa che a volte può sembrare una transazione (gruppi di entità). Quindi usando l'API nativa eviterai di fare una ginnastica così imprevisibile.

Provare a guidare un'auto con un joystick potrebbe funzionare, ma ci sono sicuramente cose nuove da imparare. Secondo me vale la pena imparare il modo nativo

+1

Zied ha ragione. Sebbene sia leggermente interessante che Google fornisca i livelli di astrazione JPA e JDO, non ho mai visto un progetto che andasse molto bene con loro. Di solito finisce in tutti i tipi di brutti hack, perché la gente continua a pensare all'archivio di supporto come un RDBMS quando non lo è certamente. Suggerisco caldamente alle persone di non utilizzare le librerie JPA o JDO e utilizzare l'API Google o Objectify. –