NDB 트랜잭션

트랜잭션은 하나의 작업 또는 일련의 작업으로서 원자성이 보장되므로 트랜잭션이 일부만 적용되는 경우는 없습니다. 트랜잭션의 모든 작업이 적용되거나 하나도 적용되지 않거나, 둘 중 하나입니다. 트랜잭션의 최대 지속 시간은 60초이며, 30초 후에 10초의 유휴 만료 시간이 있습니다.

NDB 비동기 API를 사용하면 애플리케이션이 서로 독립적인 여러 트랜잭션을 동시에 관리할 수 있습니다. 동기 API는 @ndb.transactional() 데코레이터를 사용하여 간소화된 API를 제공합니다. 데코레이션된 함수는 해당 트랜잭션의 컨텍스트에서 실행됩니다.

@ndb.transactional
def insert_if_absent(note_key, note):
    fetch = note_key.get()
    if fetch is None:
        note.put()
        return True
    return False
note_key = ndb.Key(Note, note_title, parent=parent)
note = Note(key=note_key, content=note_text)
inserted = insert_if_absent(note_key, note)

트랜잭션이 다른 트랜잭션과 '충돌'하는 경우에는 트랜잭션이 실패하며, NDB는 이렇게 실패한 트랜잭션을 몇 번 재시도합니다. 트랜잭션이 재시도될 때 함수가 여러 번 호출될 수 있습니다. 재시도 횟수에는 제한(기본값: 3회)이 있으며 이 제한에 도달할 때까지 트랜잭션이 성공하지 못하면 NDB가 TransactionFailedError를 발생시킵니다. retries=Ntransactional() 데코레이터에 전달하여 재시도 횟수를 변경할 수 있습니다. 재시도 횟수가 0이면 트랜잭션을 한 번 시도하지만 실패하더라도 재시도되지 않습니다. 재시도 횟수가 N이면 트랜잭션을 총 N+1회 시도할 수 있습니다. 예:

@ndb.transactional(retries=1)
def insert_if_absent_2_retries(note_key, note):
    # do insert

트랜잭션에서는 상위 쿼리만 허용됩니다. 기본적으로 트랜잭션은 동일한 항목 그룹에 있는 항목('상위' 키가 동일한 항목)과 함께만 작동합니다.

xg=True를 전달하여 항목 그룹을 최대 25개까지 허용하는 교차 그룹('XG') 트랜잭션을 지정할 수 있습니다.

@ndb.transactional(xg=True)
def insert_if_absent_xg(note_key, note):
    # do insert

교차 그룹 트랜잭션은 여러 항목 그룹에서 작동하고, 단일 그룹 트랜잭션처럼 작동하지만, 코드가 둘 이상의 항목 그룹에서 항목을 업데이트하려고 시도하더라도 실패하지 않습니다.

함수에서 예외가 발생하면 트랜잭션이 즉시 취소되며 NDB는 호출하는 코드에서 볼 수 있도록 예외를 다시 발생시킵니다. ndb.Rollback 예외를 발생시키면 트랜잭션을 자동으로 강제 실패하도록 처리할 수 있습니다(이 경우 함수 호출에서 None을 반환함). 강제로 다시 시도하도록 할 수 있는 메커니즘은 없습니다.

트랜잭션에서 실행할 필요가 없는 함수의 경우, @ndb.transactional이 있는 함수로 데코레이션하는 대신 ndb.transaction()에 콜백 함수로 전달합니다.

def insert_if_absent_sometimes(note_key, note):
    # do insert
inserted = ndb.transaction(lambda:
                           insert_if_absent_sometimes(note_key, note))

코드 일부가 트랜잭션 내에서 실행되고 있는지 테스트하려면 in_transaction() 함수를 사용합니다.

'트랜잭션' 함수가 트랜잭션에 이미 포함된 코드를 통해 호출될 경우의 동작을 지정할 수 있습니다. @ndb.non_transactional 데코레이터는 함수가 트랜잭션에서 실행되지 않아야 하며 트랜잭션에서 호출될 경우에는 트랜잭션 외부에서 실행되도록 지정합니다. @ndb.transactional 데코레이터 및 ndb.transaction 함수는 propagation 키워드를 인수로 사용합니다. 예를 들어 함수가 새로운 개별 트랜잭션을 시작해야 하는 경우 이와 같이 데코레이션할 수 있습니다.

@ndb.transactional(propagation=ndb.TransactionOptions.INDEPENDENT)
def insert_if_absent_indep(note_key, note):
    # do insert

전파 유형은 다른 컨텍스트 옵션 및 트랜잭션 옵션과 함께 나열됩니다.

트랜잭션 동작과 NDB의 캐싱 동작은 진행 과정을 잘 모를 경우 혼동될 수 있습니다. 트랜잭션 내에서 항목을 수정했지만 아직 트랜잭션을 커밋하지는 않은 경우 NDB의 컨텍스트 캐시는 수정된 값을 포함하지만 기본 데이터 저장소는 아직 수정되지 않은 값을 포함합니다.

트랜잭션 작업 대기열에 추가

Datastore 트랜잭션의 일부로 대기열에 작업을 추가하면 트랜잭션이 성공적으로 커밋되는 경우에만 대기열에 작업이 추가됩니다. 트랜잭션이 커밋되지 않으면 작업은 대기열에 추가되지 않습니다. 트랜잭션이 커밋되면 작업이 대기열에 추가됩니다. 대기열에 추가된 작업은 즉시 실행되지 않으므로 해당 작업은 트랜잭션에서 원자성을 갖지 않습니다. 일단 대기열에 추가되면 작업은 성공할 때까지 재시도됩니다. 이는 데코레이션된 함수가 실행될 때 대기열에 추가된 모든 작업에 적용됩니다.

트랜잭션 작업은 Datastore 이외의 작업을 해당 트랜잭션이 성공해야 실행되는 트랜잭션(예: 구매 확인을 위한 이메일 전송)에 결합할 수 있다는 면에서 유용합니다. 또한 Datastore 작업을 트랜잭션에 연결하여 해당 트랜잭션이 성공하는 경우에만 트랜잭션 외부의 항목 그룹에 대한 변경사항을 커밋할 수 있습니다.

애플리케이션은 단일 트랜잭션 도중 태스크 큐트랜잭션 태스크를 5개를 초과하여 삽입할 수 없습니다. 트랜잭션 작업의 이름이 사용자 지정 이름이어서는 안 됩니다.

from google.appengine.api import taskqueue
from google.appengine.ext import ndb
@ndb.transactional
def insert_if_absent_taskq(note_key, note):
    taskqueue.add(url=flask.url_for('taskq_worker'), transactional=True)
    # do insert