DynamoDB의 관계형 데이터 모델링에 대한 예 - Amazon DynamoDB

DynamoDB의 관계형 데이터 모델링에 대한 예

이 예는 Amazon DynamoDB에서 관계형 데이터를 모델링하는 방법을 설명합니다. DynamoDB 테이블 설계는 관계형 모델링에 표시된 관계형 주문 입력 스키마에 해당합니다. 이 설계는 단일 인접 목록 대신 여러 개의 특수 테이블을 사용하여 명확한 운영 경계를 제공하는 동시에 전략적 GSI를 활용하여 모든 액세스 패턴을 효율적으로 제공합니다.

설계 접근 방식은 집계 지향 원칙을 사용하여 엄격한 개체 경계가 아닌 액세스 패턴을 기반으로 데이터를 그룹화합니다. 주요 설계 결정에는 액세스 상관관계가 낮은 개체에 대해 별도의 테이블 사용, 항상 함께 액세스할 때 관련 데이터 포함, 관계를 식별하기 위한 항목 컬렉션 사용이 포함됩니다.

다음 테이블과 함께 제공되는 인덱스는 관계형 순서 항목 스키마를 지원합니다.

직원 테이블 설계

직원 테이블은 직원 정보를 항목당 단일 엔터티로 저장하고 직원 직접 조회에 최적화되어 있으며 전략적 GSI를 통해 여러 쿼리 패턴을 지원합니다. 이 표는 독립적인 운영 특성과 낮은 교차 엔터티 액세스 상관관계를 가진 엔터티에 대해 별도의 테이블을 설계하는 원칙을 보여줍니다.

테이블은 각 직원이 고유한 엔터티이므로 정렬 키 없이 단순 파티션 키(employee_id)를 사용합니다. 4개의 GSI를 사용하면 다양한 속성을 기준으로 효율적인 쿼리를 수행할 수 있습니다.

  • EmployeeByName GSI - 모든 직원 속성과 함께 INCLUDE 프로젝션을 사용하여 이름으로 전체 직원 세부 정보 검색을 지원하고 employee_id를 정렬 키로 사용하여 중복될 수 있는 이름을 처리합니다.

  • EmployeeByWarehouse GSI - 필수 속성(이름, job_title, hire_date)만 있는 INCLUDE 프로젝션을 사용하여 스토리지 비용을 최소화하는 동시에 웨어하우스 기반 쿼리를 지원합니다.

  • EmployeeByJobTitle GSI - 보고 및 조직 분석을 위해 INCLUDE 프로젝션을 사용하여 역할 기반 쿼리를 활성화합니다.

  • EmployeeByHireDate GSI - 최근 채용과 관련하여 효율적인 날짜 범위 쿼리를 활성화하기 위해 hire_date를 정렬 키로 사용하여 정적 파티션 키 값 "EMPLOYEE"를 사용합니다. 직원 추가/업데이트는 일반적으로 1,000WCU 미만이므로 단일 파티션이 핫 파티션 문제 없이 쓰기 로드를 처리할 수 있습니다.

직원 테이블 - 기본 테이블 구조
employee_id(PK) 이름 phone_number warehouse_id job_title hire_date entity_type
emp_001 John Smith ["+1-555-0101"] wh_sea Manager 2024-03-15 피고용인
emp_002 Jane Doe ["+1-555-0102", "+1-555-0103"] wh_sea 연결 2025-01-10 피고용인
emp_003 Bob Wilson ["+1-555-0104"] wh_pdx 연결 2025-06-20 피고용인
emp_004 Alice Brown ["+1-555-0105"] wh_pdx Supervisor 2023-11-05 피고용인
emp_005 Charlie Davis ["+1-555-0106"] wh_sea 연결 2025-12-01 피고용인
EmployeeByName GSI - 직원 이름 쿼리 지원
이름(GSI-PK) employee_id(GSI-SK) phone_number warehouse_id job_title hire_date
Alice Brown emp_004 ["+1-555-0105"] wh_pdx Supervisor 2023-11-05
Bob Wilson emp_003 ["+1-555-0104"] wh_pdx 연결 2025-06-20
Charlie Davis emp_005 ["+1-555-0106"] wh_sea 연결 2025-12-01
Jane Doe emp_002 ["+1-555-0102", "+1-555-0103"] wh_sea 연결 2025-01-10
John Smith emp_001 ["+1-555-0101"] wh_sea Manager 2024-03-15
EmployeeByWarehouse GSI - 웨어하우스 쿼리 지원
warehouse_id(GSI-PK) employee_id(GSI-SK) 이름 job_title hire_date
wh_pdx emp_003 Bob Wilson 연결 2025-06-20
wh_pdx emp_004 Alice Brown Supervisor 2023-11-05
wh_sea emp_001 John Smith Manager 2024-03-15
wh_sea emp_002 Jane Doe 연결 2025-01-10
wh_sea emp_005 Charlie Davis 연결 2025-12-01
EmployeeByJobTitle GSI - 직함 쿼리 지원
job_title(GSI-PK) employee_id(GSI-SK) 이름 warehouse_id hire_date
연결 emp_002 Jane Doe wh_sea 2025-01-10
연결 emp_003 Bob Wilson wh_pdx 2025-06-20
연결 emp_005 Charlie Davis wh_sea 2025-12-01
Manager emp_001 John Smith wh_sea 2024-03-15
Supervisor emp_004 Alice Brown wh_pdx 2023-11-05
EmployeeByHireDate GSI - 최근 고용 쿼리 지원
entity_type(GSI-PK) hire_date(GSI-SK) employee_id 이름 warehouse_id
피고용인 2023-11-05 emp_004 Alice Brown wh_pdx
피고용인 2024-03-15 emp_001 John Smith wh_sea
피고용인 2025-01-10 emp_002 Jane Doe wh_sea
피고용인 2025-06-20 emp_003 Bob Wilson wh_pdx
피고용인 2025-12-01 emp_005 Charlie Davis wh_sea

