8

Questa è un'eccezione comune che ricevo quotidianamente sul registro dell'applicazione, in genere 5/6 volte al giorno con un traffico di 1K di visite/giorno :Timeout di Google App Engine scaduto, oppure i dati erano temporaneamente non disponibili

db error trying to store stats 
Traceback (most recent call last): 
    File "/base/data/home/apps/stackprinter/1b.347728306076327132/app/utility/worker.py", line 36, in deferred_store_print_statistics 
    dbcounter.increment() 
    File "/base/data/home/apps/stackprinter/1b.347728306076327132/app/db/counter.py", line 28, in increment 
    db.run_in_transaction(txn) 
    File "/base/python_runtime/python_lib/versions/1/google/appengine/api/datastore.py", line 1981, in RunInTransaction 
    DEFAULT_TRANSACTION_RETRIES, function, *args, **kwargs) 
    File "/base/python_runtime/python_lib/versions/1/google/appengine/api/datastore.py", line 2067, in RunInTransactionCustomRetries 
    ok, result = _DoOneTry(new_connection, function, args, kwargs) 
    File "/base/python_runtime/python_lib/versions/1/google/appengine/api/datastore.py", line 2105, in _DoOneTry 
    if new_connection.commit(): 
    File "/base/python_runtime/python_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1585, in commit 
    return rpc.get_result() 
    File "/base/python_runtime/python_lib/versions/1/google/appengine/api/apiproxy_stub_map.py", line 530, in get_result 
    return self.__get_result_hook(self) 
    File "/base/python_runtime/python_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1613, in __commit_hook 
    raise _ToDatastoreError(err) 
Timeout: The datastore operation timed out, or the data was temporarily unavailable. 

la funzione che sta sollevando l'eccezione sopra è la seguente:

def store_printed_question(question_id, service, title): 
    def _store_TX(): 
     entity = Question.get_by_key_name(key_names = '%s_%s' % \ 
             (question_id, service)) 
     if entity: 
      entity.counter = entity.counter + 1     
      entity.put() 
     else: 
      Question(key_name = '%s_%s' % (question_id, service),\ 
          question_id ,\ 
          service,\ 
          title,\ 
          counter = 1).put() 
    db.run_in_transaction(_store_TX) 

Fondamentalmente, il controllo store_printed_question funzione se una determinata questione è stata precedentemente stampata, incrementando in tal caso il relativo contatore in una singola transazione.
Questa funzione viene aggiunto da un WebHandler ad un deferred lavoratore utilizzando il predefinito di coda default che, come forse già sapete, ha una velocità di trasmissione di cinque invocazioni compito al secondo.

su un soggetto con sei attributi (due indici) ho pensato che l'utilizzo di transactions regolato da un limite di velocità compito differito mi avrebbe permesso di evitare timeout datastore ma, guardando il registro, questo errore è ancora mostrando giornalmente.

Questo contatore che sto memorizzando non è molto importante, quindi non sono preoccupato di ottenere questi timeout; comunque sono curioso di sapere perché Google App Engine non è in grado di gestire correttamente questa attività anche a un ritmo basso come 5 attività al secondo e se la riduzione della velocità potrebbe essere una possibile soluzione.
A sharded counter su ogni domanda per evitare timeout sarebbe un eccesso di esperienza per me.

EDIT:
Ho impostato il limite di velocità a 1 compito al secondo sulla coda di default; Sto ancora ricevendo lo stesso errore.

+0

Le attività posticipate non sono "più leggere" delle normali attività, ad eccezione del fatto che sono più facili da scrivere. Sotto il cofano, sono implementati con gestori regolari. In ogni caso, ciò non avrebbe alcun impatto sul sovraccarico della transazione stessa. –

risposta

6

In generale, un timeout come questo è solitamente dovuto a write contention. Se hai una transazione in corso e stai scrivendo un po 'di roba allo stesso gruppo di entità contemporaneamente, ti imbatti in problemi di conflitto di scrittura (un effetto collaterale di optimistic concurrency). Nella maggior parte dei casi, se rendi il tuo entity group più piccolo, di solito questo problema viene minimizzato.

Nel tuo caso specifico, basato sul codice sopra riportato, è molto probabilmente perché dovresti utilizzare uno sharded counter per evitare l'impilamento di scritture serializzate.

Un'altra possibilità molto meno probabile (menzionata qui solo per completezza) è che il tablet sul quale si trovano i dati è being moved.

+1

Non esiste un gruppo di entità definito, sto aggiornando un singolo modello. La contesa di scrittura sul gruppo di entità generalmente solleva un altro tipo di eccezione. Come ho scritto nella mia domanda, l'uso di un contatore simile sembra eccessivo in questo caso. – systempuntoout

+1

@systempuntoout Puoi ancora ottenere contesa di scrittura su singole entità se stai facendo più di circa 1QPS di modifiche a loro. Contenzione _will_ causa un'eccezione Timeout. –

+0

@No sì, il conflitto di scrittura sul gruppo di entità causa un errore "Collisione transazione per gruppo entità con chiave ...". – systempuntoout

7

Una query può vivere solo per 30 secondi. Vedere la mia risposta allo this question per qualche codice di esempio per interrompere una query utilizzando i cursori.

+0

Il timeout predefinito per le API-call è 5 secondi. È possibile modificare ciò configurando il contesto in questo modo: ctx: = appengine.Timeout (appengine.NewContext (req), 30 * time.Second) – nilsmagnus

+0

'appengine.Timeout' now' context.WithTimeout' – murrekatt