home

Dynamodb의 트랜잭션

DynamoDB 트랜잭션 특징

  1. Transactions are submitted as single request
    1. 기존의 DB 트랜잭션 방식은 장시간 실행될 수 있다.
    2. DynamoDB에서는 단일 요청으로 제출되는 트랜잭션 제공
  2. Transactions rely on a transaction coordinator while non-transaction operations bypass the two-phase coordination
    1. DynamoDB 트랜잭션은 트랜잭션 코디네이터를 사용하여 관리
      1. 2단계 커밋(2PC) 프로토콜을 이용해 원자성 보장
    2. 성능상의 이유로, GetPut과 같은 일반적인 비 트랜잭션 연산은 코디네이터를 거치지 않고 스토리지 서버에서 직접 실행
    3. 비록 코디네이터를 우회하지만, 비 트랜잭션 연산은 멀티 아이템 트랜잭션과의 직렬화가 보장
  3. Transactions update items in place.
    1. DynamoDB는 MVCC 대신 단일 버전 스토어를 사용
    2. 단일 버전 스토어 환경에서 읽기 및 쓰기 트랜잭션은 충돌 가능성이 존재
  4. Transactions do not acquire locks.
    1. RDBMS의 2PL은 동시성 제한, 데드락, 복구 메커니즘 필요 등의 단점을 가진다.
    2. DynamoDB는 락킹을 피하고 동시성을 높이기 위해 낙관적 동시성 제어 사용
  5. Transactions are serially ordered using timestamps.
    1. DynamoDB 트랜잭션은 타임스탬프를 기반으로 직렬 순서화
    2. 각 트랜잭션은 타임스탬프를 할당받아 실행 순서가 결정되며, 이를 통해 직렬 가능성을 보장

DynamoDB 트랜잭션 처리 구조

  • DynamoDB로 보내는 모든 연산은 요청 라우터에 모두 도착
    • 각 요청이 유효한지 확인
    • 해당 요청을 처리할 스토리지 노드를 결정 → 요청을 전달
      • 어느 키가 어느 스토리지 노드에 있는지에 대한 정보는 메타데이터 서브시스템에서 관리
  • 트랜잭션 연산의 경우(ex: TransactWriteItems, TransactGetItems)
    • 마찬가지로 요청 라우터에서 시작
    • 인증 및 권한 부여 후, 트랜잭션 코디네이터라는 서버 그룹으로 전달
    • 트랜잭션 코디네이터는 two-phase coordination을 수행한다.

Two-Phase Commit, 2PC

분산 시스템 환경에서 여러 참여자(노드)가 하나의 트랜잭션을 원자적으로 처리하기 위한 합의 알고리즘

  • 준비 단계
    • 각 참여자는 자신의 상태를 확인하고, 트랜잭션을 성공적으로 처리할 수 있다면 준비 완료(Prepare) 응답을 코디네이터에게 보낸다.
  • 커밋 단계
    • 모든 참여자가 “준비 완료” 응답을 보낸 경우
      • 코디네이터는 커밋(Commit) 결정 → 트랜잭션을 데이터베이스에 반영하고 완료
      • 하나라도 거부한다면 코디네이터는 롤백 결정을 내림

DynamoDB 트랜잭션의 2단계 프로토콜

  • 준비 단계
    • 코디네이터는 트랜잭션에 포함된 아이템을 포함해 준비 메시지를 스토리지 노드로 보낸다.
      • 연산, 트랜잭션 ID, 트랜잭션 타임스탬프
    • 스토리지 노드가 여러 조건을 만족하면 메시지를 수락한다.
      • 트랜잭션 타임스탬프가 아이템의 마지막 쓰기 타임스탬프보다 커야 한다
  • 커밋 또는 취소 단계
    • 모든 준비 성공 시 커밋, 하나라도 실패 시 취소 → 2PC와 유사

