[DynamoDB] 4. Vertical Partitioning

조성우·2025년 10월 12일

DynamoDB

목록 보기
4/4
post-thumbnail

Vertical Partitioning(수직 파티셔닝)단일 테이블 설계의 핵심 원칙을 구현하는 방법론이다. Item을 더 작은 데이터 청크로 나누고, 함께 액세스해야 하는 데이터를 하나의 파티션 키 아래에 논리적으로 그룹화하여, 성능과 확장성 및 비용 효율성을 높이는 데에 중점을 둔다.


1. Item Collection (항목 컬렉션)

: 동일한 Partition Key 값을 공유하지만, 서로 다른 Sort Key 값을 갖는 항목들의 그룹

Partition key: user_idSort key: update_timestampAttribute: new_stateAttribute: entity_type
aman_dhingra2024-01-13T14:37:07.999Z{"First Name":{"S":"Aman"},"CityPreference":{"S":"London"}}profile_update
aman_dhingra2024-04-15T11:35:57.557Z{"First Name":{"S":"Aman"},"CityPreference":{"S":"Dublin"}}profile_update
aman_dhingra2024-07-10T20:34:48.296Z{"First Name":{"S":"Aman"},"CityPreference":{"S":"Dubai"}}profile_update
lorenzo_ripani2023-10-29T22:35:42.427Z{"First Name":{"S":"Lorenzo"},"CityPreference":{"S":"Milan"}}profile_update

위에서 user_id: aman_dhingra를 갖는 모든 항목들의 그룹이 Item Collection이다.

Query API

항목 컬렉션에 저장된 데이터를 읽을 때는 DynamoDB의 Query API를 사용할 수 있다.

  1. 단일 항목 컬렉션: 하나의 항목 컬렉션만 읽을 수 있다. 즉, 단일 PK 값을 제공해야 한다.
  2. 정렬: 반환되는 항목들은 정렬 키 값을 기준으로 정렬된다.
  3. 필터링 (begins_with): 정렬 키 값에 대한 다양한 연산자들을 지원하며, 그중 begins_with() 연산자는 항목 컬렉션 내에서 원하는 데이터 그룹을 효율적으로 검색하는 데 필수적이다.

