API Datastore assíncrona (Python)

Nota: os programadores que criam novas aplicações são fortemente aconselhados a usar a biblioteca de cliente NDB, que tem várias vantagens em comparação com esta biblioteca de cliente, como o armazenamento em cache automático de entidades através da API Memcache. Se estiver a usar atualmente a biblioteca cliente DB mais antiga, leia o guia de migração de DB para NDB

A API Async Datastore permite-lhe fazer chamadas paralelas e não bloqueadoras para o armazenamento de dados e obter os resultados destas chamadas num ponto posterior no processamento do pedido. Esta documentação descreve os seguintes aspetos da API Async Datastore:

Trabalhar com o serviço Async Datastore

Com a API datastore assíncrona, faz chamadas ao datastore através de métodos do formulário *_async (como get_async() no pacote google.appengine.ext.db). O seguinte exemplo de código demonstra algumas operações simples da base de dados usando os métodos assíncronos:

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)

Estas funções realizam as mesmas operações que as versões síncronas, exceto que devolvem imediatamente um objeto assíncrono que pode usar para obter o resultado real num momento posterior. O seguinte exemplo de código demonstra como obter o resultado assíncrono através de 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: as exceções não são lançadas até chamar get_result(). Chamar este método permite-lhe verificar se a operação assíncrona foi bem-sucedida.

Trabalhar com transações assíncronas

As chamadas API de armazenamento de dados assíncronas podem participar em transações tal como as chamadas síncronas. Segue-se uma função que ajusta o salário de um Employee e escreve uma entidade SalaryAdjustment adicional no mesmo grupo de entidades que o Employee, tudo numa única transação.

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)

Este exemplo ilustra uma diferença importante entre chamadas assíncronas sem transações e chamadas assíncronas em transações. Quando não está a usar uma transação, a única forma de garantir que uma chamada assíncrona individual foi concluída é obter o resultado do objeto assíncrono através de uma transação. A confirmação dessa transação é bloqueada no resultado de todas as chamadas assíncronas feitas numa transação.

Assim, no exemplo acima, mesmo que a nossa chamada assíncrona para inserir a entidade SalaryAdjustment ainda possa estar pendente quando runner() terminar, a confirmação não ocorre até que a inserção seja concluída.

Consultas assíncronas

Atualmente, não expomos uma API explicitamente assíncrona para consultas. No entanto, quando invoca Query.run(), a consulta é devolvida imediatamente e pré-obtém os resultados de forma assíncrona. Isto permite que a sua aplicação execute tarefas em paralelo enquanto os resultados da consulta são obtidos.

# ...

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)

Infelizmente, Query.fetch() não tem o mesmo comportamento.

Quando usar chamadas de armazenamento de dados assíncronas

Quando chama um método google.ext.db síncrono, como db.get(), o seu código é bloqueado até que a chamada ao armazenamento de dados seja concluída. Se a única coisa que a sua aplicação precisa de fazer é renderizar o resultado de get() em HTML, bloquear até que a chamada esteja concluída é perfeitamente razoável. No entanto, se a sua aplicação precisar do resultado de get() mais o resultado de uma consulta para renderizar a resposta e se get() e a consulta não tiverem dependências de dados, esperar até que get() seja concluído para iniciar a consulta é uma perda de tempo. Segue-se um exemplo de código que pode ser melhorado através da API assíncrona:

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

Em vez de aguardar a conclusão de get(), use db.get_async() para executar a chamada de forma assíncrona:

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)

As versões síncronas e assíncronas deste código usam quantidades semelhantes de CPU (afinal, ambas realizam a mesma quantidade de trabalho), mas, uma vez que a versão assíncrona permite que as duas operações da base de dados sejam executadas em paralelo, a versão assíncrona tem uma latência inferior. Em geral, se precisar de realizar várias operações de armazenamento de dados que não tenham dependências de dados, a API assíncrona pode melhorar significativamente a latência.