Async Datastore API(Python)

참고: 새로운 애플리케이션을 빌드하는 개발자는 NDB 클라이언트 라이브러리를 사용하는 것이 좋습니다. NDB 클라이언트 라이브러리는 이 클라이언트 라이브러리와 비교할 때 Memcache API를 통한 자동 항목 캐싱과 같은 여러 이점이 있습니다. 현재 이전 DB 클라이언트 라이브러리를 사용 중인 경우 DB에서 NDB로의 마이그레이션 가이드를 참조하세요.

Async Datastore API를 사용하면 Datastore를 병렬 비차단 방식으로 호출한 후 요청을 처리하는 과정에서 이후 시점에 호출 결과를 검색할 수 있습니다. 이 문서에서는 Async Datastore API의 다음과 같은 측면에 대해 설명합니다.

비동기 Datastore 서비스로 작업

Async Datastore API에서는 google.appengine.ext.db 패키지의 get_async()와 같은 *_async 형식의 메서드를 사용하여 Datastore를 호출합니다. 다음 코드 샘플에서는 비동기 메서드를 사용한 몇 가지 간단한 Datastore 작업을 보여줍니다.

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)

이러한 함수는 비동기 버전과 동일한 작업을 수행하지만, 이후 시점에 실제 결과를 얻는 데 사용할 수 있는 비동기 객체를 즉시 반환한다는 점이 다릅니다. 다음 코드 샘플에서는 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()

참고: get() 메서드를 호출할 때까지 예외가 발생하지 않습니다. 이 메서드를 호출하면 비동기 작업의 성공 여부를 확인할 수 있습니다.

비동기 트랜잭션 다루기

Async Datastore API 호출은 동기식 호출과 마찬가지로 트랜잭션에 참여할 수 있습니다. 다음은 단일 트랜잭션 내에서 Employee의 급여를 조정하고 Employee와 동일한 항목 그룹에 SalaryAdjustment 항목을 추가로 작성하는 함수입니다.

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)

이 샘플에서는 트랜잭션 없는 비동기 호출과 트랜잭션 내에 있는 비동기 호출 사이의 중요한 차이점을 보여줍니다. 트랜잭션을 사용하지 않는 경우 개별 비동기 호출이 완료되었는지 확인하는 유일한 방법은 트랜잭션을 사용하여 비동기 객체의 결과를 가져오는 것입니다. 이 트랜잭션을 커밋하면 트랜잭션 내에서 이루어진 모든 비동기 호출의 결과를 차단합니다.

따라서 위의 예시에서는 runner()가 완료되는 시점에 SalaryAdjustment 항목을 삽입하는 비동기 호출이 여전히 미완료 상태일 수 있지만 삽입이 완료될 때까지 커밋이 수행되지 않습니다.

비동기 쿼리

명시적 비동기 API는 현재 쿼리용으로 노출되지 않습니다. 하지만 Query.run()을 호출하면 즉시 쿼리가 반환되고 비동기식으로 결과를 미리 가져옵니다. 따라서 쿼리 결과를 가져오는 동안 애플리케이션에서 병렬로 작업을 수행할 수 있습니다.

# ...

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)

하지만 Query.fetch()는 동일하게 동작하지 않습니다.

비동기 Datastore 호출을 사용해야 하는 경우

db.get()과 같은 동기식 google.ext.db 메서드를 호출하면 Datastore 호출이 완료될 때까지 코드가 처리 대기 상태가 됩니다. 애플리케이션이 HTML에서 get()의 결과를 렌더링하기만 하면 되는 경우, 호출이 완료될 때까지 차단할 수 있습니다. 그러나 애플리케이션이 응답을 렌더링하기 위해 get()과 쿼리 결과를 필요로 하고, get() 및 쿼리에 데이터 종속 항목이 없으면 get()이 완료될 때까지 기다렸다가 쿼리를 시작하는 것은 시간 낭비입니다. 다음은 비동기 API를 사용하여 개선할 수 있는 코드의 예입니다.

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

get()이 완료되기를 기다리는 대신 db.get_async()를 사용하여 다음과 같이 비동기식으로 호출을 실행합니다.

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)

이 코드의 동기식 버전과 비동기식 버전은 같은 양의 작업을 수행하므로 CPU 사용량이 비슷합니다. 그러나 비동기 버전에서는 두 데이터 저장소 작업이 병렬로 실행되므로 지연 시간이 더 짧습니다. 일반적으로, 데이터 종속 항목이 없는 여러 Datastore 작업을 수행해야 하는 경우 비동기 API로 지연 시간을 크게 개선할 수 있습니다.