begins_with()
ISO 8601 타임스탬프(YYYY-MM-DDThh:mm:ssZ)를 정렬 키로 사용하는 경우, sort_key begins_with('2024-01')와 같은 쿼리 조건을 사용하여 2024년 1월에 발생한 모든 변경 이력을 가져올 수 있다. 또한 엔티티 이름을 접두사로 사용할 수도 있다. (ex: o#12, ,p#34)
일종의 Group By인 셈이다.


Partition key: PKSort key: SKAttr: typeAttr: titleAttr: artistAttr: countAttr: user
s#song1AdetailsHow Much Is The Fish?Scooter
s#song1A#downloadstotal_downloads100
s#song1d#download-1downloadamdhing
s#song1d#download-2downloadripani

추가로 위 예시처럼, 항목 컬렉션은 다양한 엔티티의 데이터(1:1, 1:m, m:n 관계)를 같은 테이블 내에 함께 저장할 수 있게 해준다. song_id를 PK로 공유하는 항목 컬렉션에는 노래 세부 정보, 총 다운로드 수, 그리고 개별 다운로드 기록과 같은 여러 엔티티가 포함될 수 있다.
(다운로드 수를 같이 두지 않은 이유는 곡 정보와 같이 두게 되면 업데이트 비용이 커지기 때문)

키 네임 오버로딩:
항목 컬렉션을 사용해 여러 엔티티를 저장하려면, 파티션 키와 정렬 키에 PK, SK와 같은 일반적인 이름을 사용하는 것이 일반적인 모범 사례이다. 이를 키 네임 오버로딩이라고 하며, 항목 컬렉션이 최대 두 개의 엔티티(PK, SK)에만 국한되지 않고, 정렬 키 값만 잘 설계해 한 파티션 안에 여러 타입의 엔티티 넣을 수 있게 된다.


2. 데이터 분해 (Breaking down)

사실 위에서 알아본 것이 데이터 분해이다.

대용량 JSON 구조를 더 작은 청크로 분해하고, 이들이 동일한 항목 컬렉션의 일부가 되도록 구성해두면, 한 번의 쿼리(Query)로 관련 데이터를 전부 효율적으로 가져올 수 있다.

단 이러한 접근 방식은 읽기 효율성을 위해 데이터 비정규화 및 중복을 수반한다. 쓰기 단계에서 여러 번의 쓰기 작업이 필요할 수도 있지만, 이는 읽기 효율성 최적화를 위한 의도적인 트레이드오프이다.

또한 DynamoDB의 400 KiB 항목 크기 제한도 극복할 수 있겠죠?


관리할 GSI 수 감소

만약 하나의 아이템에 아래와 같은 정보를 다 넣는다고 생각해보자.

(위 표 참고) 노래 제목, 아티스트, 다운로드 횟수, 모든 다운로드 ID 목록

이렇게 하면, 예를 들어 다운로드 ID로 노래를 찾거나, 아티스트 또는 타이틀로 찾으려면 GSI가 여러 개 필요해진다. (예: downloadId로 인덱스 1개, artist로 인덱스 1개, title로 인덱스 1개 등)

GSI를 많이 만드는 게 성능에 크게 문제를 주진 않지만, 관리해야 할 리소스가 늘어난다.
메인 테이블과 별개로, 자체적인 throughput, capacity, autoscaling 등이 존재하기 때문에 각 인덱스마다 설정·모니터링·비용 관리가 필요하다.

그런데 GSI1PK에 아티스트나 다운로드 ID, 타이틀 등을 저장해두면, 하나의 GSI만으로도
해당 속성으로 검색이 모두 가능하다.

PK: GSI1PKSK: GSI1SKPK(table)SK(table)typetitleartistuser
ScooterHow Much Is The Fish?s#song1AdetailsHow Much Is The Fish?Scooter
ScooterRamp! (The Logical Song)s#song2AdetailsRamp! (The Logical Song)Scooter
d#download-1s#song1s#song1d#download-1downloadHow Much Is The Fish?Scooteramdhing
d#download-2s#song1s#song1d#download-2downloadHow Much Is The Fish?Scooterripani
d#download-3s#song2s#song2d#download-1downloadRamp! (The Logical Song)Scooteramdhing

GSI1PK에 아티스트나 download ID가 들어가, 같은 인덱스에서 아티스트, download 검색이 가능하다.


3. 무조건 분해하는 게 좋을까?

특정 상황에서는 데이터를 너무 잘게 쪼개는 것이 오히려 비효율적일 수 있다.

  1. 쓰기 작업이 매우 많고, 업데이트는 거의 없는 경우 (주식 거래 정보, 로그 데이터)
    • 문제점: 이런 상황에서 하나의 데이터(예: 1건의 주식 거래 내역)를 여러 개의 작은 항목으로 쪼개면, 데이터를 저장할 때 여러 번의 쓰기 작업이 필요하게 된다. 이는 쓰기 비용(WCU)을 증가시키고, 데이터를 쓰기 전에 변환해야 하는 복잡성까지 더해진다.
    • (실시간 데이터 집계 등은 DynamoDB Streams 같은 비동기 방식으로 처리하면 된다고 한다.)
  1. 전체 데이터 크기가 이미 1KB 미만으로 매우 작은 경우
    • 문제점: DynamoDB는 쓰기 비용(WCU)을 1KB 단위로 올림하여 계산한다. 만약 900byte 데이터를 100byte짜리 항목 9개로 쪼개서 쓰면, 각 쓰기 작업마다 1 WCU가 부과되어 총 9 WCU가 소모된다.
  1. 분석을 위한 추가 데이터 변환 필요
    • Amazon S3 등으로 내보내 분석 용도로 사용해야 하는 경우
    • 문제점: 데이터를 너무 잘게 쪼개 놓으면, 분석 시스템에서 원래의 의미 있는 데이터 형태로 만들기 위해 분해된 데이터들을 다시 합치는 추가적인 변환 작업이 필요하다. 이는 데이터 파이프라인을 더 복잡하게 만든다.

0개의 댓글