고객 테이블 설계

고객 테이블은 효율적인 계정 담당자 쿼리를 활성화하기 위해 account_rep_id의 전략적 비정규화로 고객 정보를 유지합니다. 이 설계 선택은 쿼리 성능을 위해 약간의 스토리지 오버헤드를 상쇄하므로 고객과 계정 담당자 데이터를 조인할 필요가 없습니다.

이 표는 DynamoDB의 스키마 유연성을 보여주는 목록 속성을 사용하여 고객당 여러 전화번호를 지원합니다. 단일 GSI는 계정 담당자 워크플로를 활성화합니다.

  • CustomerByAccountRep GSI - 전체 고객 레코드 검색 없이 계정 담당자 고객 관리를 지원하기 위해 이름 및 이메일 속성과 함께 INCLUDE 프로젝션 사용

고객 테이블 - 기본 테이블 구조
customer_id(PK) 이름 phone_number 이메일 account_rep_id
cust_001 Acme Corp ["+1-555-1001"] contact@acme.com rep_001
cust_002 TechStart Inc ["+1-555-1002", "+1-555-1003"] info@techstart.com rep_001
cust_003 글로벌 거래자 ["+1-555-1004"] sales@globaltraders.com rep_002
cust_004 BuildRight LLC ["+1-555-1005"] orders@buildright.com rep_002
cust_005 FastShip Co ["+1-555-1006"] support@fastship.com rep_003
CustomerByAccountRep GSI - 계정 담당자 쿼리 지원
account_rep_id(GSI-PK) customer_id(GSI-SK) 이름 이메일
rep_001 cust_001 Acme Corp contact@acme.com
rep_001 cust_002 TechStart Inc info@techstart.com
rep_002 cust_003 글로벌 거래자 sales@globaltraders.com
rep_002 cust_004 BuildRight LLC orders@buildright.com
rep_003 cust_005 FastShip Co support@fastship.com

주문 테이블 설계

주문 테이블은 주문 헤더 및 주문 항목에 대해 별도의 항목과 함께 수직 파티셔닝을 사용합니다. 이 설계를 사용하면 효율적인 액세스를 위해 모든 주문 구성 요소를 동일한 파티션 내에 유지하면서 효율적인 제품 기반 쿼리를 수행할 수 있습니다. 각 주문은 여러 항목으로 구성됩니다.

  • 주문 헤더 - PK=order_id, SK=order_id의 주문 메타데이터 포함

  • 주문 항목 - PK=order_id, SK=product_id가 있는 개별 품목, 직접 제품 쿼리 활성화

참고

이 수직 분할 접근 방식은 쿼리 유연성을 높이기 위해 임베디드 주문 항목의 단순성을 절충합니다. 각 주문 항목은 별도의 DynamoDB 항목이 되어, 단일 요청으로 효율적으로 검색할 수 있도록 모든 주문 데이터를 동일한 파티션 내에 유지하면서 효율적인 제품 기반 쿼리를 가능하게 합니다.

테이블에는 account_rep_id(고객 테이블에서 복제됨)의 전략적 비정규화가 포함되어 있어 고객 조회 없이 직접 계정 담당자 쿼리를 활성화할 수 있습니다. 처리량이 많은 쓰기 시나리오의 경우 OPEN 주문에는 여러 파티션에서 쓰기 샤딩을 활성화할 수 있는 상태 및 샤드 속성이 포함됩니다.

