Async Datastore API (Python)

Hinweis: Entwicklern von neuen Anwendungen wird dringend empfohlen, die NDB-Clientbibliothek zu verwenden. Diese bietet im Vergleich zur vorliegenden Clientbibliothek verschiedene Vorteile, z. B. das automatische Caching von Entitäten über die Memcache API. Wenn Sie derzeit die ältere DB-Clientbibliothek verwenden, finden Sie weitere Informationen im Leitfaden zur Migration von DB- zu NDB-Clientbibliotheken.

Mit der Async Datastore API können Sie parallele, nicht blockierende Aufrufe an den Datenspeicher ausführen und die Ergebnisse später bei der Verarbeitung der Anfrage abrufen. In dieser Dokumentation werden die folgenden Aspekte der Async Datastore API erläutert:

Mit dem Async Datastore-Dienst arbeiten

Mit der Async Datastore API führen Sie Datastore-Aufrufe mit Methoden der Form *_async aus (z. B. get_async () im google.appengine.ext.db-Paket). Das folgende Codebeispiel zeigt einige einfache Datenspeichervorgänge mithilfe der asynchronen Methoden:

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)

Diese Funktionen führen dieselben Vorgänge wie die synchronen Versionen aus, mit dem Unterschied, dass sie sofort ein asynchrones Objekt zurückgeben, mit dem Sie später das echte Ergebnis abrufen können. Im folgenden Codebeispiel wird veranschaulicht, wie Sie das asynchrone Ergebnis mit get_result() abrufen:

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()

Hinweis: Ausnahmen werden erst ausgelöst, wenn Sie get_result() aufrufen. Durch den Aufruf dieser Methode können Sie prüfen, ob der asynchrone Vorgang erfolgreich war.

Mit asynchronen Transaktionen arbeiten

Aufrufe der Async Datastore API können genau wie synchrone Aufrufe an Transaktionen beteiligt sein. Bei dieser Funktion wird im Rahmen einer einzelnen Transaktion das Gehalt von Employee angepasst und eine zusätzliche Entität SalaryAdjustment in dieselbe Entitätengruppe wie Employee geschrieben:

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)

Dieses Beispiel veranschaulicht einen wichtigen Unterschied zwischen asynchronen Aufrufen ohne Transaktionen und asynchronen Aufrufen innerhalb von Transaktionen. Wenn Sie keine Transaktion verwenden, können Sie sich nur vergewissern, dass ein einzelner asynchroner Aufruf abgeschlossen wurde, indem Sie das Ergebnis des asynchronen Objekts mit einer Transaktion abrufen. Durch den Commit-Vorgang bei dieser Transaktion wird gewartet (Blockierung), bis das Ergebnis aller asynchronen Aufrufe vorliegt, die innerhalb einer Transaktion vorgenommen wurden.

Für unser obiges Beispiel bedeutet dies: Auch wenn unser asynchroner Aufruf zur Einfügung der SalaryAdjustment-Entität vielleicht noch aussteht, wenn runner() abgeschlossen wird, wird der Commit-Vorgang erst nach Abschluss der Einfügung durchgeführt.

Asynchrone Abfragen

Wir bieten derzeit keine explizit asynchrone API für Abfragen. Wenn Sie Query.run() aufrufen, werden von der Abfrage jedoch sofort Ergebnisse zurückgegeben und asynchron vorab abgerufen. Dadurch kann Ihre Anwendung Aufgaben parallel abarbeiten, während die Abfrageergebnisse abgerufen werden.

# ...

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)

Leider weist Query.fetch() nicht das gleiche Verhalten auf.

Einsatzmöglichkeiten für Async Datastore-Aufrufe

Wenn Sie eine synchrone google.ext.db-Methode wie db.get() aufrufen, wird Ihr Code blockiert, bis der Aufruf des Datenspeichers abgeschlossen ist. Wenn Ihre Anwendung nur das Ergebnis von get() in HTML rendern muss, ist das Blockieren bis zum Abschluss des Aufrufs sinnvoll. Wenn Ihre Anwendung jedoch neben dem Ergebnis von get() auch das Ergebnis einer Abfrage benötigt, um die Antwort zu rendern – und wenn der get()-Vorgang und die Abfrage keine Datenabhängigkeiten haben –, dann ist es nicht sinnvoll zu warten, bis der get()-Vorgang abgeschlossen ist, um die Abfrage zu starten. Hier sehen Sie ein Beispiel für Code, der durch die Verwendung der Async API verbessert werden kann:

# 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)

Anstatt auf den Abschluss des get()-Vorgangs zu warten, verwenden Sie db.get_async(), um den Aufruf asynchron auszuführen:

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)

Die synchrone und die asynchrone Version dieses Codes weisen einen ähnlichen CPU-Verbrauch auf, sie führen ja schließlich jeweils denselben Arbeitsumfang durch. Aber da die beiden Datenspeichervorgänge bei der asynchronen Version parallel ausgeführt werden können, weist die asynchrone Version eine geringere Latenz auf. Grundsätzlich gilt: Wenn Sie mehrere Datenspeichervorgänge ohne Datenabhängigkeiten ausführen, kann die Latenz mit der asynchronen API erheblich verbessert werden.