들어가며

DynamoDB의 UpdateItem API는 카운터 증감이나 상태 변경 같은 ‘읽기-수정-쓰기’ 연산을 단 한 번의 호출로 실행합니다. 이때 여러 클라이언트가 동시에 같은 아이템을 수정하려 할 때, 어떻게 데이터의 정합성과 원자성(Atomicity)을 보장하는지 그 내부 동작 원리가 궁금할 수 있습니다.

결론부터 말하면, 특정 아이템이 속한 파티션의 모든 쓰기 요청을 단일 리더(Leader) 노드가 직렬화(Serialize)하여 순차 처리하기 때문입니다. 이 구조 덕분에 동시 쓰기 요청이 서로를 덮어쓰거나 유실되지 않고 안전하게 처리됩니다.

핵심 원리 요약

개념설명
단일 파티션 리더특정 파티션 키에 대한 모든 쓰기 요청은 하나의 리더 노드가 전담함
쓰기 요청 직렬화리더는 수신한 쓰기 요청을 큐처럼 순서대로 하나씩 처리함
동기식 복제리더는 변경 사항을 다수의 팔로워 노드에 복제하고, 확인 응답을 받아야 최종 커밋함
원자성 보장이 세 가지 요소가 결합되어 UpdateItem 연산의 원자성을 보장함

원자성 보장 메커니즘 상세

1. 파티션 기반 분산과 리더 선정

  • DynamoDB 테이블 데이터는 파티션 키(Partition Key)를 기준으로 여러 파티션에 분산 저장됨
  • 각 파티션은 3개의 복제본(Replica)으로 구성되며, 서로 다른 가용 영역(AZ)에 배치됨
  • 복제본 중 하나는 리더(Leader)가 되고 나머지는 팔로워(Follower) 역할을 수행함

2. 단일 리더로의 쓰기 요청 라우팅

  • 클라이언트의 UpdateItem 요청은 먼저 DynamoDB 요청 라우터(Request Router)에 도달함
  • 요청 라우터는 아이템의 파티션 키를 해싱하여 데이터가 속한 파티션을 찾고, 해당 파티션의 단일 리더 노드로 요청을 전달함
  • 따라서 같은 아이템에 대한 모든 쓰기 요청은 항상 동일한 리더 노드가 처리하게 됨

3. 리더 노드의 쓰기 요청 직렬화

  • 원자성 보장의 핵심으로, 리더 노드는 동시에 도착한 여러 쓰기 요청을 병렬로 처리하지 않음
  • 대신, 요청을 내부적으로 큐에 넣고 하나씩 순서대로(Serially) 처리함. 이는 아이템에 대한 비관적 락(Pessimistic Lock)과 유사한 효과를 가져옴
  • 예를 들어, 두 요청이 동시에 SET counter = counter + 1을 호출하면, 리더는 첫 번째 요청의 ‘읽기-수정-쓰기’ 사이클을 완전히 마친 후에야 두 번째 요청을 시작함
  • 이로 인해 두 번째 요청은 첫 번째 요청이 변경한 최신 값을 읽게 되어 데이터 정합성이 깨지지 않음

4. 동기 복제를 통한 내구성 확보

  • 리더는 쓰기 연산을 완료한 후, 변경 사항을 팔로워 노드들에게 **동기적으로 복제(Synchronous Replication)**함
  • 리더는 쿼럼(Quorum), 즉 과반수 이상의 복제본에 데이터가 성공적으로 기록되었음을 확인한 뒤에 클라이언트에게 성공(200 OK) 응답을 보냄
  • 이 과정 덕분에 UpdateItem 연산은 ‘전부 성공’하거나 ‘전부 실패’하는 원자적 특성을 가지며, 리더 장애 시에도 데이터 유실을 방지함

동시 업데이트 처리 과정 예시

두 클라이언트(A, B)가 counter 값이 0인 아이템에 SET counter = counter + 1 요청을 동시에 보냈다고 가정해 보겠습니다.

  1. 라우팅: A와 B의 요청은 모두 같은 파티션 키를 가지므로, 동일한 리더 노드로 전달됩니다.
  2. 직렬화: 리더 노드는 두 요청을 순서대로 큐에 넣고, 요청 A를 먼저 처리하기로 결정합니다. 요청 B는 대기 상태가 됩니다.
  3. 요청 A 처리: 리더는 현재 counter0을 읽고, 1을 더해 1로 계산합니다. 이 변경 사항을 팔로워들에게 동기 복제한 후 커밋하고, 클라이언트 A에게 성공을 응답합니다.
  4. 요청 B 처리: 요청 A가 완전히 끝난 뒤, 리더는 대기 중이던 요청 B를 처리합니다. 이때 리더는 이미 1로 변경된 최신 counter을 읽습니다. 여기에 1을 더해 2로 계산하고, 마찬가지로 복제 및 커밋 후 클라이언트 B에게 성공을 응답합니다.

결과적으로 counter의 최종 값은 2가 됩니다. 쓰기 직렬화 덕분에 두 요청이 모두 0을 읽고 값을 1로 덮어쓰는 경쟁 상태(Race Condition)가 원천적으로 방지됩니다.

마무리

DynamoDB의 UpdateItem은 파티션의 단일 리더가 모든 쓰기 요청을 직렬화하고, 동기 복제를 통해 내구성을 보장하는 강력한 메커니즘을 통해 원자성을 제공합니다.

이는 조건부 쓰기(Conditional Writes)에서 사용하는 옵티미스틱 락(Optimistic Lock)과는 달리, 요청을 순차 처리하여 충돌 자체를 방지하는 비관적 락(Pessimistic Lock)과 유사한 방식으로 동작합니다.

이러한 설계 덕분에 개발자는 애플리케이션 레벨에서 복잡한 동시성 제어 로직 없이도 안전하게 아이템을 수정할 수 있습니다.