DynamoDB 타임스탬프 순서화 방식

  • DynamoDB는 타임스탬프 순서화 방식을 사용하여 트랜잭션의 논리적인 실행 순서를 결정
    • 실제 실행 순서와는 다를 수 있지만 결과적으로는 논리적 순서를 따르는 것처럼 보이게 하는 것임
  • 트랜잭션 요청을 받으면, 트랜잭션 코디네이터는 자신의 현재 시계 값을 사용하여 트랜잭션에 타임스탬프를 할당
    • 더 정확한 시계를 사용하면 더 많은 트랜잭션이 성공하고, 직렬화 순서가 실제 시간 순서에 더 부합
    • DynamoDB 코디네이터들은 AWS time-sync 서비스를 통해 시계 동기화를 유지 → 수 마이크로초 수준의 정밀도
  • 많은 수의 트랜잭션 코디네이터를 병렬로 운영하고, 서로 다른 트랜잭션에 타임스탬프를 할당
  • 스토리지 노드들은 이 타임스탬프를 기준으로 독립적으로 트랜잭션을 처리
  • 기존의 클래식한 타임스탬프 순서화 방식을 키-값 스토어 환경에 맞춰 최적화했음
    • 트랜잭션 외부의 개별 아이템 읽기(Get) 연산은 항상 성공적으로 수행
      • Get 연산은 트랜잭션 코디네이터를 거치지 않고 해당 키를 담당하는 스토리지 노드로 직접 라우팅
      • 스토리지 노드는 준비 중인 트랜잭션의 존재 여부와 관계없이 가장 최신 저장 값을 즉시 반환
    • 트랜잭션 외부의 개별 아이템 쓰기(Put, Modify, Delete) 연산은 대부분의 경우 즉시 실행되고, 준비 중인 트랜잭션보다 먼저 직렬화
      • 예외가 있는데, 준비 중인 트랜잭션이 사전 조건을 검사했고, 새로운 쓰기 연산이 이 조건을 위반할 수 있는 경우이다.
        • 은행 계좌에서 100달러를 인출하는 트랜잭션이 잔액 조건을 확인하고 준비 상태
        • 다른 인출 트랜잭션이 잔액을 100달러 미만으로 줄이는 것을 허용할 수 없다.
    • 앞선 예시로 위반하더라도, 스토리지 노드는 쓰기 연산을 즉시 거부할 필요 없이 버퍼링
      • 준비 중인 트랜잭션이 완료(커밋 또는 롤백) 후, 큐에 저장된 쓰기 연산에 나중 타임스탬프를 할당하고 트랜잭션 이후에 직렬화
    • 트랜잭션의 쓰기 연산이 스토리지 노드에 도착했을 때, 이미 더 늦은 타임스탬프의 쓰기(개별 Put 또는 트랜잭션 Put)가 수행되었더라도 트랜잭션을 여전히 수락하고 준비 상태로 만들 수 있다.
      • 해당 Put과 Delete 연산은 무시된다. 더 늦은 Put 연산에 의해 완전히 덮여졌을 것이기 때문
      • Modify의 경우에는 적용되지 않는다.
      • 또한 더 늦은 타임스탬프를 가진 트랜잭션의 Put(또는 Delete) 연산이 마지막으로 수행되기만 하면 실제로 어떤 순서로든 커밋될 수 있다.
    • 데이터를 읽기만 하는 트랜잭션이면 단일 라운드로 끝낼 수 있다. 즉, GetItemWithTimestamp를 통해 2단계 프로토콜의 복잡성을 줄임
      • 요청을 보낼 때 Read Timestamp를 동봉해 스토리지 노드로 보냄
      • 스토리지 노드에서는 아이템의 마지막 쓰기 타임스탬프가 요청에 포함된 읽기 타임스탬프보다 이전인지 확인
      • 스토리지 노드는 데이터의 시간(마지막 쓰기 타임스탬프)이 “읽기 시간”보다 이전이라면 데이터 반환 → 즉 요청 시점까지의 최신 데이터를 보장할 수 있는 경우에만 결과 반환
      • 코디네이터는 모든 노드들이 결과값을 반환하면 바로 클라이언트에 반환
    • 트랜잭션에서 쓰려는 모든 아이템이 동일한 파티션에 속하는 경우(즉, 동일한 스토리지 노드에 저장되는 경우), 별도의 준비 및 커밋 라운드 없이 단일 라운드로 트랜잭션을 실행할 수 있다.