고급5분 분량

다운타임 없는 DynamoDB 마이그레이션

SQL에서 넘어왔다면 마이그레이션은 모든 행을 다시 쓰는 동안 테이블을 잠그는 ALTER TABLE입니다. DynamoDB에는 변경할 스키마가 없습니다 — 아이템은 스키마가 없으므로, 속성이나 새 엔티티 유형을 추가하는 것은 거저입니다.

어려운 부분은 새 데이터가 처리해야 하는 액세스 패턴이며, 멈춰 세우고 다시 쓰는 일 없이 살아 있는 데이터를 그것에 맞게 재형성하는 것입니다.

다운타임 없이 DynamoDB 테이블을 마이그레이션하려면 어떻게 해야 하나요?

DynamoDB에는 ALTER TABLE이 없으므로 마이그레이션 중에 테이블이 잠기지 않습니다. UpdateTable을 통해 속성, 새 키 형태, 또는 새 를 온라인으로 추가한 뒤, 라이브 데이터를 점진적으로 재형성합니다. 읽기 시 지연 백필하거나 스로틀된 스윕으로 기존 아이템을 백필하고, 전환 기간 동안 두 형식을 이중 쓰기합니다. 일괄 전환 시점은 없습니다.

  • ALTER TABLE이 없습니다. 아이템은 스키마가 없습니다. "마이그레이션"은 속성, 새 키 형태, 또는 새 인덱스를 추가하는 것을 의미합니다 — 고정된 컬럼 집합을 다시 쓰는 것이 절대 아닙니다.
  • 새 쓰기는 쉽고, 옛 아이템이 문제입니다. 기존 행은 새 속성을 지니지 않으므로, 새 인덱스나 쿼리가 백필하기 전까지 그것들을 조용히 놓칩니다.
  • 인덱스는 온라인으로 추가하고, 백필은 지연시키세요. UpdateTable은 살아 있는 테이블에 GSI를 만듭니다. 옛 아이템은 읽기 시(지연) 또는 통제된 스윕으로 백필하세요 — 절대 한 날에 전부 전환하지 마세요.
  • 전환 기간 동안 이중 쓰기하세요. 두 형태가 공존하는 동안, 어느 읽기 경로도 낡지 않도록 옛 형식과 새 형식을 함께 쓰세요.

컬럼이 아니라 액세스 패턴으로 틀 잡기

하나의 테이블에서 SaaS 워크스페이스 제품을 운영한다고 합시다. 아이템은 PK = "WS#<id>"를 쓰고 SK는 엔티티별로 오버로딩됩니다:

PKSKattributes
WS#a91METAname, tier
WS#a91DOC#2026-04-01#x7title, author, body
WS#a91DOC#2026-04-02#k2title, author, body

이제 제품 팀이 문서에 댓글을 원하고, 새 읽기를 추가합니다: "한 멤버가 워크스페이스 전반에 작성한 모든 댓글을, 최신 순으로 나열하기." 그 마지막 절이 마이그레이션입니다. 새 엔티티 유형만이라면 사소합니다. 현재 키가 답할 수 없는 쿼리를 처리하는 것이 진짜 일입니다.

새 엔티티 유형을 먼저 추가하기

댓글은 그저 같은 파티션의 새 아이템입니다 — 마이그레이션 의식도, 새 테이블도 없습니다:

PKSKattributes
WS#a91DOC#2026-04-01#x7#CMT#01HZ...author, text, createdAt

PK = "WS#a91"SK begins_with "DOC#2026-04-01#x7#CMT#"를 건 Query는 이미 한 문서의 댓글을 나열합니다. 기존 문서는 손대지 않습니다. 이 절반은 첫날에 출시됩니다 — 같은 파티션이 둘 다 담는 이유는 아이템 컬렉션과 오버로딩된 키를 참고하세요.

새 쿼리에는 GSI가 필요하다

"한 멤버의 모든 댓글, 최신 순"은 베이스 테이블이 처리할 수 없습니다 — memberIdPKSK 접두사도 아닙니다. 그것은 새 인덱스이며, 그것을 올바르게 고르는 것은 자체적인 결정입니다: GSI vs LSI를 참고하세요(LSI는 테이블 생성 시 존재해야 하므로, 살아 있는 테이블의 마이그레이션에는 GSI가 유일한 선택지입니다).

일반적인 GSI1을 추가하고 댓글 아이템에 새 속성을 쓰세요:

GSI1PKGSI1SK
MEMBER#u442026-04-02T09:15:00Z

Query GSI1 WHERE GSI1PK = "MEMBER#u44"ScanIndexForward = false를 걸면 멤버별 최신 순 댓글을 얻습니다.

