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, ad esempio la memorizzazione nella cache automatica delle entità tramite l'API Memcache. Se al momento utilizzi la libreria client DB precedente, leggi la guida alla migrazione da DB a NDB

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

Utilizzo del servizio Async Datastore

Con l'API Datastore asincrona, puoi effettuare chiamate a Datastore utilizzando metodi del tipo *_async (ad esempio get_async() nel pacchetto google.appengine.ext.db). Il seguente esempio di codice mostra alcune semplici operazioni sul datastore che utilizzano 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: le eccezioni non vengono lanciate finché non chiami get_result(). La chiamata a questo metodo ti consente di verificare che l'operazione asincrona sia riuscita.

Utilizzo delle transazioni asincrone

Le chiamate all'API Async Datastore possono partecipare alle transazioni come le chiamate sincrone. Di seguito è riportata una funzione che regola lo stipendio di un Employee e scrive un'entità SalaryAdjustment aggiuntiva nello stesso gruppo di entità dell'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 una differenza importante tra le chiamate asincrone senza transazioni e le chiamate asincrone all'interno delle transazioni. Quando 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'commit della transazione si blocca sul risultato di tutte le chiamate asincrone effettuate all'interno di una transazione.

Pertanto, nel nostro esempio precedente, anche se la chiamata asincrona per inserire l'entità SalaryAdjustment potrebbe essere ancora in sospeso al termine di runner(), il commit non verrà eseguito finché l'inserimento non sarà completato.

Query asincrone

Al momento non esponiamo un'API esplicitamente asincrona per le query. Tuttavia, quando chiami Query.run(), la query restituisce immediatamente i risultati e li precompila in modo asincrono. In questo modo, l'applicazione può eseguire attività 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 Async Datastore

Quando chiami un metodo google.ext.db sincrono, ad esempio db.get(), il codice si blocca fino al completamento della chiamata al datastore. Se l'unica cosa che la tua applicazione deve fare è visualizzare il 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() e del risultato di una query per visualizzare la risposta e se get() e la query non hanno dipendenze di dati, attendere il completamento di get() per avviare la query è uno spreco 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 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)

Le versioni sincrone e asincrone di questo codice utilizzano quantità simili di CPU (dopotutto, entrambe eseguono la stessa quantità di lavoro), ma poiché la versione asincrona consente l'esecuzione in parallelo delle due operazioni del datastore, ha una latenza inferiore. In generale, se devi eseguire più operazioni di Datastore che non hanno dipendenze dai dati, l'API asincrona può migliorare notevolmente la latenza.