[AWS] DynamoDB 스키마 설계 가이드

DynamoDB는 RDBMS와 달리 액세스 패턴을 먼저 정의한 후 키를 설계합니다. PK/SK 설계와 GSI를 활용하여 비정규화된 데이터로 빠른 조회를 구현하는 방법을 소개합니다.

[AWS] DynamoDB 스키마 설계 가이드
Photo by Stephen Phillips - Hostreviews.co.uk / Unsplash

개요

AI 프로젝트를 진행하며 DynamoDB 설계 방법론에 대해 정리했습니다. 챗봇 로그, 채팅 저장용이 아닌 크롤링 데이터를 어떻게 설계했는지 정리했습니다.

DynamoDB 설계 원칙

DynamoDB를 포함한 NoSQL을 효율적으로 사용하기 위해선 RDBMS와의 주요 차이점, 접근법 등을 이해하는게 중요합니다. 주요 차이점은 다음과 같습니다

  • RDBMS의 경우 유연성을 목적으로 설계합니다. 쿼리 최적화가 스키마 설계에 영향을 미치지 않지만, 정규화가 중요합니다.
  • DynamoDB의 경우 중요하고 범용적인 쿼리를 가능한 빠르고 저렴하게 수행할 목적으로 설계합니다. 데이터 구조는 비즈니스 사용 사례의 요구 사항에 적합하도록 만듭니다.

따라서 RDBMS의 주요 설계는 엑세스 패턴을 생각하지 않고 정규화된 데이터 모델을 사용합니다. 데이터 모델 변경없이 새 쿼리에 유연하게 대응 가능하며 쿼리 확장이 가능합니다. 반면 NoSQL 주요 설계는 데이터를 정규화하지 않기 때문에 어떻게 데이터를 조회할 것인가를 정의해야 테이블을 생성할 수 있습니다.

구분 RDBMS DynamoDB
설계 순서 데이터 구조 먼저 → 쿼리는 나중에 쿼리(액세스 패턴) 먼저 → 구조는 나중에
데이터 저장 정규화로 중복 제거 비정규화로 조회 최적화
관계 조회 JOIN으로 여러 테이블 연결 단일 쿼리로 필요한 데이터 모두 가져오기
  • RDBMS는 데이터를 깔끔하게 쪼개놓고, 필요할 때 JOIN으로 조립
  • DynamoDB는 자주 쓰는 조합을 미리 합쳐서 저장, 한 번에 가져감

핵심 개념

이를 정리하면 다음과 같습니다.

DynamoDB는 RDBMS와 달리 정규화가 필요 없다. 대신 "어떻게 데이터를 조회할 것인가"를 먼저 정의하고, 그에 맞춰 키를 설계한다.

설계 순서

아래는 AWS 공식 문서에서 제시하는 NoSQL 설계 원칙입니다.

원칙 설명 적용 방법
Keep related data together 관련 데이터를 한 곳에 유지 (locality of reference) 한 테이블에 여러 엔티티 저장, 복합 키 사용
Use sort order 정렬 키로 관련 항목 그룹화 begins_with, between 등으로 효율적 범위 조회
Distribute queries 파티션 전체에 트래픽 균등 분산 핫파티션 방지, 카디널리티 높은 PK 선택
Use GSI 다른 조회 패턴 지원 메인 테이블과 다른 PK/SK 조합 필요 시

이를 기반으로 순서대로 정리해보겠습니다.

1. 액세스 패턴 정의 (어떤 쿼리가 필요한지)

DynamoDB 설계의 첫 단계는 시스템이 충족해야 할 특정 쿼리 패턴을 식별하는 것입니다.

액세스 패턴 설계 시 다음 3가지 데이터 특성을 고려해야 합니다.

특성 의미 설계 영향
Data Size 한 번에 저장/조회할 데이터 양 파티션 분할 방식 결정. 파티션당 최대 10GB
Data Shape 조회할 형태에 맞게 미리 구성 비정규화 수준 결정. RDBMS처럼 조회 시 reshape 하지 않음
Data Velocity 피크 시 쿼리 부하량 파티션 I/O 용량 설계. 파티션당 3,000 RCU / 1,000 WCU 제한

따라서 적절한 파티션과 데이터 구성을 통해 핫파티션(병목)이 발생하지 않게 하는 것도 중요합니다.

프로젝트에서는 크롤링 파이프라인이며 배치 파이프라인이기 때문에 정리하면 다음과 같습니다

액세스 패턴 용도
배치별 전체 결과 조회 특정 배치의 모든 URL 처리 결과 확인
URL 이력 조회 특정 URL이 언제, 어떻게 처리되었는지 이력 확인
상태별 조회 실패한 항목만 모아보기

2. PK/SK 설계 (패턴에 맞게)

DynamoDB에서 사용하는 PK와 SK의 목적은 다음과 같습니다.

역할 설계 기준
PK (Partition Key) 같이 조회할 데이터를 묶는 기준 카디널리티 높을수록 좋음 (균등 분산)
SK (Sort Key) 그 안에서 정렬/필터링할 기준 범위 조회, 계층 구조 표현

배치 크롤링 파이프라인이기 때문에 PK를 배치 단위로 설정하였습니다. → batch#{batch_id}

SortKey는 단순 처리 시간을 확인하기 위해 구성했습니다. → {timestamp}#{url_hash}

response = table.query(
    KeyConditionExpression=Key('pk').eq('batch#20251203104530') & Key('sk').begins_with('20251203')  # 해당 날짜 데이터
)

3. GSI (Global Secondary Index)

메인 테이블과 다른 PK/SK 조합으로 조회할 수 있게 해주는 복사본 인덱스입니다.

어떻게 조회할 것인가를 생각했을 때 조회 패턴이 여러가지라면 사용합니다. 프로젝트에서는 메인 테이블은 배치별 조회용으로 사용했기 때문에 urlHash-index 를 추가하여 패턴을 사용했습니다.

GSI PK SK 용도
urlHash-index urlHash sortKey URL별 이력 조회
# URL 이력 조회 (GSI)
def get_by_url_hash(url_hash: str) -> list[dict]:
    response = table.query(
        IndexName="urlHash-index",
        KeyConditionExpression=Key("urlHash").eq(url_hash),
        ScanIndexForward=False,  # 최신순
    )
    
    return response.get("Items", []
pk sk urlHash sortKey url statusValue ...
batch#20251203104530 2025-12-03T10:45:30Z#a1b2c3 a1b2c3 2025-12-03T10:45:30Z https://website1-a.com 10400
batch#20251203104530 2025-12-03T10:45:31Z#d4e5f6 d4e5f6 2025-12-03T10:45:31Z https://website2-b.com 90000

요약

DynamoDB는 RDBMS와 달리 정규화가 필요 없습니다. 대신 "어떻게 데이터를 조회할 것인가"를 먼저 정의한 뒤 키를 설계합니다. PK는 조회할 데이터의 묶음이며, SK는 가져온 데이터를 정렬하는 키 입니다. 다른 엑세스 패턴이 필요한 경우 GSI를 사용합니다.