API Async Datastore (Python)

Nota: Gli sviluppatori che creano nuove applicazioni sono vivamente incoraggiati a utilizzare il Libreria client NDB, che offre numerosi vantaggi rispetto a questa libreria client, come la memorizzazione automatica nella cache delle entità tramite API. Se al momento utilizzi la libreria client DB precedente, leggi il Guida alla migrazione da DB a NDB

L'API Async Datastore consente di effettuare chiamate parallele e non bloccanti al datastore e di recuperare i risultati di queste chiamate in un secondo momento nella gestione della richiesta. Questa documentazione descrive i seguenti aspetti dell'API Async Datastore:

Utilizzo del servizio Datastore asincrono

Con l'API datastore asincrona, puoi effettuare chiamate al datastore utilizzando metodi nel formato *_async (ad esempio get_async() nel pacchetto google.appengine.ext.db). Il seguente esempio di codice illustra alcune semplici operazioni sul datastore utilizzando i metodi asincroni:

from google.appengine.ext import db

get_future = db.get_async(key)
put_future = db.put_async(model)
delete_future = db.delete_async(key)
allocate_ids_future = db.allocate_ids_async(key, 10)

Queste funzioni eseguono le stesse operazioni delle versioni sincrone, ma restituiscono immediatamente un oggetto asincrono che puoi utilizzare per ottenere il risultato reale in un secondo momento. Il seguente esempio di codice mostra come ottenere il risultato asincrono utilizzando get_result():

entity = get_future.get_result()
key = put_future.get_result()
range = allocate_ids_future.get_result()

# Wait for the operation to complete without returning a value.
# Exceptions that occurred in the call are thrown here. Calling
# get_result() allows you to verify that the deletion succeeded.
delete_future.get_result()

Nota: le eccezioni non vengono generate finché non chiami get_result(). La chiamata di questo metodo consente di verificare che l'operazione asincrona sia riuscita.

Utilizzo delle transazioni asincrone

Le chiamate API datastore asincrone possono partecipare alle transazioni proprio come le chiamate sincrone. Ecco una funzione che regola lo stipendio di un Employee e scrive un'entità SalaryAdjustment aggiuntiva nello stesso gruppo di entità di Employee, il tutto all'interno di un'unica transazione.

def adjust_salary(employee_key, raise_ammount):
   def runner():
        # Async call to lookup the Employee entity
        employee_entity_future = db.get_async(employee_key)

        # Create and put a SalaryAdjustment entity in parallel with the lookup
        adjustment_entity = SalaryAdjustment(parent=employeeKey)
        adjustment_entity.adjustment = raise_amount
        db.put_async(adjustmentEntity)

        # Fetch the result of our lookup to make the salary adjustment
        employee_entity = employee_entity_future.get_result()
        employee_entity.salary += raise_amount

        # Re-put the Employee entity with the adjusted salary.
        db.put_async(employee_entity)
    db.run_in_transaction(runner)

Questo esempio illustra un'importante differenza tra le chiamate asincrone senza transazioni e quelle asincrone all'interno delle transazioni. Quando non utilizzi una transazione, l'unico modo per garantire che una singola chiamata asincrona sia stata completata è recuperare il risultato dell'oggetto asincrono utilizzando una transazione. Il commit della transazione blocca il risultato di tutte le chiamate asincrone effettuate all'interno di una transazione.

Quindi, nell'esempio precedente, anche se la nostra chiamata asincrona per inserire l'entità SalaryAdjustment potrebbe essere ancora in sospeso al termine di runner(), il commit non verrà eseguito fino al completamento dell'inserimento.

Query asincrone

Al momento non esponi un'API esplicitamente asincrona per le query. Tuttavia, quando richiami Query.run(), la query restituisce immediatamente e precarica i risultati in modo asincrono. In questo modo l'applicazione può eseguire il lavoro in parallelo durante il recupero dei risultati della query.

# ...

q1 = Salesperson.all().filter('date_of_hire <', one_month_ago)

# Returns instantly, query is executing in the background.
recent_hires = q1.run()

q2 = Customer.all().filter('last_contact >', one_year_ago)

# Also returns instantly, query is executing in the background.
needs_followup = q2.run()

schedule_phone_call(recent_hires, needs_followUp)

Purtroppo Query.fetch() non ha lo stesso comportamento.

Quando utilizzare le chiamate Datastore asincrone

Quando chiami un metodo google.ext.db sincrono, come db.get(), il codice si blocca fino al completamento della chiamata al datastore. Se l'unica cosa che l'applicazione deve fare è eseguire il rendering del risultato di get() in HTML, bloccare fino al completamento della chiamata è una cosa perfettamente ragionevole. Tuttavia, se la tua applicazione ha bisogno del risultato di get() più il risultato di una query per eseguire il rendering della risposta (e se get() e la query non hanno dipendenze per i dati), attendere il completamento di get() per avviare la query è una perdita di tempo. Di seguito è riportato un esempio di codice che può essere migliorato utilizzando l'API asincrona:

# Read employee data from the Datastore
employee = Employee.get_by_key_name('Max')  # Blocking for no good reason!

# Fetch payment history
query = PaymentHistory.all().ancestor(employee.key())
history = query.fetch(10)
render_html(employee, history)

Anziché attendere il completamento di get(), utilizza db.get_async() per eseguire la chiamata in modo asincrono:

employee_key = db.Key.from_path(Employee.kind(), 'Max')

# Read employee data from the Datastore
employee_future = db.get_async(employee_key)  # Returns immediately!

# Fetch payment history
query = PaymentHistory.all().ancestor(employee_key)

# Implicitly performs query asynchronously
history_itr = query.run(config=datastore_query.QueryOptions(limit=10))
employee = employee_future.get_result()
render_html(employee, history_itr)

La versione sincrona e asincrona di questo codice utilizzano quantità di CPU simili (dopo tutto, entrambe svolgono la stessa quantità di lavoro), ma poiché la versione asincrona consente l'esecuzione in parallelo di due operazioni sul datastore, la versione asincrona ha una latenza inferiore. In generale, se devi eseguire più operazioni sul datastore che non hanno dipendenze dei dati, l'API asincrona può migliorare notevolmente la latenza.