4개의 GSI가 최적화된 프로젝션으로 다양한 쿼리 패턴을 지원합니다.

  • OrderByCustomerDate GSI - 주문 요약 및 항목 세부 정보와 함께 INCLUDE 프로젝션을 사용하여 날짜 범위 필터링으로 고객 주문 기록을 지원합니다.

  • OpenOrdersByDate GSI(Sparse, Sharded) - 5개의 샤드가 있는 다중 속성 파티션 키(상태 + 샤드)를 사용하여 파티션 간에 5,000WPS(초당 쓰기 수)를 분산합니다(각 1,000WPS, DynamoDB의 파티션당 1,000WCU 제한과 일치). OPEN 주문(총 주문의 20%)만 인덱싱하므로 GSI 스토리지 비용을 줄이는 데 도움이 될 수 있습니다. 클라이언트 측 결과 병합을 사용하여 샤드 5개 모두에 병렬 쿼리 필요

  • OrderByAccountRep GSI - 주문 요약 속성과 함께 INCLUDE 프로젝션을 사용하여 전체 주문 세부 정보 없이 계정 담당자 워크플로 지원

  • ProductInOrders GSI - OrderItem 레코드(PK=order_id, SK=product_id)에서 생성된 이 GSI를 사용하면 쿼리가 특정 제품이 포함된 모든 주문을 찾을 수 있습니다. 제품 수요 분석을 위해 주문 컨텍스트(customer_id, order_date, 수량)와 함께 INCLUDE 프로젝션을 사용합니다.

순서 테이블 - 기본 테이블 구조(수직 분할)
PK SK customer_id order_date status account_rep_id quantity 가격 샤드
ord_001 ord_001 cust_001 2025-11-15 CLOSED rep_001
ord_001 prod_100 5 25.00
ord_002 ord_002 cust_001 2025-12-20 OPEN rep_001 0
ord_002 prod_101 10 15.00
ord_003 ord_003 cust_002 2026-01-05 OPEN rep_001 2
ord_003 prod_100 3 25.00
OrderByCustomerDate GSI - 고객 주문 쿼리 지원
customer_id(GSI-PK) order_date(GSI-SK) order_id status total_amount order_items 샤드
cust_001 2025-11-15 ord_001 CLOSED 225.00 [{product_id: "prod_100", qty: 5}]
cust_001 2025-12-20 ord_002 OPEN 150.00 [{product_id: "prod_101", qty: 10}] 0
cust_002 2026-01-05 ord_003 OPEN 175.00 [{product_id: "prod_100", qty: 3}] 2
cust_003 2025-10-10 ord_004 CLOSED 250.00 [{product_id: "prod_101", qty: 5}]
cust_004 2026-01-03 ord_005 OPEN 200.00 [{product_id: "prod_100", qty: 20}] 1
OpenOrdersByDate GSI(Sparse, Sharded) - 높은 처리량 미결 주문 쿼리 지원
상태(GSI-PK-1) 샤드(GSI-PK-2) order_date(SK) order_id customer_id account_rep_id order_items total_amount
OPEN 0 2025-12-20 ord_002 cust_001 rep_001 [{product_id: "prod_101", qty: 10}] 150.00
OPEN 1 2026-01-03 ord_005 cust_004 rep_002 [{product_id: "prod_100", qty: 20}] 200.00
OPEN 2 2026-01-05 ord_003 cust_002 rep_001 [{product_id: "prod_100", qty: 3}] 175.00
OrderByAccountRep GSI - 계정 담당자 주문 쿼리 지원
account_rep_id(GSI-PK) order_date(GSI-SK) order_id customer_id status total_amount
rep_001 2025-11-15 ord_001 cust_001 CLOSED 225.00
rep_001 2025-12-20 ord_002 cust_001 OPEN 150.00
rep_001 2026-01-05 ord_003 cust_002 OPEN 175.00
rep_002 2025-10-10 ord_004 cust_003 CLOSED 250.00
rep_002 2026-01-03 ord_005 cust_004 OPEN 200.00
ProductInOrders GSI - 제품 주문 쿼리 지원
product_id(GSI-PK) order_id(GSI-SK) customer_id order_date quantity
prod_100 ord_001 cust_001 2025-11-15 5
prod_100 ord_003 cust_002 2026-01-05 3
prod_101 ord_002 cust_001 2025-12-20 10

제품 테이블 설계

제품 테이블은 항목 수집 패턴을 사용하여 동일한 파티션 내에 제품 메타데이터와 인벤토리 데이터를 모두 저장합니다. 이 설계는 제품과 인벤토리 간의 식별 관계를 활용합니다. 상위 제품이 없으면 인벤토리가 존재할 수 없습니다. 제품 메타데이터에는 PK=product_id를 SK=product_id와 함께 사용하고 인벤토리 항목에는 SK=warehouse_id를 사용하면 별도의 인벤토리 테이블과 GSI가 필요하지 않으므로 비용이 약 50% 절감됩니다.

