home
Dynamodb의 트랜잭션
DynamoDB 트랜잭션 특징
- Transactions are submitted as single request
- 기존의 DB 트랜잭션 방식은 장시간 실행될 수 있다.
- DynamoDB에서는 단일 요청으로 제출되는 트랜잭션 제공
- Transactions rely on a transaction coordinator while non-transaction operations bypass the two-phase coordination
- DynamoDB 트랜잭션은 트랜잭션 코디네이터를 사용하여 관리
- 2단계 커밋(2PC) 프로토콜을 이용해 원자성 보장
- 성능상의 이유로,
Get
및Put
과 같은 일반적인 비 트랜잭션 연산은 코디네이터를 거치지 않고 스토리지 서버에서 직접 실행 - 비록 코디네이터를 우회하지만, 비 트랜잭션 연산은 멀티 아이템 트랜잭션과의 직렬화가 보장
- DynamoDB 트랜잭션은 트랜잭션 코디네이터를 사용하여 관리
- Transactions update items in place.
- DynamoDB는 MVCC 대신 단일 버전 스토어를 사용
- 단일 버전 스토어 환경에서 읽기 및 쓰기 트랜잭션은 충돌 가능성이 존재
- Transactions do not acquire locks.
- RDBMS의 2PL은 동시성 제한, 데드락, 복구 메커니즘 필요 등의 단점을 가진다.
- DynamoDB는 락킹을 피하고 동시성을 높이기 위해 낙관적 동시성 제어 사용
- Transactions are serially ordered using timestamps.
- DynamoDB 트랜잭션은 타임스탬프를 기반으로 직렬 순서화
- 각 트랜잭션은 타임스탬프를 할당받아 실행 순서가 결정되며, 이를 통해 직렬 가능성을 보장
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를 동봉해 스토리지 노드로 보냄
- 스토리지 노드에서는 아이템의 마지막 쓰기 타임스탬프가 요청에 포함된 읽기 타임스탬프보다 이전인지 확인
- 스토리지 노드는 데이터의 시간(마지막 쓰기 타임스탬프)이 “읽기 시간”보다 이전이라면 데이터 반환 → 즉 요청 시점까지의 최신 데이터를 보장할 수 있는 경우에만 결과 반환
- 코디네이터는 모든 노드들이 결과값을 반환하면 바로 클라이언트에 반환
- 트랜잭션에서 쓰려는 모든 아이템이 동일한 파티션에 속하는 경우(즉, 동일한 스토리지 노드에 저장되는 경우), 별도의 준비 및 커밋 라운드 없이 단일 라운드로 트랜잭션을 실행할 수 있다.
- 트랜잭션 외부의 개별 아이템 읽기(