API Async Datastore (Python)

Nota: gli sviluppatori che creano nuove applicazioni sono vivamente incoraggiati a utilizzare la libreria client NDB, che offre diversi vantaggi rispetto a questa libreria client, come la memorizzazione nella cache automatica delle entità tramite l'API Memcache. Se attualmente utilizzi la libreria client DB precedente, leggi la Guida alla migrazione dal database a NDB

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

Utilizzo del servizio Async Datastore

Con l'API asincrona del datastore, puoi effettuare chiamate al datastore utilizzando i metodi nel formato *_async (ad esempio get_async() nel pacchetto google.appengine.ext.db). Il seguente esempio di codice mostra 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, tranne per il fatto che 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: non vengono generate eccezioni 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 del 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 una singola 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. Se non utilizzi una transazione, l'unico modo per assicurarti che una singola chiamata asincrona sia stata completata è recuperare il risultato dell'oggetto asincrono utilizzando una transazione. L'esecuzione di questa 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 l'inserimento dell'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 esponiamo un'API esplicitamente asincrona per le query. Tuttavia, quando richiami Query.run(), la query restituisce immediatamente i risultati e precarica in modo asincrono i risultati. Ciò consente all'applicazione di eseguire operazioni 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 a 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 è un'operazione del tutto ragionevole. Tuttavia, se l'applicazione necessita del risultato di get() più il risultato di una query per visualizzare la risposta (e se get() e la query non hanno dipendenze dei dati), attendere il completamento di get() per avviare la query è una perdita di tempo. Ecco 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 della funzione 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)

Le versioni sincrone e asincrone di questo codice utilizzano quantità simili di CPU (dopo tutto, entrambe svolgono la stessa quantità di lavoro), ma poiché la versione asincrona consente l'esecuzione in parallelo delle due operazioni sul datastore, la versione asincrona ha una latenza minore. In generale, se devi eseguire più operazioni sul datastore che non hanno dipendenze per i dati, l'API asincrona può migliorare significativamente la latenza.