인덱스를 온라인으로 빌드하기

UpdateTable은 다운타임 없이 살아 있는 테이블에 GSI를 추가합니다. DynamoDB는 기존 아이템을 백그라운드에서 인덱스로 백필합니다. 인덱스는 완료될 때까지 CREATING/백필 중을 보고하고, 그 다음 ACTIVE로 전환됩니다 (GSI 관리하기).

UpdateTable: GSI1 추가인덱스 상태: CREATING기존 아이템 백필상태: ACTIVEGSI1 Query 안전

여기 두 가지 함정이 있습니다. 첫째, AWS는 새 키가 고르지 않게 분산되면 GSI 추가가 베이스 테이블 쓰기를 스로틀할 수 있다고 경고합니다 — 트래픽이 적은 시간대에 추가하고 CloudWatch를 지켜보세요. 둘째, 인덱스는 ACTIVE가 된 후에도 최종적 일관성을 가집니다. 쓰기가 잠시 GSI에 보이지 않을 수 있습니다. GSI가 최종적 일관성을 가지는 이유를 참고하세요.

옛 아이템 백필하기

GSI는 GSI1PK/GSI1SK가진 아이템만 인덱싱합니다. 속성이 존재하기 전에 작성된 당신의 마이그레이션 이전 댓글은 — 백필이 완료된 후에도 절대 나타나지 않습니다. 온라인 GSI 백필은 기존 아이템을 복사하지만, 그것들에 없는 속성을 발명할 수는 없습니다. 값을 추가해야 합니다.

두 가지 전략:

전략동작 방식사용할 때
지연옛 아이템을 읽을 때 새 속성을 되돌려 씀옛 아이템을 자주 읽음; 비용을 조금씩 흘림
스윕페이지를 나눈 Scan이 모든 옛 아이템을 한 번 업데이트마감일까지 GSI를 완성해야 함

스윕의 경우, Scan으로 페이지를 넘기고, 각 옛 댓글에 대해 조건부 UpdateItem으로 인덱스 속성을 추가해 동시 쓰기를 절대 짓밟지 않도록 하세요.

조건은 속성이 아직 존재하지 않는다는 것에 가드를 겁니다. 정확한 ConditionExpressionUpdateExpressionattribute_not_exists(GSI1PK)를 손으로 입력하는 대신 DynamoDB Expression Builder로 만들고 복사하세요.

전환 기간 동안 이중 쓰기하기

모든 옛 아이템이 새 속성을 지닐 때까지, 두 형태가 공존합니다. 쓰기 경로는 모든 쓰기에서 — 새 댓글과 옛 댓글에 대한 어떤 업데이트든 — 새 형식을 채워야 합니다. 그래야 간극이 줄어들기만 합니다.

검증할 수 있는 백필 종료 조건을 고르세요: 스윕이 테이블 전체를 페이징했거나, 지연 경로가 충분히 오래 실행되어 변환되지 않은 아이템이 설계상 낡았거나. 그때에야 옛 읽기 경로를 제거하세요. 이를 건너뛰는 것이 일부 쿼리가 조용히 짧은 결과를 반환하는 와중에 마이그레이션이 "완료"되는 방식입니다.

DynoTable에서 테이블을 페이징하여, 백필 중 새 인덱스 속성이 빠진 아이템을 찾아내는 모습.
DynoTable에서 테이블을 페이징하여, 백필 중 새 인덱스 속성이 빠진 아이템을 찾아내는 모습.

함정

  • 속성 추가 ≠ 백필됨. 새 GSI는 옛 아이템에 대해 비어 있는 상태로 시작합니다. 쿼리를 믿기 전에 커버리지를 검증하세요.
  • 제자리에서 키 바꾸기는 마이그레이션이 아니라 재작성입니다. 아이템의 PK/SK를 변경할 수 없습니다 — 새 키로 새 아이템을 쓰고 옛 것을 삭제합니다. 복사-후-삭제로 계획하고, 그 사이에는 이중 읽기하세요.
  • 트랜잭션적 전환은 없습니다. 테이블 전체가 전환되는 순간은 없습니다. 두 형태가 살아 있는 동안에도 모든 단계가 안전하도록 설계하세요.

다음 단계

단일 테이블 설계에서 새 키와 오버로딩된 컬렉션을 점검하고, 살아 있는 테이블을 페이징하여 백필이 완료되었음을 확인하세요. DynoTable을 사용해 테이블을 둘러보고, 백필되지 않은 아이템을 찾아내고, 자신의 데이터에 조건부 업데이트를 실행하세요.

업데이트됨