DynamoDB의 Type 속성
SQL에서는 행이 속한 테이블이 곧 그 타입입니다 — documents에 있는 행은 문서입니다.
DynamoDB의 단일 테이블은 모든 엔터티를 하나의 스키마 아래에 섞어 두므로, 아이템 자체에는
"이게 뭐지?"라는 질문에 대한 내장된 답이 없습니다.
Type 속성은 그 답을 되돌려 줍니다. 모든 아이템에 그것이 나타내는 엔터티를 명명하는 단순한 문자열을 담는 것입니다.
DynamoDB의 Type 속성이란 무엇인가요?
Type 속성은 모든 아이템에 찍는 단순한 문자열로 — EntityType: "Document" 같은 — 해당 아이템이 나타내는 엔터티를 명명합니다. 단일 테이블은 하나의 스키마 아래 여러 엔터티를 혼합하기 때문에, 아이템 자체에는 내장된 타입이 없습니다. Type 속성이 그 답을 되돌려 주어, 코드가 행을 식별하고, GSI를 한 엔터티로 필터링하며, 마이그레이션에서도 살아남을 수 있게 합니다.
- 모든 쓰기에 Type을 찍으세요. 예외 없이 모든 아이템에 속성 하나 —
EntityType: "Document"— 를 둡니다. 몇 바이트의 비용으로 나중에 큰 수고를 덜어 줍니다. - 혼합 파티션에서 엔터티를 식별합니다.
Query는 워크스페이스, 문서, 댓글을 함께 반환하는데, Type이 있으면 키 접두사를 파싱하지 않고도 코드가 어느 것이 무엇인지 알 수 있습니다. - GSI에서 단일 엔터티 필터링을 가능하게 합니다. Type을 인덱스에 프로젝션하면 오버로드된 인덱스를 정확히 하나의 엔터티 타입으로 좁힐 수 있습니다.
- 마이그레이션을 위한 비상 탈출구입니다. 재모델링을 위해 내보내거나 한 엔터티를 자기만의 테이블로 옮길 때, Type은 기준으로 분할하는 컬럼이 됩니다.
혼합 테이블이 타입을 잃는 이유
단일 테이블 설계는 모든 엔터티를 PK와 SK 같은 범용 키
뒤에 하나의 테이블에 저장합니다. 그게 바로 핵심입니다 — 하나의 Query가 부모와 그 자식들을
함께 반환합니다. 하지만 그 결과 파티션은 이질적이게 됩니다.
SaaS 문서 협업 앱을 예로 들어 봅시다. 하나의 워크스페이스 파티션에 워크스페이스 레코드, 그 문서들, 그리고 그 문서들에 달린 댓글이 함께 들어 있습니다:
| PK | SK | attributes |
|---|---|---|
| WS#acme | META | name, plan, seats |
| WS#acme | DOC#a1#META | title, owner, wordCount |
| WS#acme | DOC#a1#CMT#0007 | author, body, createdAt |
| WS#acme | DOC#a1#CMT#0008 | author, body, createdAt |
Query PK = "WS#acme"는 네 개의 아이템을 모두 한 번의 과금되는 읽기로 돌려줍니다. 이제
코드에는 가공되지 않은 아이템 목록만 있고, 어느 것이 문서이고 어느 것이 댓글인지 확실히
구분할 방법이 없습니다 — SK를 문자열 매칭하는 것 말고는요. 그런데 그건 키 형식이 바뀌는
순간 깨지는 취약한 방법입니다.
모든 아이템에 Type을 찍으세요
해법은 모든 쓰기에 엔터티를 명명하는 속성 하나를 두는 것입니다:
| PK | SK | EntityType | title |
|---|---|---|---|
| WS#acme | META | Workspace | — |
| WS#acme | DOC#a1#META | Document | Q3 Roadmap |
| WS#acme | DOC#a1#CMT#0007 | Comment | — |
item.EntityType === "Document"로 분기하는 것은 안정적인 동등성 검사입니다.
SK.startsWith("DOC#") && SK.includes("#CMT#")를 파싱하는 것은 키를 바꾸는 순간 깨지는
추측입니다. Type은 읽기 로직을 키 인코딩에서 분리해 줍니다 — 그게 진짜 이득입니다.
한 번의 읽기가 세 가지 엔터티 타입을 반환하고, Type 속성은 키를 건드리지 않고도 각 아이템을 올바른 핸들러로 라우팅합니다.
GSI를 한 엔터티로 좁히기
Type은 인덱스에서 진가를 발휘합니다. "이 워크스페이스에서 최근에 변경된 모든 것을 최신순으로"
나열하기 위해 GSI1PK = WS#acme, GSI1SK = updatedAt로 키를 지정한 GSI를 추가했다고 합시다.
오버로드된 인덱스는 문서 그리고 댓글을 모두 쓸어 담지만, 피드 UI는 문서만 원할 수 있습니다.
이를 좁히는 두 가지 방법이 있고, 그 차이는 곧 돈입니다:
| 접근 방식 | 비용 | 사용 시점 |
|---|---|---|
Type에 대한 FilterExpression | 일치하는 모든 아이템을 읽고 그 전부에 과금한 뒤, 읽은 다음에 불일치 항목을 버림 | 결과에 혼합 엔터티가 드묾; 빠르게 출시하고 싶을 때 |
희소 인덱스(Type을 GSI1PK에) | 원하는 엔터티만 애초에 인덱스에 들어감 | 한 엔터티가 지배적임; 낭비를 전혀 원치 않을 때 |
FilterExpression은 아이템이 읽힌 후, 용량이 소모된 후에 실행됩니다 — 필터링이 읽기
비용을 줄이지 않는다는 점을 AWS는 명시하고 있습니다
(DynamoDB 개발자 안내서: FilterExpression).
Type에 대한 필터링은 정직하지만 공짜는 아닙니다. 버리는 댓글에 대해서도 비용을 지불합니다.
피드를 문서로 좁히려면, 쿼리에 Type 속성에 대한 조건을 실어 보냅니다.
FilterExpression, 이름, 값은 DynamoDB 표현식 빌더로
조립하세요 — 예약어를 잘못 입력하지 않도록 #t = :doc 플레이스홀더를 생성해 줍니다.
KeyConditionExpression GSI1PK = :ws
FilterExpression #t = :doc
ExpressionAttributeNames { "#t": "EntityType" }
ExpressionAttributeValues { ":ws": "WS#acme", ":doc": "Document" }
인덱스가 오직 문서만 담아 필터를 아예 건너뛰게 하고 싶나요? 문서 아이템에만 GSI1PK를
쓰세요 — 희소 인덱스입니다. GSI 키가 없는 아이템은 인덱스로 복제되지 않으므로, 읽기는
문서만 건드립니다. 어떤 아이템이 자격을 갖췄는지 작성기에 알려 주는 것이 바로 Type 속성입니다.
값을 안정적이고 단수로 유지하세요
값을 한 번 고르고 enum처럼 다루세요. Document이지, 어떤 때는 Doc이고 어떤 때는 document이면
안 됩니다 — 흔들리는 값은 값이 없는 것보다 나쁩니다. 동등성 검사가 한 표기에서는 통과하고
다른 표기는 조용히 놓치기 때문입니다.
아이템당 Type은 하나입니다. 어떤 아이템이 두 엔터티처럼 느껴진다면, 그건 보통 모델링 냄새입니다 — 하나의 행이 두 모자를 쓰는 게 아니라, 각자 자기 컬렉션이나 정렬 키 범위에 있는 두 아이템이어야 합니다.
마이그레이션이라는 보상
필요해지기 전에 Type을 찍어 두어야 하는 이유는 재모델링입니다. 권장되는 재모델링 경로는 내보내기, 변환, 다시 가져오기이며, AWS는 바로 이런 종류의 오프라인 재구성을 위한 S3로의 대량 내보내기를 문서화하고 있습니다 (DynamoDB를 S3로 내보내기).
그날이 오면, Type은 여러분이 GROUP BY 하는 컬럼입니다. 댓글을 자기만의 테이블로 올리거나,
분석 웨어하우스를 위해 내보낸 데이터를 엔터티별 파일로 다시 정규화하고 싶나요? EntityType으로
덤프를 분할하면 됩니다. 그게 없으면, 수백만 행에 걸쳐 키를 역설계하는 처지로 되돌아갑니다.
다음 단계
Type 속성은 값싼 보험입니다. 혼합 읽기에서 엔터티를 식별하고, 오버로드된 GSI를 필터링하며, 재모델링할 때 깔끔하게 분할합니다. 첫날부터 모든 쓰기에 찍으세요 — 운영 중인 테이블에 나중에 끼워 넣으려면 전체 백필이 필요합니다.
관련 읽을거리: 이것이 받쳐 주는 혼합 파티션 패턴에 관한 단일 테이블 설계,
희소 인덱스 뒤의 인덱스 형태를 고르기 위한 GSI vs LSI, 그리고
FilterExpression이 절대 읽기 비용을 줄여 주지 않는 이유에 관한
Query vs Scan.
DynamoDB 표현식 빌더로 Type에 대한 필터를 만들고, DynoTable을 사용해 보세요 — 실제 혼합 엔터티 테이블을 둘러보며 Type 컬럼이 모든 아이템에 걸쳐 줄 맞춰 정렬되는 것을 확인할 수 있습니다.