이 패턴을 사용하면 개별 웨어하우스 인벤토리(복합 키가 있는 GetItem)와 제품의 모든 웨어하우스 인벤토리(파티션 키에 대한 쿼리) 모두에 대해 효율적인 쿼리를 수행할 수 있습니다. 제품 메타데이터 항목의 total_inventory 속성은 빠른 총 인벤토리 조회를 위한 비정규화된 집계를 제공합니다.

제품 테이블 - 기본 테이블 구조(항목 수집 패턴)
product_id(PK) warehouse_id(SK) product_name category unit_price inventory_quantity total_inventory
prod_100 prod_100 위젯 A 하드웨어 25.00 500
prod_100 wh_sea 200
prod_100 wh_pdx 150
prod_100 wh_atl 150
prod_101 prod_101 도구 B 전자 제품 50.00 300
prod_101 wh_sea 100
prod_101 wh_pdx 200

각 테이블은 특정 글로벌 보조 인덱스(GSI)로 설계되어 필요한 액세스 패턴을 효율적으로 지원합니다. 설계에서는 전략적 비정규화 및 희소 인덱싱과 함께 집계 지향 원칙을 사용하여 성능과 비용을 최적화합니다.

주요 설계 최적화에는 다음이 포함됩니다.

  • 구문 분석 GSI - OpenOrdersByDate는 OPEN 주문(총 20%)만 인덱싱하므로 GSI 스토리지 비용을 줄이는 데 도움이 될 수 있습니다.

  • 항목 수집 패턴 - 제품 테이블은 PK=product_id, SK=warehouse_id를 사용하여 인벤토리를 저장하여 별도의 인벤토리 테이블을 제거합니다.

  • Order + OrderItems 집계 - 100% 액세스 상관 관계로 인해 단일 항목으로 포함됩니다.

  • 전략적 비정규화 - 효율적인 쿼리를 위해 주문 테이블에 account_rep_id가 중복됩니다.

마지막으로 앞서 정의한 액세스 패턴을 다시 살펴보겠습니다. 다음 표는 전략적 GSI와 함께 다중 테이블 설계를 사용하여 각 액세스 패턴을 효율적으로 지원하는 방법을 보여줍니다. 각 패턴은 직접 키 조회 또는 단일 GSI 쿼리를 사용하여 비용이 많이 드는 스캔을 방지하고 모든 규모에서 일관된 성능을 제공합니다.

일련 번호 액세스 패턴 쿼리 조건

1

직원 ID별로 직원 세부 정보 조회

직원 테이블: GetItem(employee_id="emp_001")

2

직원 이름별 직원 세부 정보 쿼리

EmployeeByName GSI: Query(name="John Smith")

3

직원의 전화번호 찾기

직원 테이블: GetItem(employee_id="emp_001")

4

고객의 전화번호 찾기

고객 테이블: GetItem(customer_id="cust_001")

5

날짜 범위 내에서 고객에 대한 주문 가져오기

OrderByCustomerDate GSI: Query(customer_id="cust_001", order_date BETWEEN "2025-01-01" AND "2025-12-31")

6

날짜 범위 내의 모든 미결 주문 표시

OpenOrdersByDate GSI: 다중 속성 PK(status="OPEN" + shard=0-4), SK=order_date BETWEEN "2025-01-01" AND "2025-12-31"와 함께 5개의 샤드 쿼리, 결과 병합

7

최근 채용된 모든 직원 표시

EmployeeByHireDate GSI: Query(entity_type="EMPLOYEE", hire_date >= "2025-01-01")

8

창고의 모든 직원 찾기

EmployeeByWarehouse GSI: Query(warehouse_id="wh_sea")

9

제품 주문 시 모든 항목 가져오기

ProductInOrders GSI: Query(product_id="prod_100")

10

모든 웨어하우스에서 제품에 대한 인벤토리 가져오기

제품 테이블: Query(product_id="prod_100")

11

계정 담당자별로 고객 가져오기

CustomerByAccountRep GSI: Query(account_rep_id="rep_001")

12

계정 담당자별로 주문 가져오기

OrderByAccountRep GSI: Query(account_rep_id="rep_001")

13

직함으로 직원 가져오기

EmployeeByJobTitle GSI: Query(job_title="Manager")

14

제품 및 창고별로 인벤토리 가져오기

제품 테이블: GetItem(product_id="prod_100", warehouse_id="wh_sea")

15

전체 제품 인벤토리 가져오기

제품 테이블: GetItem(product_id="prod_100", warehouse_id="prod_100")