DynamoDB는 AWS DVA-C02 시험에서 두 번째로 자주 출제되는 핵심 서비스입니다. 특히 키 설계와 쿼리 방법을 제대로 이해하는 것이 합격의 열쇠입니다.
DynamoDB, 도대체 어떤 데이터베이스인가요?
일반적인 데이터베이스(RDS, MySQL 등)는 정해진 표(테이블) 구조에 데이터를 넣습니다. 반면 DynamoDB는 NoSQL 데이터베이스로, 구조가 유연하고 아주 빠른 속도(밀리초 수준)를 자랑합니다.
쉽게 말하면, 일반 데이터베이스가 엑셀 시트라면 DynamoDB는 포스트잇 묶음과 같습니다. 각 포스트잇(항목)마다 내용이 달라도 되고, 수십억 장을 순식간에 찾을 수 있습니다.
기본 키 설계 — 데이터를 어디에 저장할지 결정하는 핵심
DynamoDB에서 가장 중요한 개념은 기본 키(Primary Key)입니다. 기본 키는 데이터를 어느 서버(파티션)에 저장할지 결정합니다.
파티션 키 (Partition Key)
파티션 키는 데이터를 여러 서버에 고르게 나눠주는 기준입니다. 쉽게 말하면, 우체국의 우편번호와 같습니다. 우편번호가 고르게 분포할수록 배달이 빠릅니다.
중요한 원칙은 고유 값이 많은(카디널리티가 높은) 속성을 파티션 키로 선택해야 한다는 것입니다.
나쁜 예: 날짜를 파티션 키로 사용 → 하루에 수만 건이 같은 파티션에 몰려서 해당 서버에만 부하가 집중됩니다. 이것을 핫 파티션 문제라고 부릅니다.
좋은 예: 사용자 ID를 파티션 키로 사용 → 사용자마다 다른 서버에 분산되어 부하가 고르게 퍼집니다.
정렬 키 (Sort Key)
같은 파티션 안에서 데이터를 순서대로 정리하는 역할을 합니다. 파티션 키와 정렬 키를 함께 사용하면 범위 검색이 가능합니다.
예를 들어, 파티션 키를 사용자 ID로, 정렬 키를 날짜/시각(timestamp)으로 설정하면 특정 사용자의 최근 활동 내역을 순서대로 빠르게 가져올 수 있습니다.
인덱스 — 다양한 방법으로 데이터를 찾고 싶을 때
기본 키만으로는 한 가지 방식으로만 데이터를 검색할 수 있습니다. 다른 속성으로도 빠르게 검색하고 싶을 때 인덱스를 사용합니다. 책의 색인(인덱스)이 원하는 내용을 빠르게 찾게 해주는 것과 같은 원리입니다.
GSI (Global Secondary Index)
GSI는 원래 테이블과 완전히 다른 파티션 키와 정렬 키를 사용해 검색할 수 있게 해줍니다. 테이블을 만든 후에도 언제든지 추가할 수 있어 유연합니다. 단, 별도의 처리 용량이 필요합니다.
예: 사용자 ID 기준 테이블에서 이메일 주소로 검색하고 싶을 때 GSI를 만듭니다.
LSI (Local Secondary Index)
LSI는 같은 파티션 키를 유지하면서 다른 정렬 키로 검색할 수 있게 해줍니다. 단, 테이블을 처음 만들 때만 추가할 수 있습니다. 나중에는 추가 불가능하니 주의가 필요합니다.
Query vs Scan — 효율적인 검색의 핵심
Query (쿼리)
파티션 키를 알고 있을 때 사용합니다. 특정 구역만 검색하므로 매우 효율적이고 빠릅니다. 쉽게 말하면 도서관에서 특정 분류번호 서가로 바로 가서 찾는 방식입니다.
Scan (스캔)
테이블 전체를 처음부터 끝까지 뒤집니다. 원하는 데이터를 찾을 수 있지만, 모든 데이터를 읽어야 하므로 느리고 비용이 많이 듭니다. 쉽게 말하면 도서관의 모든 책을 하나하나 열어보는 방식입니다.
가능하면 Scan을 피해야 합니다. 불가피하게 Scan이 필요하다면 ProjectionExpression(필요한 속성만 가져오기)을 사용하거나 Parallel Scan(여러 스레드로 동시에 스캔)으로 속도를 높입니다.
!DynamoDB의 Query vs Scan
일관성 모델 — 얼마나 최신 데이터를 원하나요?
데이터를 저장한 직후 바로 읽으면, 아직 모든 서버에 전파되지 않아 잠깐 이전 데이터가 보일 수 있습니다. DynamoDB는 두 가지 읽기 방식을 제공합니다.
최종 일관성(Eventually Consistent): DynamoDB의 기본 방식입니다. 약간의 지연 후 최신 데이터가 반영됩니다. 비용이 절반이라 대부분의 경우 이 방식으로 충분합니다.
강한 일관성(Strongly Consistent): 읽는 즉시 최신 데이터를 보장합니다. ConsistentRead=True 옵션을 사용합니다. 비용은 2배이지만, 금융 거래처럼 항상 최신 데이터가 필요한 경우에 사용합니다.
트랜잭션 — 여러 작업을 한 번에 처리해야 할 때
은행 이체를 생각해보세요. 계좌 A에서 돈을 빼고 계좌 B에 넣는 두 작업이 반드시 함께 성공하거나 함께 실패해야 합니다. 이런 경우에 트랜잭션을 사용합니다.
TransactWriteItems: 여러 항목에 대한 쓰기 작업을 원자적으로 처리합니다. 모두 성공하거나 모두 실패합니다.
TransactGetItems: 여러 항목을 원자적으로 읽습니다.
트랜잭션은 일반 작업보다 비용이 2배입니다. 꼭 필요한 경우에만 사용하는 것이 좋습니다.
배치 작업 — 여러 항목을 한꺼번에 처리할 때
BatchWriteItem: 한 번의 요청으로 최대 25개 항목을 쓰거나 삭제합니다.
BatchGetItem: 한 번의 요청으로 최대 100개 항목을 읽습니다.
트랜잭션과 다른 점은 원자적이지 않다는 것입니다. 즉, 일부 항목은 성공하고 일부는 실패할 수 있습니다. 실패한 항목은 UnprocessedItems에 담겨 반환되므로 반드시 재처리해야 합니다.
시험 핵심 정리
"효율적인 쿼리를 위한 키 설계" -- 높은 카디널리티 파티션 키
"다른 속성으로 쿼리, 테이블 생성 후 추가 가능" -- GSI
"같은 파티션 키, 다른 정렬, 생성 시에만 추가" -- LSI
"효율적인 데이터 조회" -- Query (파티션 키 기반)
"전체 테이블 훑기 (비효율적)" -- Scan
"즉시 최신 데이터 필요" -- 강한 일관성 (ConsistentRead=True)
"여러 항목을 원자적으로 쓰기" -- TransactWriteItems
"일괄 쓰기, 일부 실패 가능" -- BatchWriteItem (UnprocessedItems 확인)
핫 파티션을 피하려면 파티션 키의 카디널리티를 높여야 함