

# DynamoDB 테이블을 위한 데이터 모델링
<a name="data-modeling"></a>

데이터 모델링에 대해 자세히 알아보기 전에 DynamoDB의 몇 가지 기본 사항을 이해하는 것이 중요합니다. DynamoDB는 유연한 스키마를 지원하는 키-값 NoSQL 데이터베이스입니다. 각 항목의 주요 속성을 제외한 데이터 속성 세트는 균일하거나 이산적일 수 있습니다. DynamoDB 키 스키마는 파티션 키로 항목을 고유하게 식별하는 단순 프라이머리 키 형식이거나 파티션 키와 정렬 키의 조합으로 항목을 고유하게 정의하는 복합 기본 키 형식입니다. 파티션 키는 해시되어 데이터의 물리적 위치를 확인하고 데이터를 검색합니다. 따라서 데이터를 균일하게 분배하려면 카디널리티가 높고 수평적으로 확장 가능한 속성을 파티션 키로 선택하는 것이 중요합니다. 정렬 키 속성은 키 스키마에서 선택 사항이며 정렬 키가 있으면 DynamoDB에서 일대다 관계를 모델링하고 항목 컬렉션을 생성할 수 있습니다. 정렬 키는 범위 키라고도 합니다. 정렬 키는 항목 컬렉션의 항목을 정렬하는 데 사용되며 유연한 범위 기반 작업을 가능하게 합니다.

DynamoDB 키 스키마에 대한 자세한 내용 및 모범 사례는 다음을 참조하세요.
+ [DynamoDB의 파티션 및 데이터 배포](HowItWorks.Partitions.md) 
+ [DynamoDB에서 효과적으로 파티션 키를 설계해 사용하는 모범 사례입니다.](bp-partition-key-design.md) 
+ [정렬 키를 사용하여 DynamoDB의 데이터를 정리하는 모범 사례](bp-sort-keys.md) 
+ [올바른 DynamoDB 파티션 키 선택](https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/) 

DynamoDB에서 추가 쿼리 패턴을 지원하려면 보조 인덱스가 필요한 경우가 많습니다. 보조 인덱스는 동일한 데이터가 기본 테이블과 다른 키 스키마를 통해 구성된 섀도우 테이블입니다. 로컬 보조 인덱스(LSI)는 기본 테이블과 동일한 파티션 키를 공유하며, 대체 정렬 키를 사용하여 기본 테이블의 용량을 공유할 수 있도록 합니다. 글로벌 보조 인덱스(GSI)는 기본 테이블과 다른 파티션 키와 정렬 키 속성을 가질 수 있습니다. 이는 GSI의 처리량 관리가 기본 테이블과 독립적임을 의미합니다.

보조 인덱스에 대한 자세한 내용 및 모범 사례는 다음을 참조하세요.
+ [DynamoDB에서 보조 인덱스를 사용하여 데이터 액세스 개선](SecondaryIndexes.md) 
+ [DynamoDB의 보조 인덱스 사용에 대한 모범 사례](bp-indexes.md) 

이제 데이터 모델링에 대해 좀더 자세히 살펴보겠습니다. DynamoDB 또는 NoSQL 데이터베이스에서 유연하고 고도로 최적화된 스키마를 설계하는 프로세스는 배우기 어려운 기술일 수 있습니다. 이 모듈의 목표는 사용 사례에서 시작해 프로덕션 단계까지 이어지는 스키마 설계를 위한 멘탈 흐름 차트를 개발하는 데 도움을 주는 것입니다. 먼저 모든 설계의 기본 선택, 즉 단일 테이블 설계와 다중 테이블 설계를 소개하는 것으로 시작하겠습니다. 그런 다음 애플리케이션의 다양한 조직적 결과 또는 성능 결과를 달성하는 데 사용할 수 있는 여러 설계 패턴(빌딩 블록)을 검토하겠습니다. 마지막으로 다양한 사용 사례와 산업을 위한 완전한 스키마 설계 패키지가 다양하게 포함되어 있습니다.

![\[데이터, 데이터 아래 있는 블록, 블록 아래 있는 기초 간의 개념적 관계를 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesign.png)


**Topics**
+ [항목 컬렉션 - DynamoDB에서 일대다 관계를 모델링하는 방법](WorkingWithItemCollections.md)
+ [DynamoDB의 데이터 모델링 기초](data-modeling-foundations.md)
+ [DynamoDB의 데이터 모델링 빌딩 블록](data-modeling-blocks.md)
+ [DynamoDB의 데이터 모델링 스키마 설계 패키지](data-modeling-schemas.md)
+ [DynamoDB의 관계형 데이터 모델링 모범 사례](bp-relational-modeling.md)

# 항목 컬렉션 - DynamoDB에서 일대다 관계를 모델링하는 방법
<a name="WorkingWithItemCollections"></a>

DynamoDB에서 *항목 컬렉션*은 동일한 파티션 키 값을 공유하는 항목 그룹으로, 항목이 관련되어 있음을 의미합니다. 항목 컬렉션은 DynamoDB에서 일대다 관계를 모델링하는 기본 메커니즘입니다. 항목 컬렉션은 [복합 기본 키](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey)를 사용하도록 구성된 테이블 또는 인덱스에만 존재할 수 있습니다.

**참고**  
항목 컬렉션은 기본 테이블 또는 보조 인덱스에 존재할 수 있습니다. 항목 컬렉션이 인덱스와 상호 작용하는 방식에 대한 자세한 내용은 [로컬 보조 인덱스의 항목 컬렉션](LSI.md#LSI.ItemCollections) 단원을 참조하세요.

세 명의 서로 다른 사용자와 게임 내 인벤토리를 보여 주는 다음 표를 살펴보십시오.

![\[서로 다른 속성을 가진 세 가지 항목 컬렉션.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/item_collection.png)


각 컬렉션의 일부 항목에 대해 정렬 키는 `inventory::armor`, `inventory::weapon` 또는 `info`와 같은 데이터를 그룹화하는 데 사용되는 정보로 구성된 연결입니다. 각 항목 컬렉션은 이러한 속성의 서로 다른 조합을 정렬 키로 가질 수 있습니다. 사용자 `account1234`에게는 `inventory::weapons` 항목이 있지만 사용자 `account1387`에게는 없습니다(아직 찾지 못했기 때문). 사용자 `account1138`은 정렬 키로 두 개의 항목만 사용하고(아직 인벤토리가 없기 때문) 다른 사용자는 세 개를 사용합니다.

DynamoDB를 사용하면 이러한 항목 컬렉션에서 항목을 선택적으로 검색하여 다음을 수행할 수 있습니다.
+ 특정 사용자의 모든 항목 검색
+ 특정 사용자로부터 하나의 항목만 검색
+ 특정 사용자에 속하는 특정 유형의 모든 항목 검색

## 항목 컬렉션으로 데이터를 구성하여 쿼리 속도 향상
<a name="WorkingWithItemCollections.Example"></a>

이 예제에서 이 세 항목 컬렉션의 각 항목은 게임 및 플레이어의 액세스 패턴을 기반으로 선택한 플레이어와 데이터 모델을 나타냅니다. 게임에 필요한 데이터는 무엇입니까? 언제 필요합니까? 얼마나 자주 필요합니까? 이렇게 하는 데 비용이 얼마나 듭니까? 이러한 데이터 모델링 결정은 이러한 질문에 대한 답을 바탕으로 이루어졌습니다.

이 게임에서는 무기 인벤토리와 갑옷 인벤토리에 대해 서로 다른 페이지가 플레이어에게 표시됩니다. 플레이어가 인벤토리를 열면 무기가 먼저 표시됩니다. 이 페이지가 매우 빠르게 로드된 다음에 후속 인벤토리 페이지가 로드되도록 하려는 것입니다. 플레이어가 게임 내 항목을 점점 더 많이 획득함에 따라 이러한 각 항목 유형은 상당히 커질 수 있으므로 각 인벤토리 페이지를 데이터베이스의 플레이어 항목 컬렉션에 있는 자체 항목으로 만들었습니다.

다음 단원에서는 `Query` 작업을 통해 항목 컬렉션과 상호 작용하는 방법을 자세히 설명합니다.

**Topics**
+ [항목 컬렉션으로 데이터를 구성하여 쿼리 속도 향상](#WorkingWithItemCollections.Example)

# DynamoDB의 데이터 모델링 기초
<a name="data-modeling-foundations"></a>

이 섹션에서는 단일 테이블과 다중 테이블이라는 두 가지 테이블 설계 유형을 살펴보며 기초 계층을 다룹니다.

![\[데이터, 데이터 아래 있는 블록, 블록 아래 있는 기초 간의 개념적 관계를 보여주는 이미지. 기초를 강조.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignFoundation.png)


## 단일 테이블 설계 기초
<a name="data-modeling-foundations-single"></a>

DynamoDB 스키마의 기초를 위한 한 가지 선택은 **단일 테이블 설계**입니다. 단일 테이블 설계는 단일 DynamoDB 테이블에 여러 유형(엔터티)의 데이터를 저장할 수 있는 패턴입니다. 그 목표는 여러 테이블을 유지 관리할 필요와 테이블 간의 복잡한 관계를 제거함으로써 데이터 액세스 패턴을 최적화하고 성능을 개선하며 비용을 절감하는 것입니다. 이것이 가능한 이유는 DynamoDB가 동일한 파티션 키를 가진 항목들(항목 컬렉션이라고 함)을 서로 동일한 파티션에 저장하기 때문입니다. 이 설계에서는 서로 다른 유형의 데이터가 동일한 테이블에 항목으로 저장되고, 각 항목은 고유한 정렬 키로 식별됩니다.

![\[정렬 키를 사용하여 동일한 UserID 항목 컬렉션 내에서 엔터티 유형별로 각 항목을 구분하는 방법과 테이블을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SingleTableSchema.png)


**장점**
+ 단일 데이터베이스 직접 호출로 여러 엔터티 유형에 대한 쿼리를 지원하는 데이터 로컬리티
+ 전체 읽기 비용 및 지연 시간 비용 절감:
  + 합계 4KB 미만인 두 항목에 대한 단일 쿼리는 0.5RCU 최종적으로 일관된 읽기입니다.
  + 합계 4KB 미만인 두 항목에 대한 두 번의 쿼리는 1RCU 최종적으로 일관된 읽기입니다(각각 0.5 RCU).
  + 두 개의 개별 데이터베이스 직접 호출을 반환하는 데 걸리는 시간은 평균적으로 단일 직접 호출보다 깁니다.
+ 관리할 테이블 수 감소:
  + 여러 IAM 역할 또는 정책에서 권한을 유지 관리할 필요가 없습니다.
  + 테이블 용량 관리는 모든 엔터티에서 평균화되므로 일반적으로 소비 패턴의 예측 가능성이 높아집니다.
  + 모니터링에 필요한 경보 감소
  + 고객 관리형 암호화 키는 한 테이블에서만 교체하면 됩니다.
+ 테이블로 가는 트래픽 평탄화:
  + 여러 사용량 패턴을 동일한 테이블에 집계하면 전체 사용량이 더 평탄해지고(주가 지수의 성과가 개별 주식보다 평탄해지는 것처럼), 프로비저닝된 모드 테이블을 사용하면 사용률을 높이는 데 더 효과적입니다.

**단점**
+ 관계형 데이터베이스와 비교할 때 역설적인 설계로 인해 학습 곡선이 가팔라질 수 있습니다.
+ 모든 엔터티 유형에서 데이터 요구 사항이 일관되어야 합니다.
  + 백업은 전부 아니면 전무 방식이므로 업무상 중요하지 않은 데이터가 있다면 별도의 테이블에 보관하는 것이 좋습니다.
  + 테이블 암호화가 모든 항목에서 공유됩니다. 개별 테넌트 암호화 요구 사항이 있는 멀티테넌트 애플리케이션의 경우, 클라이언트측 암호화가 필요합니다.
  + 기록 데이터와 운영 데이터가 혼합된 테이블에서는 Infrequent Access 스토리지 클래스를 활성화해도 이점이 그리 크지 않습니다. 자세한 내용은 [DynamoDB 테이블 클래스](HowItWorks.TableClasses.md) 섹션을 참조하세요.
+ 일부 엔터티만 처리해야 하는 경우에도 변경된 모든 데이터가 DynamoDB Streams로 전파됩니다.
  + Lambda 이벤트 필터 덕분에 Lambda를 사용할 때는 이것이 청구서에 영향을 미치지 않지만 Kinesis Consumer Library를 사용할 때는 추가 비용이 발생합니다.
+ GraphQL을 사용할 경우, 단일 테이블 설계를 구현하기가 더 어렵습니다.
+ Java의 [`DynamoDBMapper`](DynamoDBMapper.md) 또는 [Enhanced Client](DynamoDBEnhanced.md) 같은 상위 수준 SDK 클라이언트를 사용하는 경우, 동일한 응답의 항목들이 서로 다른 클래스에 연결될 수 있으므로 결과를 처리하기가 더 어려울 수 있습니다.

**(을)를 사용해야 하는 경우**

단일 테이블 설계는 여러 엔터티 유형을 함께 자주 쿼리하거나 서로 다른 데이터 유형 간의 관계를 유지해야 하는 애플리케이션에 적합합니다. 액세스 패턴이 데이터 지역성의 이점을 얻고 여러 테이블 관리의 오버헤드를 최소화하려는 경우에 특히 효과적입니다.

## 다중 테이블 설계 기초
<a name="data-modeling-foundations-multi"></a>

DynamoDB 스키마의 기초를 위한 두 번째 선택은 다중 테이블 설계입니다****. 다중 테이블 설계는 각 DynamoDB 테이블에 단일 유형(엔터티)의 데이터를 저장하는 기존 데이터베이스 설계와 비슷한 패턴입니다. 각 테이블 내의 데이터는 여전히 파티션 키에 의해 구성되므로 단일 엔터티 유형 내의 성능은 확장성과 성능에 최적화되지만 여러 테이블에 걸친 쿼리는 독립적으로 수행해야 합니다.

![\[포럼 목록과 일부 집계 데이터가 포함된 포럼 테이블을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/MultipleTable1.png)


![\[스레드가 속한 특정 포럼별로 파티셔닝된 스레드 목록이 포함된 스레드 테이블을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/MultipleTable2.png)


**장점**
+ 단일 테이블 설계에 익숙하지 않은 사용자들이 설계하기에 더 간단합니다.
+ 각 리졸버가 단일 엔터티(테이블)에 매핑되므로 GraphQL 리졸버를 더 쉽게 구현할 수 있습니다.
+ 다양한 엔터티 유형에서 고유한 데이터 요구 사항 가능:
  + 업무상 중요한 개별 테이블을 백업할 수 있습니다.
  + 각 테이블의 테이블 암호화를 관리할 수 있습니다. 개별 테넌트 암호화 요구 사항이 있는 멀티테넌트 애플리케이션의 경우, 별도의 테넌트 테이블을 통해 각 고객이 자체 암호화 키를 가질 수 있습니다.
  + 기록 데이터가 있는 테이블에서만 Infrequent Access 스토리지 클래스를 활성화하여 비용 절감 효과를 극대화할 수 있습니다. 자세한 내용은 [DynamoDB 테이블 클래스](HowItWorks.TableClasses.md) 섹션을 참조하세요.
+ 각 테이블에는 고유한 변경 데이터 스트림이 있으므로 단일 모놀리식 프로세서 대신 각 항목 유형에 맞는 전용 Lambda 함수를 설계할 수 있습니다.

**단점**
+ 여러 테이블의 데이터가 필요한 액세스 패턴의 경우, DynamoDB에서 여러 번 읽어야 하며, 클라이언트 코드에서 데이터를 처리/결합해야 할 수 있습니다.
+ 여러 테이블을 운영 및 모니터링하려면 더 많은 CloudWatch 경보가 필요하며, 각 테이블 규모를 독립적으로 조정해야 합니다.
+ 각 테이블 권한을 개별적으로 관리해야 합니다. 향후 테이블을 추가하려면 필요한 IAM 역할 또는 정책을 변경해야 합니다.

**(을)를 사용해야 하는 경우**

애플리케이션의 액세스 패턴에서 여러 엔터티 또는 테이블을 함께 쿼리할 필요가 없다면 다중 테이블 설계가 좋고 충분한 접근 방식입니다.

# DynamoDB의 데이터 모델링 빌딩 블록
<a name="data-modeling-blocks"></a>

이 섹션에서는 애플리케이션에 사용할 수 있는 설계 패턴을 제공하는 빌딩 블록 계층을 다룹니다.

![\[데이터, 데이터 아래 있는 블록, 블록 아래 있는 기초 간의 개념적 관계를 보여주는 이미지. 기초를 강조.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignBlocks.png)


**Topics**
+ [복합 정렬 키 빌딩 블록](#data-modeling-blocks-composite)
+ [멀티테넌시 빌딩 블록](#data-modeling-blocks-multi-tenancy)
+ [희소 인덱스 빌딩 블록](#data-modeling-blocks-sparse-index)
+ [TTL(Time To Live) 빌딩 블록](#data-modeling-blocks-ttl)
+ [아카이브용 TTL(Time To Live) 빌딩 블록](#data-modeling-blocks-ttl-archival)
+ [수직 파티셔닝 빌딩 블록](#data-modeling-blocks-vertical-partitioning)
+ [쓰기 샤딩 빌딩 블록](#data-modeling-blocks-write-sharding)

## 복합 정렬 키 빌딩 블록
<a name="data-modeling-blocks-composite"></a>

NoSQL을 비관계형이라고 생각할 수도 있습니다. 궁극적으로 관계를 DynamoDB 스키마에 배치하지 못할 이유는 없습니다. 관계는 관계형 데이터베이스 및 그 외래 키와 다르게 보일 뿐입니다. DynamoDB에서 데이터의 논리적 계층 구조를 개발하는 데 사용할 수 있는 가장 중요한 패턴 중 하나는 복합 정렬 키입니다. 가장 일반적인 설계 스타일은 계층 구조의 각 계층(부모 계층 > 자식 계층 > 손자 계층)을 해시태그로 구분하는 것입니다. 예를 들어 `PARENT#CHILD#GRANDCHILD#ETC`입니다.

![\[userID가 프라이머리 키이고 다른 속성들의 조합이 정렬 키인 테이블의 항목을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ShoppingCart.png)


DynamoDB에서 데이터를 쿼리하려면 파티션 키에 항상 정확한 값이 필요하지만, 이진 트리를 통과하는 것과 비슷하게 왼쪽부터 오른쪽으로 정렬 키에 부분적 조건을 적용할 수 있습니다.

위의 예에는 사용자 세션에서 유지해야 하는 장바구니가 있는 전자 상거래 상점이 있습니다. 사용자가 로그인할 때마다 나중을 위해 저장된 항목을 포함하여 전체 장바구니를 보고 싶어 할 수 있습니다. 하지만 사용자가 결제에 들어갈 때는 구매를 위해 활성 장바구니에 있는 항목만 로드되어야 합니다. 이 두 `KeyConditions`는 모두 명시적으로 CART 정렬 키를 요청하므로 DynamoDB는 읽기 시 추가 위시리스트 데이터를 무시합니다. 저장된 항목과 활성 항목은 모두 동일한 장바구니의 일부이지만 애플리케이션의 서로 다른 부분에서 서로 다르게 처리해야 하므로 정렬 키의 접두사에 `KeyCondition`을 적용하는 것이 애플리케이션의 각 부분에 필요한 데이터만 검색하는 가장 최적화된 방법입니다.

**이 빌딩 블록의 주요 특징**
+ 효과적인 데이터 액세스를 위해 관련 항목들이 서로에 대해 로컬로 저장됩니다 
+ `KeyCondition` 식을 사용하면 계층 구조의 하위 집합을 선택적으로 검색할 수 있으므로 낭비되는 RCU가 없습니다 
+ 애플리케이션의 서로 다른 부분이 특정 접두사 아래 항목을 저장하여 항목 덮어쓰기나 쓰기 충돌을 방지합니다

## 멀티테넌시 빌딩 블록
<a name="data-modeling-blocks-multi-tenancy"></a>

많은 고객이 DynamoDB를 사용하여 멀티테넌트 애플리케이션의 데이터를 호스팅합니다. 이런 시나리오에서 단일 테넌트의 모든 데이터를 테이블의 자체 논리적 파티션에 보관하는 방식으로 스키마를 설계하려 합니다. 이 설계는 동일한 파티션 키를 가진 DynamoDB 테이블의 모든 항목을 가리키는 용어인 항목 컬렉션이라는 개념을 활용합니다. DynamoDB의 멀티테넌시 접근 방식에 대한 자세한 내용은 [Multitenancy on DynamoDB](https://docs.aws.amazon.com/whitepapers/latest/multi-tenant-saas-storage-strategies/multitenancy-on-dynamodb.html)를 참조하세요.

![\[멀티테넌트 사진 사이트를 나타낼 수 있는 테이블을 보여 주는 이미지. 프라이머리 키는 파티션 키인 사용자와 정렬 키인 다양한 사진으로 구성됩니다. 각 항목의 속성에 사진이 호스팅되는 URL이 표시됩니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/MultiTenant.png)


이 예제에서는 사용자가 수천 명인 사진 호스팅 사이트를 운영 중입니다. 각 사용자는 처음에는 자신의 프로필에만 사진을 업로드하지만 기본적으로 사용자가 다른 사용자의 사진을 보는 것을 허용하지 않을 것입니다. 각 사용자의 API 직접 호출 승인에 추가 격리 수준을 추가하여 해당 사용자가 자신의 파티션에서만 데이터를 요청하도록 하는 것이 이상적이지만 스키마 수준에서는 고유한 파티션 키로도 충분합니다.

**이 빌딩 블록의 주요 특징**
+ 한 사용자 또는 테넌트가 읽는 데이터 양은 해당 파티션에 있는 항목의 총량을 초과할 수 없습니다
+ 계정 폐쇄나 규정 준수 요청으로 인한 테넌트의 데이터 삭제를 요령 있고 저렴하게 수행할 수 있습니다. 파티션 키가 테넌트 ID와 같은 쿼리를 실행한 다음 반환된 각 프라이머리 키에 대해 `DeleteItem` 작업을 실행하기만 하면 됩니다.

**참고**  
멀티테넌시를 염두에 두고 설계되었으므로 단일 테이블에서 다양한 암호화 키 제공자를 사용하여 데이터를 안전하게 격리할 수 있습니다. [AWS Amazon DynamoDB용 데이터베이스 암호화 SDK](https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/what-is-database-encryption-sdk.html)를 사용하면 DynamoDB 워크로드에 클라이언트측 암호화를 포함할 수 있습니다. 속성 수준 암호화를 수행하여 DynamoDB 테이블에 저장하기 전에 특정 속성 값을 암호화하고, 전체 데이터베이스를 미리 해독하지 않고도 암호화된 속성을 검색할 수 있습니다.

## 희소 인덱스 빌딩 블록
<a name="data-modeling-blocks-sparse-index"></a>

액세스 패턴에서 드문 항목 또는 상태를 수신하는 항목(에스컬레이션된 응답 필요)과 일치하는 항목을 찾아야 하는 경우가 있습니다. 전체 데이터 세트에서 이러한 항목을 정기적으로 쿼리하는 대신 **글로벌 보조 인덱스(GSI)**에 데이터가 드물게 로드된다는 사실을 활용할 수 있습니다. 즉, 인덱스에 속성이 정의된 기본 테이블의 항목만 인덱스에 복제하는 것입니다.

![\[대량의 안정 상태 데이터를 수신하는 기본 테이블을 보여 주는 이미지\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SparseBaseTable.png)


![\[에스컬레이션된 항목만 수신하는 글로벌 보조 인덱스를 보여 주는 이미지\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SparseGSI.png)


이 예제에서는 현장의 각 디바이스가 정기적으로 상태를 보고하는 IoT 사용 사례를 볼 수 있습니다. 대부분의 보고서에서는 모든 것이 정상이라고 디바이스가 보고하기를 기대하지만, 때때로 고장이 발생할 수 있으며, 고장은 수리 기술자에게 에스컬레이션해야 합니다. 보고서에 에스컬레이션이 있으면 `EscalatedTo` 속성이 항목에 추가되지만 없다면 이 속성이 나타나지 않습니다. 이 예제의 GSI는 `EscalatedTo`를 기준으로 파티셔닝되며, GSI가 기본 테이블에서 키를 가져오므로 어떤 DeviceID가 언제 고장을 보고했는지 여전히 알 수 있습니다.

DynamoDB에서는 읽기가 쓰기보다 저렴하지만, 특정 항목 유형의 인스턴스가 드물면서도 이를 찾기 위한 읽기는 흔한 사용 사례에서 희소 인덱스는 매우 강력한 도구입니다.

**이 빌딩 블록의 주요 특징**
+ 희소 GSI의 쓰기 및 스토리지 비용은 키 패턴과 일치하는 항목에만 적용되므로 모든 항목이 복제되는 다른 GSI보다 GSI 비용이 훨씬 저렴할 수 있습니다.
+ 복합 정렬 키를 사용하면 원하는 쿼리와 일치하는 항목의 범위를 더욱 좁힐 수 있습니다. 예를 들어 정렬 키에 타임스탬프를 사용하여 지난 X분 동안 보고된 고장을 볼 수 있습니다(`SK > 5 minutes ago, ScanIndexForward: False`).

## TTL(Time To Live) 빌딩 블록
<a name="data-modeling-blocks-ttl"></a>

대부분의 데이터에는 해당 데이터를 기본 데이터 스토어에 보관할 가치가 있다고 간주되는 일정한 기간이 있습니다. 오래된 데이터를 쉽게 제거할 수 있도록 DynamoDB에는 **TTL(Time to Live)**이라는 기능이 있습니다. [TTL](TTL.md) 기능을 사용하면 (과거의) epoch 타임스탬프가 있는 항목에 대한 모니터링이 필요한 특정 속성을 테이블 수준에서 정의할 수 있습니다. 이렇게 하면 만료된 레코드를 무료로 테이블에서 삭제할 수 있습니다.

**참고**  
글로벌 테이블의 [글로벌 테이블 버전 2019.11.21(현재)](GlobalTables.md)을 사용하고 [Time To Live](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) 기능도 사용한다면 DynamoDB는 TTL 삭제를 모든 복제본 테이블에 복제합니다. 최초 TTL 삭제는 TTL 만료가 발생하는 리전의 쓰기 용량을 사용하지 않습니다. 그러나 복제본 테이블에 복제되는 TTL 삭제는 각 복제본 리전의 복제된 쓰기 용량을 사용하며, 해당 요금이 적용됩니다.

![\[TTL(Time To Live) 속성이 있는 사용자 메시지가 포함된 테이블을 보여 주는 이미지\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/TTL.png)


이 예제에는 사용자가 짧은 수명의 메시지를 생성할 수 있도록 설계된 애플리케이션이 있습니다. DynamoDB에서 메시지가 생성되면 애플리케이션 코드에 의해 TTL 속성이 7일 후 날짜로 설정됩니다. 약 7일 후, DynamoDB는 이러한 항목의 에포크 타임스탬프가 과거임을 확인하고 삭제합니다.

TTL을 통한 삭제는 무료이므로 이 기능을 사용하여 테이블에서 기록 데이터를 제거하는 것이 좋습니다. 이렇게 하면 매달 전체 스토리지 요금이 줄어들고, 사용자 쿼리를 통해 검색되는 데이터가 줄어들기 때문에 사용자 읽기 비용도 절감할 수 있습니다. TTL은 테이블 수준에서 활성화되어 있지만 어떤 항목이나 엔터티에 TTL 속성을 생성할지와 에포크 타임스탬프를 얼마나 먼 장래로 설정할지는 사용자에게 달려 있습니다.

**이 빌딩 블록의 주요 특징**
+ TTL 삭제는 테이블 성능에 영향을 주지 않고 백그라운드에서 실행됩니다.
+ TTL은 대략 6시간마다 실행되는 비동기 프로세스이지만 만료된 레코드를 삭제하는 데는 48시간 이상 걸릴 수 있습니다.
  + 오래된 데이터를 48시간 이내에 정리해야 하는 경우, 잠금 기록이나 상태 관리 같은 사용 사례에서 TTL 삭제를 사용하지 마세요.
+ TTL 속성에 유효한 속성 이름을 지정할 수 있지만 값은 숫자 유형이어야 합니다.

## 아카이브용 TTL(Time To Live) 빌딩 블록
<a name="data-modeling-blocks-ttl-archival"></a>

TTL은 DynamoDB에서 오래된 데이터를 삭제하는 데 효과적인 도구이지만, 기본 데이터 스토어보다 더 긴 기간 동안 데이터 아카이브를 보관해야 하는 사용 사례가 많습니다. 이 경우 TTL의 시간 설정된 레코드 삭제를 활용하면 만료된 레코드를 장기 데이터 스토어로 푸시할 수 있습니다.

![\[TTL(Time To Live) 삭제 작업을 DynamoDB Streams로 전송한 다음 장기 데이터 스토어로 전송하는 테이블을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/TTLArchive.png)


DynamoDB가 TTL 삭제를 수행하면 삭제 작업은 DynamoDB Stream에도 `Delete` 이벤트로 푸시됩니다. 하지만 DynamoDB TTL이 삭제를 수행하는 경우, `principal:dynamodb`의 스트림 레코드에 속성이 있습니다. DynamoDB Stream의 Lambda 구독자를 사용하면 DynamoDB 보안 주체 속성에만 이벤트 필터를 적용할 수 있으며, 해당 필터와 일치하는 모든 레코드가 Amazon Glacier 같은 아카이브 스토어로 푸시된다는 것을 알 수 있습니다.

**이 빌딩 블록의 주요 특징**
+  기록 항목에 대해 지연 시간이 짧은 DynamoDB 읽기가 더 이상 필요하지 않게 되면 해당 항목을 Amazon Glacier와 같은 콜드 스토리지 서비스로 마이그레이션하여 사용 사례의 데이터 규정 준수 요구 사항을 충족하는 동시에 스토리지 비용을 크게 줄일 수 있습니다.
+ 데이터가 Amazon S3에 보관되는 경우, Amazon Athena나 Redshift Spectrum 같은 비용 효율적인 분석 도구를 사용하여 데이터의 기록 분석을 수행할 수 있습니다.

## 수직 파티셔닝 빌딩 블록
<a name="data-modeling-blocks-vertical-partitioning"></a>

문서 모델 데이터베이스에 익숙한 사용자라면 모든 관련 데이터를 단일 JSON 문서에 저장한다는 개념에 익숙할 것입니다. DynamoDB는 JSON 데이터 유형을 지원하지만 중첩된 JSON에서 `KeyConditions`를 실행하는 것은 지원하지 않습니다. 디스크에서 읽는 데이터의 양과 쿼리가 실제로 사용하는 RCU 수를 결정하는 것은 `KeyConditions`이므로 규모가 커지면 비효율성이 초래될 수 있습니다. DynamoDB의 쓰기 및 읽기를 더 잘 최적화하려면 문서의 개별 엔터티를 개별 DynamoDB 항목으로 분리하는 것이 좋습니다. 이를 **수직 파티셔닝**이라고도 합니다.

![\[중첩된 JSON 객체로 포맷된 대규모 데이터 구조를 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DocumentBlob.png)


![\[항목의 정렬 키가 DynamoDB 사용량 최적화에 도움이 되는 항목 컬렉션을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SingleTableSchema.png)


위에 나온 것처럼 수직 파티셔닝은 실제 단일 테이블 설계의 주요 예이지만 원하는 경우 여러 테이블에 걸쳐 구현할 수도 있습니다. DynamoDB는 1KB 단위로 쓰기 요금을 청구하므로 항목이 1KB 미만이 되는 방식으로 문서를 파티셔닝하는 것이 좋습니다.

**이 빌딩 블록의 주요 특징**
+ 데이터 관계의 계층 구조는 정렬 키 접두사를 통해 유지되므로 필요한 경우 클라이언트측에서 단일 문서 구조를 다시 구축할 수 있습니다.
+ 데이터 구조의 단일 구성 요소를 독립적으로 업데이트할 수 있으므로 작은 항목 업데이트의 WCU는 1에 불과합니다.
+ 정렬 키 `BeginsWith`를 사용하면 애플리케이션이 단일 쿼리로 유사한 데이터를 검색할 수 있으므로 줄어든 총 비용/지연 시간에 대해 읽기 비용이 집계됩니다.
+ 큰 문서는 DynamoDB의 개별 항목 크기 제한인 400KB를 쉽게 초과할 수 있으며, 수직 파티셔닝으로 이 제한을 피할 수 있습니다.

## 쓰기 샤딩 빌딩 블록
<a name="data-modeling-blocks-write-sharding"></a>

DynamoDB에 있는 몇 안 되는 엄격한 제한 중 하나는 단일 물리적 파티션이 초당 유지할 수 있는 처리량(단일 파티션 키일 필요는 없음)에 대한 제한입니다. 이러한 제한은 현재 다음과 같습니다.
+ 1,000WCU(또는 1KB 이하 항목 초당 쓰기 1,000회) 및 3,000 RCU(또는 4KB 이하 초당 읽기 3,000회)의 *강력한 일관성* 또는 
+ 4KB 이하 초당 읽기 6,000회의 *최종 일관성*

테이블에 대한 요청이 이러한 제한 중 어느 하나라도 초과하는 경우, `ThroughputExceededException`의 클라이언트 SDK에 오류가 다시 전송되는데, 이를 보다 일반적으로는 제한(throttling)이라고 합니다. 이 제한을 초과하는 읽기 작업이 필요한 사용 사례에서는 대부분 DynamoDB 앞에 읽기 캐시를 배치하는 것이 가장 좋지만 쓰기 작업에는 **쓰기 샤딩**이라는 스키마 수준 설계가 필요합니다.

![\[DynamoDB가 여러 파티션에 파티션 키를 샤딩하여 트래픽 급증으로 인한 제한을 방지하는 방법을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/WriteShardingProblem.png)


![\[DynamoDB가 여러 파티션에 파티션 키를 샤딩하여 트래픽 급증으로 인한 제한을 방지하는 방법을 보여 주는 이미지.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/WriteShardingSolution.png)


이 문제를 해결하기 위해 애플리케이션의 `UpdateItem` 코드에서 각 참가자의 파티션 키 끝에 무작위 정수를 추가하겠습니다. 난수 생성기의 범위는 주어진 참가자의 초당 예상 쓰기 횟수를 1,000으로 나눈 값과 일치하거나 초과하는 상한값을 가져야 합니다. 초당 20,000회의 투표를 지지하기 위해 이 값은 rand(0,19)처럼 보일 것입니다. 이제 데이터가 별도의 논리적 파티션에 저장되었으므로 읽을 때 데이터를 다시 결합해야 합니다. 투표 합계가 실시간일 필요는 없으므로 X분마다 모든 투표 파티션을 읽도록 예약된 Lambda 함수가 각 참가자에 대해 집계를 때때로 수행하여 실시간으로 읽을 수 있도록 단일 투표 합계 레코드에 다시 쓸 수 있습니다.

**이 빌딩 블록의 주요 특징**
+ 특정 파티션 키의 매우 높은 쓰기 처리량이 불가피한 사용 사례의 경우, 여러 DynamoDB 파티션에 쓰기 작업을 인위적으로 분산할 수 있습니다.
+ 카디널리티가 낮은 파티션 키가 있는 GSI도 이 패턴을 활용해야 합니다. GSI에서 제한이 발생하면 기본 테이블의 쓰기 작업에 역압이 가해지기 때문입니다.

# DynamoDB의 데이터 모델링 스키마 설계 패키지
<a name="data-modeling-schemas"></a>

DynamoDB용 데이터 모델링 스키마 설계 패키지에 대해 알아봅니다. 여기에는 소셜 네트워크, 게임 프로필, 불만 사항 관리, 반복 결제, 디바이스 상태 및 온라인 상점에 대한 사용 사례, 액세스 패턴 및 최종 스키마 설계가 포함됩니다.

![\[데이터, 데이터 아래 있는 블록, 블록 아래 있는 기초 간의 개념적 관계를 보여주는 이미지. 기초를 강조.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignData.png)


## 사전 조건
<a name="data-modeling-prereqs"></a>

DynamoDB용 스키마를 설계하기 전에 먼저 스키마가 지원해야 하는 사용 사례에 대한 몇 가지 필수 조건 데이터를 수집해야 합니다. 관계형 데이터베이스와 달리 DynamoDB는 기본적으로 샤딩됩니다. 즉, 데이터가 백그라운드에서 여러 서버에 저장되므로 데이터 로컬리티에 맞게 설계하는 것이 중요합니다. 각 스키마 설계마다 다음 목록을 작성해야 합니다.
+ 엔터티 목록(ER 다이어그램)
+ 각 엔터티의 예상 볼륨 및 처리량
+ 지원해야 할 액세스 패턴(쿼리 및 쓰기)
+ 데이터 보존 요구 사항

**Topics**
+ [사전 조건](#data-modeling-prereqs)
+ [DynamoDB의 소셜 네트워크 스키마 설계](data-modeling-schema-social-network.md)
+ [DynamoDB의 게임 프로필 스키마 설계](data-modeling-schema-gaming-profile.md)
+ [DynamoDB에서 불만 관리 시스템 스키마 설계](data-modeling-complaint-management.md)
+ [DynamoDB에서 반복 결제 스키마 설계](data-modeling-schema-recurring-payments.md)
+ [DynamoDB에서 디바이스 상태 업데이트 모니터링](data-modeling-device-status.md)
+ [DynamoDB를 온라인 상점용 데이터 스토어로 사용](data-modeling-online-shop.md)

# DynamoDB의 소셜 네트워크 스키마 설계
<a name="data-modeling-schema-social-network"></a>

## 소셜 네트워크 비즈니스 사용 사례
<a name="data-modeling-schema-social-network-use-case"></a>

이 사용 사례에서는 DynamoDB를 소셜 네트워크로 사용하는 방법을 설명합니다. 소셜 네트워크는 여러 사용자가 서로 상호 작용할 수 있는 온라인 서비스입니다. 설계할 소셜 네트워크에서 사용자는 자신의 게시물, 팔로워, 팔로우하는 사람, 팔로우하는 사람이 작성한 게시물로 구성된 타임라인을 볼 수 있습니다. 이 스키마 설계의 액세스 패턴은 다음과 같습니다.
+ 주어진 userID의 사용자 정보 가져오기 
+ 주어진 userID의 팔로워 목록 가져오기
+ 주어진 userID가 팔로우하는 사용자 목록 가져오기
+ 주어진 userID의 게시물 목록 가져오기
+ 주어진 postID의 게시물을 좋아하는 사용자 목록 가져오기
+ 주어진 postID의 좋아요 개수 가져오기
+ 주어진 userID의 타임라인 가져오기

## 소셜 네트워크 엔터티 관계 다이어그램
<a name="data-modeling-schema-social-network-erd"></a>

다음은 소셜 네트워크 스키마 설계에 사용할 엔터티 관계 다이어그램(ERD)입니다.

![\[사용자, 게시물, 팔로워와 같은 엔터티를 표시하는 소셜 네트워크 애플리케이션용 ERD.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetworkERD.png)


## 소셜 네트워크 액세스 패턴
<a name="data-modeling-schema-social-network-access-patterns"></a>

다음은 소셜 네트워크 스키마 설계 시 고려할 액세스 패턴입니다.
+ `getUserInfoByUserID`
+ `getFollowerListByUserID`
+ `getFollowingListByUserID`
+ `getPostListByUserID`
+ `getUserLikesByPostID`
+ `getLikeCountByPostID`
+ `getTimelineByUserID`

## 소셜 네트워크 스키마 설계 진화
<a name="data-modeling-schema-social-network-design-evolution"></a>

DynamoDB는 NoSQL 데이터베이스이므로 여러 데이터베이스의 데이터를 결합하는 조인 작업은 수행할 수 없습니다. DynamoDB에 익숙하지 않은 고객은 그럴 필요가 없을 때 관계형 데이터베이스 관리 시스템(RDBMS) 설계 철학(예: 각 엔터티별 테이블 생성)을 DynamoDB에 적용할 수 있습니다. DynamoDB의 단일 테이블 설계의 목적은 애플리케이션의 액세스 패턴에 따라 미리 조인된 형태로 데이터를 쓴 다음 추가 계산 없이 데이터를 즉시 사용하는 것입니다. 자세한 내용은 [Single-table vs. multi-table design in DynamoDB](https://aws.amazon.com/blogs/database/single-table-vs-multi-table-design-in-amazon-dynamodb/)를 참조하세요.

이제 모든 액세스 패턴을 처리하도록 스키마 설계를 발전시키는 방법을 단계별로 살펴보겠습니다.

**1단계: 액세스 패턴 1(`getUserInfoByUserID`) 처리**

주어진 사용자의 정보를 가져오려면 키 조건 `PK=<userID>`를 사용하여 기본 테이블을 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html)해야 합니다. 쿼리 작업을 사용하면 결과에 페이지를 매길 수 있으며, 이는 사용자의 팔로어가 많을 때 유용할 수 있습니다. 쿼리에 대한 자세한 내용은 [DynamoDB에서 테이블 쿼리](Query.md) 섹션을 참조하세요.

이 예제에서는 'count'와 'info'라는 두 가지 유형의 사용자 데이터를 추적합니다. 사용자의 'count'에는 사용자의 팔로워 수, 사용자가 팔로우 중인 사용자 수, 사용자가 작성한 게시물 수가 반영됩니다. 사용자의 'info'에는 이름과 같은 개인 정보가 반영됩니다.

이 두 종류의 데이터는 아래의 두 항목으로 표현됩니다. 정렬 키(SK)에 'count'가 있는 항목은 'info'가 있는 항목보다 변경될 가능성이 높습니다. DynamoDB는 업데이트 전후에 표시되는 항목 크기를 고려하며, 사용된 프로비저닝된 처리량은 이러한 항목 크기 중 더 큰 크기를 반영합니다. 따라서 항목 속성의 하위 집합을 업데이트하더라도 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html)은 프로비저닝된 처리량(이전 항목 크기와 이후 항목 크기 중 더 큰 것)을 모두 소비합니다. 한 번의 `Query` 작업으로 항목을 가져오고 `UpdateItem`을 사용하여 기존 숫자 속성에 더하거나 뺄 수 있습니다.

![\[ID가 u#12345인 사용자와 해당 개수 및 정보 데이터에 대한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork1.png)


**2단계: 액세스 패턴 2(`getFollowerListByUserID`) 처리**

주어진 사용자를 팔로우하는 사용자 목록을 가져오려면 키 조건 `PK=<userID>#follower`를 사용하여 기본 테이블을 `Query`해야 합니다.

![\[ID가 u#12345인 사용자의 팔로워를 나열하기 위한 테이블에 대한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork2.png)


**3단계: 액세스 패턴 3(`getFollowingListByUserID`) 처리**

주어진 사용자가 팔로우하는 사용자 목록을 가져오려면 키 조건 `PK=<userID>#following`를 사용하여 기본 테이블을 `Query`해야 합니다. 그런 다음 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 작업을 사용하여 여러 요청을 그룹화하고 다음을 수행할 수 있습니다.
+ 사용자 A를 사용자 B의 팔로워 목록에 추가한 다음 사용자 B의 팔로워 수를 1 늘립니다.
+ 사용자 B를 사용자 A의 팔로워 목록에 추가한 다음 사용자 A의 팔로워 수를 1 늘립니다.

![\[ID가 u#12345인 사용자가 팔로우하는 모든 사용자를 나열하기 위한 테이블에 대한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork3.png)


**4단계: 액세스 패턴 4(`getPostListByUserID`) 처리**

주어진 사용자가 작성한 게시물 목록을 가져오려면 키 조건 `PK=<userID>#post`를 사용하여 기본 테이블을 `Query`해야 합니다. 여기서 한 가지 중요한 점은 사용자의 postID가 증분적이어야 한다는 것입니다. 즉, 두 번째 postID 값은 첫 번째 postID 값보다 커야 합니다(사용자는 자신의 게시물을 정렬된 방식으로 보기 원하므로). 이렇게 하려면 ULID(Universally Unique Lexicographically Sortable Identifier) 같은 시간 값을 기반으로 postID를 생성하면 됩니다.

![\[특정 사용자가 생성한 게시물 목록을 가져오기 위해 키 조건을 사용한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork4.png)


**5단계: 액세스 패턴 5(`getUserLikesByPostID`) 처리**

주어진 사용자의 게시물에 좋아요를 누른 사용자 목록을 가져오려면 키 조건 `PK=<postID>#likelist`를 사용하여 기본 테이블을 `Query`해야 합니다. 이 접근 방식은 액세스 패턴 2(`getFollowerListByUserID`)와 액세스 패턴 3(`getFollowingListByUserID`)에서 팔로워 목록 및 팔로우하는 사용자 목록을 검색하는 데 사용한 것과 동일한 패턴입니다.

![\[특정 사용자의 게시물에 좋아요를 누른 사용자 목록을 가져오기 위해 키 조건을 사용한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork5.png)


**6단계: 액세스 패턴 6(`getLikeCountByPostID`) 처리**

주어진 게시물에 대한 좋아요 수를 가져오려면 키 조건 `PK=<postID>#likecount`를 사용하여 기본 테이블에서 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 작업을 수행해야 합니다. 파티션의 처리량이 초당 1,000WCU를 초과하면 제한이 발생하기 때문에 이 액세스 패턴은 팔로워가 많은 사용자(예: 유명인)가 게시물을 작성할 때마다 제한 문제를 일으킬 수 있습니다. 이 문제는 DynamoDB로 인한 것은 아니며, 소프트웨어 스택의 끝에 있기 때문에 DynamoDB에 나타납니다.

모든 사용자가 좋아요 수를 동시에 보는 것이 정말 필요한지 아니면 시간이 지남에 따라 점진적으로 표시되어도 무방한지 평가해야 합니다. 일반적으로 게시물의 좋아요 수가 즉시 100% 정확할 필요는 없습니다. 애플리케이션과 DynamoDB 사이에 대기열을 두어 정기적으로 업데이트가 이루어지도록 하면 이 전략을 구현할 수 있습니다.

![\[특정 게시물의 좋아요 수를 가져오기 위해 키 조건을 사용한 GetItem 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork6.png)


**7단계: 액세스 패턴 7(`getTimelineByUserID`) 처리**

주어진 사용자의 타임라인을 가져오려면 키 조건 `PK=<userID>#timeline`를 사용하여 기본 테이블에서 `Query` 작업을 수행해야 합니다. 사용자의 팔로워들이 게시물을 동기식으로 봐야 하는 시나리오를 생각해 보겠습니다. 사용자가 게시물을 작성할 때마다 팔로워 목록을 읽고 팔로워의 userID와 postID가 모든 팔로워의 타임라인 키에 천천히 입력됩니다. 그런 다음 애플리케이션이 시작되면 `Query` 작업으로 타임라인 키를 읽고 새 항목에 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) 작업을 사용하여 타임라인 화면을 userID와 postID의 조합으로 채울 수 있습니다. API 직접 호출로 타임라인을 읽을 수는 없지만 게시물을 자주 수정할 수 있다면 이것이 더 비용 효율적인 솔루션입니다.

타임라인은 최근 게시물을 보여 주는 곳이므로 이전 게시물을 정리할 방법이 필요합니다. WCU를 사용하여 삭제하는 대신 DynamoDB의 [TTL](TTL.md) 기능을 사용하여 무료로 삭제할 수 있습니다.

![\[특정 사용자의 최근 게시물을 표시하는 타임라인을 가져오기 위해 키 조건을 사용한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork7.png)


모든 액세스 패턴과 스키마 설계에서 이를 처리하는 방법이 아래 표에 요약되어 있습니다.


| 액세스 패턴 | 기본 테이블/GSI/LSI | 연산 | 파티션 키 값 | 정렬 키 값 | 기타 조건/필터 | 
| --- | --- | --- | --- | --- | --- | 
| getUserInfoByUserID | 기본 테이블 | 쿼리 | PK=<userID> |  |  | 
| getFollowerListByUserID | 기본 테이블 | 쿼리 | PK=<userID>\$1follower |  |  | 
| getFollowingListByUserID | 기본 테이블 | 쿼리 | PK=<userID>\$1following |  |  | 
| getPostListByUserID | 기본 테이블 | 쿼리 | PK=<userID>\$1post |  |  | 
| getUserLikesByPostID | 기본 테이블 | 쿼리 | PK=<postID>\$1likelist |  |  | 
| getLikeCountByPostID | 기본 테이블 | GetItem | PK=<postID>\$1likecount |  |  | 
| getTimelineByUserID | 기본 테이블 | 쿼리 | PK=<userID>\$1timeline |  |  | 

## 소셜 네트워크 최종 스키마
<a name="data-modeling-schema-social-network-final-schema"></a>

다음은 최종 스키마 설계입니다. 이 스키마 설계를 JSON 파일로 다운로드하려면 GitHub의 [DynamoDB 예제](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/SocialNetwork/SocialNetworkSchema.json)를 참조하세요.

**기본 테이블:**

![\[이전 Query 및 GetItem 작업의 결과를 포함하는 테이블의 최종 스키마 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork8.png)


## 이 스키마 설계와 함께 NoSQL Workbench 사용
<a name="data-modeling-schema-social-network-nosql"></a>

이 최종 스키마를 DynamoDB 데이터 모델링, 데이터 시각화, 쿼리 개발 기능을 제공하는 시각적 도구인 [NoSQL Workbench](workbench.md)로 가져와서 새 프로젝트를 추가로 탐색하고 편집할 수 있습니다. 시작하려면 다음 단계를 따릅니다.

1. NoSQL Workbench 다운로드 자세한 내용은 [DynamoDB용 NoSQL Workbench 다운로드](workbench.settingup.md) 섹션을 참조하세요.

1. 위에 나열된 JSON 스키마 파일을 다운로드합니다. 이 파일은 이미 NoSQL Workbench 모델 형식으로 되어 있습니다.

1. JSON 스키마 파일을 NoSQL Workbench로 가져옵니다. 자세한 내용은 [기존 데이터 모델 가져오기](workbench.Modeler.ImportExisting.md) 섹션을 참조하세요.

1. NOSQL Workbench로 가져온 후 데이터 모델을 편집할 수 있습니다. 자세한 내용은 [기존 데이터 모델 편집](workbench.Modeler.Edit.md) 섹션을 참조하세요.

# DynamoDB의 게임 프로필 스키마 설계
<a name="data-modeling-schema-gaming-profile"></a>

## 게임 프로필 비즈니스 사용 사례
<a name="data-modeling-schema-gaming-profile-use-case"></a>

이 사용 사례에서는 DynamoDB를 사용하여 게임 시스템을 위한 플레이어 프로필을 저장하는 방법을 설명합니다. 사용자(이 경우 플레이어)는 많은 최신 게임, 특히 온라인 게임을 이용하기 전에 프로필을 생성해야 합니다. 게임 프로필에는 일반적으로 다음이 포함됩니다.
+ 사용자 이름과 같은 기본 정보
+ 아이템 및 장비와 같은 게임 데이터
+ 태스크 및 활동과 같은 게임 레코드
+ 친구 목록과 같은 소셜 정보

이 애플리케이션의 세분화된 데이터 쿼리 액세스 요구 사항을 충족하기 위해 프라이머리 키(파티션 키 및 정렬 키)는 일반 이름(PK 및 SK)을 사용하므로 아래에서 볼 수 있듯이 다양한 유형의 값으로 오버로드될 수 있습니다.

이 스키마 설계의 액세스 패턴은 다음과 같습니다.
+ 사용자의 친구 목록 가져오기
+ 플레이어의 모든 정보 가져오기
+ 사용자의 아이템 목록 가져오기
+ 사용자의 아이템 목록에서 특정 아이템 가져오기
+ 사용자 캐릭터 업데이트
+ 사용자의 아이템 개수 업데이트

게임 프로필의 크기는 게임마다 다릅니다. [큰 속성 값 압축](bp-use-s3-too.md)을 통해 DynamoDB의 항목 제한에 맞출 수 있고, 비용을 절감할 수 있습니다. 처리량 관리 전략은 플레이어 수, 초당 플레이한 게임 수, 워크로드의 계절성 같은 다양한 요인에 따라 달라집니다. 일반적으로 새로 출시되는 게임의 경우, 플레이어 수와 인기도를 알 수 없으므로 [온디맨드 처리량 모드](capacity-mode.md#capacity-mode-on-demand)로 시작하겠습니다.

## 게임 프로필 엔터티 관계 다이어그램
<a name="data-modeling-schema-gaming-profile-erd"></a>

다음은 게임 프로필 스키마 설계에 사용할 엔터티 관계 다이어그램(ERD)입니다.

![\[사용자, 게임, 점수와 같은 엔터티 간의 관계를 보여주는 게임 프로필의 ER 다이어그램.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfileERD.png)


## 게임 프로필 액세스 패턴
<a name="data-modeling-schema-gaming-profile-access-patterns"></a>

다음은 소셜 네트워크 스키마 설계 시 고려할 액세스 패턴입니다.
+ `getPlayerFriends`
+ `getPlayerAllProfile`
+ `getPlayerAllItems`
+ `getPlayerSpecificItem`
+ `updateCharacterAttributes`
+ `updateItemCount`

## 게임 프로필 스키마 설계 진화
<a name="data-modeling-schema-social-network-design-evolution"></a>

위의 ERD에서 이것이 데이터 모델링의 일대다 관계 유형임을 알 수 있습니다. DynamoDB에서는 일대다 데이터 모델을 항목 컬렉션으로 구성할 수 있는데, 이는 외래 키를 통해 여러 테이블을 생성하고 연결하는 기존 관계형 데이터베이스와는 다릅니다. [항목 컬렉션](WorkingWithItemCollections.md)은 동일한 파티션 키 값을 공유하지만 정렬 키 값이 다른 항목의 그룹입니다. 항목 컬렉션 내에서 각 항목은 다른 항목과 구분되는 고유한 정렬 키 값을 갖습니다. 이 점을 염두에 두고 각 엔터티 유형별로 `HASH` 값과 `RANGE` 값에 다음과 같은 패턴을 사용해 보겠습니다.

우선, 모델을 향후에도 사용할 수 있도록 `PK` 및 `SK` 같은 일반 이름으로 다양한 유형의 엔터티를 동일한 테이블에 저장합니다. 가독성을 높이기 위해 데이터 유형을 나타내는 접두사를 포함하거나 `Entity_type` 또는 `Type`이라는 임의의 속성을 포함할 수 있습니다. 현재 예제에서는 `player`로 시작하는 문자열을 사용하여 `player_ID`를 `PK`로 저장하고, `entity name#`를 `SK`의 접두사로 사용하고, 이 데이터가 어떤 엔터티 유형인지 나타내는 `Type` 속성을 추가합니다. 이를 통해 향후 더 많은 엔터티 유형을 저장할 수 있고, GSI Overloading 및 Sparse GSI 같은 고급 기술을 사용하여 더 많은 액세스 패턴을 충족할 수 있습니다.

액세스 패턴 구현을 시작해 보겠습니다. 플레이어 추가와 장비 추가 같은 액세스 패턴은 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 작업을 통해 구현할 수 있으므로 무시해도 됩니다. 이 문서에서는 위에 나열된 일반적인 액세스 패턴에 초점을 맞추겠습니다.

**1단계: 액세스 패턴 1(`getPlayerFriends`) 처리**

이 단계로 액세스 패턴 1(`getPlayerFriends`)을 처리합니다. 현재 설계에서는 우정이 단순하고 게임 내 친구 수가 적습니다. 간단히 하기 위해 목록 데이터 유형을 사용하여 친구 목록을 저장합니다(1:1 모델링). 이 설계에서는 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html)을 사용하여 이 액세스 패턴을 충족합니다. `GetItem` 작업에서는 특정 항목을 가져오기 위해 파티션 키 및 정렬 키 값을 명시적으로 제공합니다.

하지만 게임에 많은 수의 친구가 있고 친구 간의 관계가 복잡한 경우(예: 친구 관계가 초대 구성 요소와 수락 구성 요소가 모두 있는 양방향 우정), 친구 목록 크기를 무제한 확장하려면 다대다 관계를 사용하여 각 친구를 개별적으로 저장해야 합니다. 또한 우정을 변경하려면 여러 항목에서 동시에 작업해야 하는 경우, DynamoDB 트랜잭션을 사용하여 여러 작업을 그룹화하여 단일한 양자택일 방식 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 또는 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html) 작업으로 제출할 수 있습니다.

![\[Friends 엔터티의 게임 프로필에 대한 복잡한 다대다 관계 다이어그램.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile1.png)


**2단계: 액세스 패턴 2(`getPlayerAllProfile`), 3(`getPlayerAllItems`), 4(`getPlayerSpecificItem`) 처리**

이 단계를 사용하여 액세스 패턴 2(`getPlayerAllProfile`), 3(`getPlayerAllItems`), 4(`getPlayerSpecificItem`)를 처리합니다. 이 세 가지 액세스 패턴에 공통적으로 있는 것은 [`Query`](Query.md) 작업을 사용하는 범위 쿼리입니다. 쿼리 범위에 따라 실제 개발에서 일반적으로 사용되는 [키 조건](Query.KeyConditionExpressions.md) 및 [필터 식](Query.FilterExpression.md)이 사용됩니다.

쿼리 작업에서는 파티션 키에 단일 값을 제공하고 해당 파티션 키 값을 가진 모든 항목을 가져옵니다. 액세스 패턴 2(`getPlayerAllProfile`)는 이 방식으로 구현됩니다. 원하는 경우, 테이블에서 읽을 항목을 결정하는 문자열인 정렬 키 조건식을 추가할 수 있습니다. 액세스 패턴 3(`getPlayerAllItems`)은 `ITEMS#`로 시작하는 정렬 키의 키 조건을 추가하여 구현됩니다. 또한 애플리케이션 측 개발을 단순화하기 위해 필터 식을 사용하여 액세스 패턴 4(`getPlayerSpecificItem`)를 구현할 수 있습니다.

다음은 `Weapon` 카테고리의 항목을 필터링하는 필터 식을 사용하는 의사 코드 예제입니다.

```
filterExpression: "ItemType = :itemType"
expressionAttributeValues: {":itemType": "Weapon"}
```

![\[다양한 액세스 패턴을 구현하도록 파티션 키 및 정렬 키 조건을 포함한 쿼리 작업 사용.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile2.png)


**참고**  
필터 식은 쿼리가 완료된 후 결과가 클라이언트에 반환되기 전에 적용됩니다. 따라서 쿼리는 필터 식이 있는지 여부에 상관없이 동일한 양의 읽기 용량을 사용합니다.

액세스 패턴이 대규모 데이터 세트를 쿼리하고 대량의 데이터를 필터링하여 작은 데이터 하위 집합만 유지하는 것이라면 DynamoDB 파티션 키와 정렬 키를 보다 효과적으로 설계하는 것이 적절한 접근 방식입니다. 예를 들어 특정 `ItemType`을 얻는 위의 예제에서 각 플레이어마다 항목이 많고 특정 `ItemType`을 쿼리하는 것이 일반적인 액세스 패턴이라면 `ItemType`을 복합 키로 `SK`에 가져오는 것이 더 효율적입니다. 데이터 모델은 다음과 같습니다. `ITEMS#ItemType#ItemId`

**3단계: 액세스 패턴 5(`updateCharacterAttributes`) 및 6(`updateItemCount`) 처리**

이 단계를 사용하여 액세스 패턴 5(`updateCharacterAttributes`)와 6(`updateItemCount`)을 처리합니다. 플레이어가 화폐를 줄이거나 아이템 중 특정 무기 수량을 수정하는 등 캐릭터를 수정해야 할 경우, [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html)을 사용하여 이러한 액세스 패턴을 구현합니다. 플레이어의 화폐를 업데이트하되 최소 금액 이하로 내려가지 않도록 하기 위해 최소 금액 이상일 경우에만 잔액을 줄일 수 있는 [DynamoDB 조건 표현식 CLI 예제](Expressions.ConditionExpressions.md)을 추가할 수 있습니다. 다음은 의사 코드 예제입니다.

```
UpdateExpression: "SET currency = currency - :amount"
ConditionExpression: "currency >= :minAmount"
```

![\[설정된 금액 미만이 되지 않도록 플레이어의 통화를 수정하기 위해 조건 표현식과 함께 UpdateItem 사용.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile4-Update-player-Currency.png)


DynamoDB로 개발하고 [Atomic Counters](WorkingWithItems.md#WorkingWithItems.AtomicCounters)를 사용하여 인벤토리를 줄이는 경우, 낙관적 잠금을 사용하여 멱등성을 보장할 수 있습니다. 다음은 Atomic Counters의 의사 코드 예제입니다.

```
UpdateExpression: "SET ItemCount = ItemCount - :incr"
expression-attribute-values: '{":incr":{"N":"1"}}'
```

![\[ItemCount 속성 값을 5에서 4로 줄이도록 원자성 카운터 사용.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile5-Update-Item-Count.png)


또한 플레이어가 화폐로 아이템을 구매하는 시나리오에서는 전체 프로세스가 통화를 차감하는 동시에 아이템을 추가해야 합니다. DynamoDB Transactions를 사용하여 여러 작업을 그룹화하고 이를 하나의 전부 아니면 전무 `TransactWriteItems` 또는 `TransactGetItems` 작업으로 제출할 수 있습니다. `TransactWriteItems`는 하나의 전부 아니면 전무 작업에서 최대 100개의 쓰기 작업을 그룹화하는 동기식 및 멱등성 쓰기 작업입니다. 작업은 원자 단위로 완료되므로 작업이 모두 성공하거나 모두 실패하게 됩니다. 트랜잭션은 화폐 중복 또는 소멸의 위험을 없애는 데 도움이 됩니다. 트랜잭션에 대한 자세한 내용은 [DynamoDB Transactions 예](transaction-example.md) 섹션을 참조세요.

모든 액세스 패턴과 스키마 설계에서 이를 처리하는 방법이 아래 표에 요약되어 있습니다.


| 액세스 패턴 | 기본 테이블/GSI/LSI | 연산 | 파티션 키 값 | 정렬 키 값 | 기타 조건/필터 | 
| --- | --- | --- | --- | --- | --- | 
| getPlayerFriends | 기본 테이블 | GetItem | PK=PlayerID | SK=“FRIENDS\$1playerID” |  | 
| getPlayerAllProfile | 기본 테이블 | 쿼리 | PK=PlayerID |  |  | 
| getPlayerAllItems | 기본 테이블 | 쿼리 | PK=PlayerID | SK begins\$1with “ITEMS\$1” |  | 
| getPlayerSpecificItem | 기본 테이블 | 쿼리 | PK=PlayerID | SK begins\$1with “ITEMS\$1” | filterExpression: "ItemType = :itemType" expressionAttributeValues: \$1 ":itemType": "Weapon" \$1 | 
| updateCharacterAttributes | 기본 테이블 | UpdateItem | PK=PlayerID | SK=“\$1METADATA\$1playerID” | UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount" | 
| updateItemCount | 기본 테이블 | UpdateItem | PK=PlayerID | SK =“ITEMS\$1ItemID” | update-expression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '\$1":incr":\$1"N":"1"\$1\$1' | 

## 게임 프로필 최종 스키마
<a name="data-modeling-schema-gaming-profile-final-schema"></a>

다음은 최종 스키마 설계입니다. 이 스키마 설계를 JSON 파일로 다운로드하려면 GitHub의 [DynamoDB 예제](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/GamingPlayerProfiles/GamePlayerProfilesSchema.json)를 참조하세요.

**기본 테이블:**

![\[이전 액세스 패턴 구현의 결과를 포함하는 테이블의 최종 스키마 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile6-FinalSchema.png)


## 이 스키마 설계와 함께 NoSQL Workbench 사용
<a name="data-modeling-schema-gaming-profile-nosql"></a>

이 최종 스키마를 DynamoDB 데이터 모델링, 데이터 시각화, 쿼리 개발 기능을 제공하는 시각적 도구인 [NoSQL Workbench](workbench.md)로 가져와서 새 프로젝트를 추가로 탐색하고 편집할 수 있습니다. 시작하려면 다음 단계를 따릅니다.

1. NoSQL Workbench 다운로드 자세한 내용은 [DynamoDB용 NoSQL Workbench 다운로드](workbench.settingup.md) 섹션을 참조하세요.

1. 위에 나열된 JSON 스키마 파일을 다운로드합니다. 이 파일은 이미 NoSQL Workbench 모델 형식으로 되어 있습니다.

1. JSON 스키마 파일을 NoSQL Workbench로 가져옵니다. 자세한 내용은 [기존 데이터 모델 가져오기](workbench.Modeler.ImportExisting.md) 섹션을 참조하세요.

1. NOSQL Workbench로 가져온 후 데이터 모델을 편집할 수 있습니다. 자세한 내용은 [기존 데이터 모델 편집](workbench.Modeler.Edit.md) 섹션을 참조하세요.

# DynamoDB에서 불만 관리 시스템 스키마 설계
<a name="data-modeling-complaint-management"></a>

## 불만 관리 시스템 비즈니스 사용 사례
<a name="data-modeling-schema-complaint-management-use-case"></a>

DynamoDB는 불만 관리 시스템(또는 콜센터) 사용 사례에 적합한 데이터베이스입니다. 관련된 대부분의 액세스 패턴이 키-값 기반 트랜잭션 조회이기 때문입니다. 이 시나리오의 일반적인 액세스 패턴은 다음과 같습니다.
+ 불만 사항 생성 및 업데이트
+ 불만 사항 에스컬레이션
+ 불만 사항에 대한 의견 작성 및 읽기
+ 고객의 모든 불만 사항 접수
+ 에이전트의 모든 의견 가져오기 및 모든 에스컬레이션 가져오기 

일부 의견에는 불만 사항 또는 해결 방법을 설명하는 첨부 파일이 있을 수 있습니다. 이러한 패턴은 모두 키-값 액세스 패턴이지만 불만 사항에 새 의견이 추가될 때 알림을 보내거나 분석 쿼리를 실행하여 주간 심각도(또는 에이전트 성과)별로 불만 사항 분포를 확인하는 등의 추가 요구 사항이 있을 수 있습니다. 수명 주기 관리 또는 규정 준수와 관련된 추가 요구 사항은 불만 사항을 기록한 지 3년이 지난 후에 불만 사항 데이터를 아카이빙하는 것입니다.

## 불만 관리 시스템 아키텍처 다이어그램
<a name="data-modeling-schema-complaint-management-ad"></a>

다음 다이어그램은 불만 관리 시스템의 아키텍처 다이어그램입니다. 이 다이어그램은 불만 관리 시스템에서 사용하는 다양한 AWS 서비스 통합을 보여니다.

![\[여러 AWS 서비스와의 통합을 통해 비트랜잭션 요구 사항을 충족할 수 있도록 통합된 워크플로.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-1-AD.jpg)


나중에 DynamoDB 데이터 모델링 섹션에서 다루게 될 키-값 트랜잭션 액세스 패턴 외에도 세 가지 비트랜잭션 요구 사항이 있습니다. 위의 아키텍처 다이어그램은 다음 세 가지 워크플로로 나눌 수 있습니다.

1. 불만 사항에 새 의견이 추가되면 알림 보내기

1. 주간 데이터에 대한 분석 쿼리 실행

1. 3년 이상 경과된 데이터 아카이빙

각각에 대해 좀더 자세히 살펴보겠습니다.

**불만 사항에 새 의견이 추가되면 알림 보내기**

아래 워크플로를 사용하여 이 요구 사항을 달성할 수 있습니다.

![\[DynamoDB Streams에서 기록된 변경 내용을 기반으로 알림을 전송하도록 Lambda 함수를 간접적으로 호출하는 워크플로.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-2-Workflow1.jpg)


[DynamoDB Streams](Streams.md)는 DynamoDB 테이블에 대한 모든 쓰기 작업을 기록하는 변경 데이터 캡처 메커니즘입니다. 이러한 변경 사항의 일부 또는 전부를 트리거하도록 Lambda 함수를 구성할 수 있습니다. 사용 사례와 관련이 없는 이벤트를 필터링하도록 Lambda 트리거에 [이벤트 필터](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)를 구성할 수 있습니다. 이 경우 필터를 사용하여 새 의견이 추가된 경우에만 Lambda를 트리거하고 [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) 또는 기타 보안 인증 정보 스토어에서 가져올 수 있는 관련 이메일 ID로 알림을 보낼 수 있습니다.

**주간 데이터에 대한 분석 쿼리 실행**

DynamoDB는 주로 온라인 트랜잭션 처리(OLTP)에 중점을 둔 워크로드에 적합합니다. 분석 요구 사항이 있는 다른 10%\$120% 액세스 패턴의 경우, DynamoDB 테이블의 실시간 트래픽에 영향을 주지 않고 관리형 [Amazon S3로 내보내기](S3DataExport.HowItWorks.md) 기능을 사용하여 데이터를 S3으로 내보낼 수 있습니다. 아래에서 이 워크플로를 살펴보세요.

![\[Lambda 함수를 주기적으로 간접적으로 호출하여 DynamoDB 데이터를 Amazon S3 버킷에 저장하는 워크플로.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-3-Workflow2.jpg)


[Amazon EventBridge](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is)를 사용하면 일정에 따라 AWS Lambda를 트리거할 수 있습니다. 이를 통해 Lambda 호출이 주기적으로 발생하도록 cron 표현식을 구성할 수 있습니다. Lambda는 `ExportToS3` API를 호출하고 DynamoDB 데이터를 S3에 저장할 수 있습니다. 그런 다음 [Amazon Athena](https://docs.aws.amazon.com/athena/latest/ug/what-is)와 같은 SQL 엔진에서 이 S3 데이터에 액세스하여 테이블의 실시간 트랜잭션 워크로드에 영향을 주지 않고 DynamoDB 데이터에 대한 분석 쿼리를 실행할 수 있습니다. 심각도 수준별 불만 사항 수를 확인하기 위한 샘플 Athena 쿼리는 다음과 같습니다.

```
SELECT Item.severity.S as "Severity", COUNT(Item) as "Count"
FROM "complaint_management"."data"
WHERE NOT Item.severity.S = ''
GROUP BY Item.severity.S ;
```

이 Athena 쿼리는 다음 결과를 반환합니다.

![\[심각도 수준 P3, P2, P1에 대한 불만 건수를 보여주는 Athena 쿼리 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-4-Athena.png)


**3년 이상 경과된 데이터 아카이빙**

DynamoDB [TTL(Time to Live)](TTL.md) 기능을 활용하면 추가 비용 없이 DynamoDB 테이블에서 사용되지 않는 데이터를 삭제할 수 있습니다(다른 리전에 복제된 TTL 삭제가 쓰기 용량을 소비하는 2019.11.21(현재) 버전의 글로벌 테이블 복제본인 경우 제외). 이 데이터는 표시되며 DynamoDB Streams에서 소비되어 Amazon S3에 아카이빙될 수 있습니다. 이 요구 사항에 대한 워크플로는 다음과 같습니다.

![\[TTL 기능과 DynamoDB Streams를 사용하여 Amazon S3 버킷에 이전 데이터를 보관하는 워크플로.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-5-Workflow3.jpg)


## 불만 관리 시스템 엔터티 관계 다이어그램
<a name="data-modeling-schema-complaint-management-erd"></a>

다음은 불만 관리 스키마 설계에 사용할 엔터티 관계 다이어그램(ERD)입니다.

![\[고객, 불만 사항, 의견, 에이전트 항목을 보여주는 불만 관리 시스템 ERD.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-6-ERD.jpg)


## 불만 관리 시스템 액세스 패턴
<a name="data-modeling-schema-complaint-management-access-patterns"></a>

다음은 불만 관리 스키마를 설계할 때 고려할 액세스 패턴입니다.

1. createComplaint

1. updateComplaint

1. updateSeveritybyComplaintID

1. getComplaintByComplaintID

1. addCommentByComplaintID

1. getAllCommentsByComplaintID

1. getLatestCommentByComplaintID

1. getAComplaintbyCustomerIDAndComplaintID

1. getAllComplaintsByCustomerID

1. escalateComplaintByComplaintID

1. getAllEscalatedComplaints

1. getEscalatedComplaintsByAgentID(최신 것부터 가장 오래된 것 순)

1. getCommentsByAgentID(두 날짜 사이)

## 불만 관리 시스템 스키마 설계 진화
<a name="data-modeling-schema-complaint-management-design-evolution"></a>

이는 불만 관리 시스템이므로 대부분의 액세스 패턴에서 불만 사항이 기본 엔터티입니다. `ComplaintID`는 카디널리티가 높을 때 기본 파티션에 데이터가 균일하게 배포되도록 하며 식별된 액세스 패턴에 대한 가장 일반적인 검색 기준이기도 합니다. 따라서 `ComplaintID`는 이 데이터 세트에서 좋은 파티션 키 후보입니다.

**1단계: 액세스 패턴 1(`createComplaint`), 2(`updateComplaint`), 3(`updateSeveritybyComplaintID`), 4(`getComplaintByComplaintID`) 처리**

'metadata'(또는 'AA')라는 일반 정렬 키 값을 사용하여 `CustomerID`, `State`, `Severity`, `CreationDate`와 같은 불만 사항별 정보를 저장할 수 있습니다. `PK=ComplaintID` 및 `SK=“metadata”`인 싱글톤 작업을 사용하여 다음을 수행합니다.

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html)을 통해 새 불만 사항 생성

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html)을 통해 불만 사항 메타데이터의 심각도 또는 기타 필드 업데이트

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html)을 통해 불만 사항에 대한 메타데이터 가져오기

![\[불만 항목의 프라이머리 키, 정렬 키, 속성 값(예: customer_id 및 심각도).\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-7-Step1.png)


**2단계: 액세스 패턴 5(`addCommentByComplaintID`) 처리**

이 액세스 패턴에는 불만 사항과 불만 사항에 대한 의견 간의 일대다 관계 모델이 필요합니다. 여기서는 [수직 파티셔닝](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning) 기법을 통해 정렬 키를 사용하며 다양한 유형의 데이터로 항목 컬렉션을 생성합니다. 액세스 패턴 6(`getAllCommentsByComplaintID`) 및 7(`getLatestCommentByComplaintID`)을 살펴보면 의견을 시간별로 정렬해야 한다는 것을 알 수 있습니다. 또한 여러 개의 의견이 동시에 들어올 수 있으므로 [복합 정렬 키](data-modeling-blocks.md#data-modeling-blocks-composite) 기법을 사용하여 정렬 키 속성에 time과 `CommentID`를 추가할 수 있습니다.

이러한 의견 충돌 가능성에 대처하기 위한 다른 옵션은 타임스탬프의 세분성을 높이거나 `Comment_ID`를 사용하는 대신 증분 숫자를 접미사로 추가하는 것입니다. 이 경우 범위 기반 작업을 활성화하기 위해 의견에 해당하는 항목의 정렬 키 값 앞에 'comm\$1'을 붙입니다.

또한 불만 사항 메타데이터의 `currentState`가 새 의견이 추가될 때의 상태를 반영하는지 확인해야 합니다. 의견을 추가하면 불만 사항이 에이전트에게 배정되었거나 해결되었다는 등의 메시지가 표시될 수 있습니다. 의견 추가 및 불만 사항 메타데이터의 현재 상태 업데이트를 전부 아니면 전무 방식으로 묶기 위해 [TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) API를 사용합니다. 이제 결과 테이블 상태는 다음과 같습니다.

![\[복합 정렬 키를 사용하여 불만 사항과 의견을 일대다 관계로 저장하는 테이블.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-8-Step2.png)


테이블에 더 많은 데이터를 추가하고 `ComplaintID`에 대한 추가 인덱스가 필요한 경우 모델의 미래 유용성을 높이기 위해 `ComplaintID`를 `PK`와 별도의 필드로 추가해 보겠습니다. 또한 일부 의견에는 첨부 파일이 있을 수 있습니다. 이 첨부 파일은 Amazon Simple Storage Service에 저장하고 해당 참조 또는 URL만 DynamoDB에 보관합니다. 비용과 성능을 최적화하기 위해 트랜잭션 데이터베이스를 최대한 간결하게 유지하는 것이 좋습니다. 데이터는 이제 다음과 같습니다.

![\[불만 사항 메타데이터 및 각 불만 사항과 관련된 모든 의견 데이터가 포함된 테이블.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-9-Step3.png)


**3단계: 액세스 패턴 6(`getAllCommentsByComplaintID`) 및 7(`getLatestCommentByComplaintID`) 처리**

불만 사항에 대한 모든 의견을 가져오려면 정렬 키의 `begins_with` 조건과 함께 [`query`](Query.md) 작업을 사용할 수 있습니다. 이와 같은 정렬 키 조건을 사용하면 메타데이터 항목을 읽기 위해 읽기 용량을 추가로 소비하고 관련 결과를 필터링하는 오버헤드가 발생하는 대신 필요한 항목만 읽을 수 있습니다. 예를 들어, `PK=Complaint123` 및 `SK` begins\$1with `comm#`을 사용한 쿼리 작업은 메타데이터 항목을 건너뛰는 동안 다음을 반환합니다.

![\[불만 사항에 대한 의견만 표시하는 정렬 키 조건을 사용한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-10-Step4.png)


패턴 7(`getLatestCommentByComplaintID`)의 불만 사항에 대한 최신 의견이 필요하므로 두 개의 추가 쿼리 파라미터를 사용하겠습니다.

1. 결과를 내림차순으로 정렬하려면 `ScanIndexForward`를 False로 설정해야 합니다.

1. 최신(단 하나) 의견을 가져오려면 `Limit`를 1로 설정해야 합니다.

액세스 패턴 6(`getAllCommentsByComplaintID`)과 유사하게 `begins_with` `comm#`을 정렬 키 조건으로 사용하여 메타데이터 항목을 건너뜁니다. 이제 `PK=Complaint123`, `SK=begins_with comm#`, `ScanIndexForward=False`, `Limit` 1과 함께 쿼리 작업을 사용하여 이 설계에서 액세스 패턴 7을 수행할 수 있습니다. 대상으로 지정된 다음 항목이 결과로 반환됩니다.

![\[불만 사항에 대한 최신 의견을 확인하기 위해 정렬 키 조건을 사용한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-11-Step5.png)


테이블에 더미 데이터를 더 추가해 보겠습니다.

![\[접수된 불만 사항에 대한 최신 의견을 확인할 수 있는 더미 데이터가 포함된 테이블.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-12-Step6.png)


**4단계: 액세스 패턴 8(`getAComplaintbyCustomerIDAndComplaintID`) 및 9(`getAllComplaintsByCustomerID`) 처리**

액세스 패턴 8(`getAComplaintbyCustomerIDAndComplaintID`) 및 9(`getAllComplaintsByCustomerID`)에는 `CustomerID`라는 새로운 검색 기준이 도입되었습니다. 기존 테이블에서 가져오려면 모든 데이터를 읽고 해당 `CustomerID`에 대한 관련 항목을 필터링하기 위해 비용이 많이 드는 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)이 필요합니다. `CustomerID`를 파티션 키로 사용하여 [글로벌 보조 인덱스(GSI)](GSI.md)를 생성하면 이 검색을 더욱 효율적으로 만들 수 있습니다. 고객과 불만 사항 간의 일대다 관계와 액세스 패턴 9(`getAllComplaintsByCustomerID`)을 염두에 두면 `ComplaintID`가 정렬 키에 적합한 후보가 됩니다.

GSI의 데이터는 다음과 같습니다.

![\[일대다 관계 모델을 사용하여 특정 CustomerID의 모든 불만 사항을 가져오는 GSI.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-13-Step4-GSI.png)


 액세스 패턴 8(`getAComplaintbyCustomerIDAndComplaintID`)에 대한 이 GSI의 쿼리 예시는 `customer_id=custXYZ`, `sort key=Complaint1321`입니다. 결과는 다음과 같습니다.

![\[해당 고객의 특정 불만 사항에 대한 데이터를 가져오기 위한 GSI 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-14-Step4-8.png)


액세스 패턴 9(`getAllComplaintsByCustomerID`)에 대한 고객의 모든 불만 사항을 가져오려면 GSI에 대한 쿼리는 파티션 키 조건으로 `customer_id=custXYZ`입니다. 결과는 다음과 같습니다.

![\[해당 고객의 모든 불만 사항을 가져오기 위해 파티션 키를 사용한 쿼리 작업 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-15-Step4-9.png)


**5단계: 액세스 패턴 10(`escalateComplaintByComplaintID`) 처리**

이 액세스에는 에스컬레이션 측면이 도입됩니다. 불만 사항을 에스컬레이션하기 위해 `UpdateItem`을 사용하여 `escalated_to` 및 `escalation_time`과 같은 속성을 기존 불만 사항 메타데이터 항목에 추가할 수 있습니다. DynamoDB는 유연한 스키마 설계를 제공하므로 키가 아닌 속성 세트가 여러 항목 간에 균일하거나 이산적일 수 있습니다. 아래 예를 참조하세요.

`UpdateItem with PK=Complaint1444, SK=metadata`

![\[UpdateItem API 작업을 사용하여 불만 사항 메타데이터를 업데이트한 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-16-Step5.png)


**6단계: 액세스 패턴 11(`getAllEscalatedComplaints`) 및 12(`getEscalatedComplaintsByAgentID`) 처리**

전체 데이터 세트에서 소수의 불만 사항만 에스컬레이션될 것으로 예상됩니다. 따라서 에스컬레이션 관련 속성에 대한 인덱스를 생성하면 효율적인 조회는 물론 비용 효율적인 GSI 저장이 가능합니다. 이를 위해 [희소 인덱스](data-modeling-blocks.md#data-modeling-blocks-sparse-index) 기법을 활용할 수 있습니다. 파티션 키가 `escalated_to`이고 정렬 키가 `escalation_time`인 GSI는 다음과 같습니다.

![\[에스컬레이션 관련 속성인 escalated_to 및 escalation_time을 사용한 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-17-Step6.png)


액세스 패턴 11(`getAllEscalatedComplaints`)에 대해 에스컬레이션된 모든 불만 사항을 가져오려면 이 GSI를 스캔하면 됩니다. 이 스캔은 GSI의 크기 때문에 성능과 비용 효율성이 뛰어납니다. 특정 에이전트에 대해 에스컬레이션된 불만 사항을 가져오려면(액세스 패턴 12(`getEscalatedComplaintsByAgentID`)) 파티션 키를 `escalated_to=agentID`로 지정하고 `ScanIndexForward`를 `False`로 설정하여 최신 항목부터 오래된 항목 순으로 정렬합니다.

**7단계: 액세스 패턴 13(`getCommentsByAgentID`) 처리**

마지막 액세스 패턴의 경우 새로운 차원인 `AgentID`로 조회를 수행해야 합니다. 또한 두 날짜 사이의 의견을 읽으려면 시간 기반 순서가 필요하므로 파티션 키로 `agent_id`를, 정렬 키로 `comm_date`를 사용하여 GSI를 생성합니다. 이 GSI의 데이터는 다음과 같습니다.

![\[의견 날짜를 기준으로 정렬하여 특정 에이전트의 의견을 조회하기 위한 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-18.png)


이 GSI에 대한 예시 쿼리는 `partition key agentID=AgentA` 및 `sort key=comm_date between (2023-04-30T12:30:00, 2023-05-01T09:00:00)`이며, 그 결과는 다음과 같습니다.

![\[GSI에 대해 파티션 키와 정렬 키를 사용한 쿼리 결과.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-19.png)


모든 액세스 패턴과 스키마 설계에서 이를 처리하는 방법이 아래 표에 요약되어 있습니다.


| 액세스 패턴 | 기본 테이블/GSI/LSI | 연산 | 파티션 키 값 | 정렬 키 값 | 기타 조건/필터 | 
| --- | --- | --- | --- | --- | --- | 
| createComplaint | 기본 테이블 | PutItem | PK=complaint\$1id | SK=metadata |  | 
| updateComplaint | 기본 테이블 | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| updateSeveritybyComplaintID | 기본 테이블 | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| getComplaintByComplaintID | 기본 테이블 | GetItem | PK=complaint\$1id | SK=metadata |  | 
| addCommentByComplaintID | 기본 테이블 | TransactWriteItems | PK=complaint\$1id | SK=metadata, SK=comm\$1comm\$1date\$1comm\$1id |  | 
| getAllCommentsByComplaintID | 기본 테이블 | 쿼리 | PK=complaint\$1id | SK begins\$1with "comm\$1" |  | 
| getLatestCommentByComplaintID | 기본 테이블 | 쿼리 | PK=complaint\$1id | SK begins\$1with "comm\$1" | scan\$1index\$1forward=False, Limit 1 | 
| getAComplaintbyCustomerIDAndComplaintID | Customer\$1complaint\$1GSI | 쿼리 | customer\$1id=customer\$1id | complaint\$1id = complaint\$1id |  | 
| getAllComplaintsByCustomerID | Customer\$1complaint\$1GSI | 쿼리 | customer\$1id=customer\$1id | 해당 사항 없음 |  | 
| escalateComplaintByComplaintID | 기본 테이블 | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| getAllEscalatedComplaints | Escalations\$1GSI | 스캔 | 해당 사항 없음 | 해당 사항 없음 |  | 
| getEscalatedComplaintsByAgentID(최신 것부터 가장 오래된 것 순) | Escalations\$1GSI | 쿼리 | escalated\$1to=agent\$1id | 해당 사항 없음 | scan\$1index\$1forward=False | 
| getCommentsByAgentID(두 날짜 사이) | Agents\$1Comments\$1GSI | 쿼리 | agent\$1id=agent\$1id | SK between (date1, date2) |  | 

## 불만 관리 시스템 최종 스키마
<a name="data-modeling-schema-complaint-management-final-schema"></a>

다음은 최종 스키마 설계입니다. 이 스키마 설계를 JSON 파일로 다운로드하려면 GitHub의 [DynamoDB 예제](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ComplainManagement/ComplaintManagementSchema.json)를 참조하세요.

**기본 테이블**

![\[불만 사항 메타데이터를 사용한 기본 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-20-Complaint_management_system.png)


**Customer\$1Complaint\$1GSI**

![\[특정 고객의 불만 사항을 보여주는 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-21-Customer_Complaint_GSI.png)


**Escalations\$1GSI**

![\[에스컬레이션 관련 속성을 보여주는 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-22-Escalations_GSI.png)


**Agents\$1Comments\$1GSI**

![\[특정 에이전트의 의견을 보여주는 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-23-Comments_GSI.png)


## 이 스키마 설계와 함께 NoSQL Workbench 사용
<a name="data-modeling-schema-complaint-management-nosql"></a>

이 최종 스키마를 DynamoDB 데이터 모델링, 데이터 시각화, 쿼리 개발 기능을 제공하는 시각적 도구인 [NoSQL Workbench](workbench.md)로 가져와서 새 프로젝트를 추가로 탐색하고 편집할 수 있습니다. 시작하려면 다음 단계를 따릅니다.

1. NoSQL Workbench 다운로드 자세한 내용은 [DynamoDB용 NoSQL Workbench 다운로드](workbench.settingup.md) 섹션을 참조하세요.

1. 위에 나열된 JSON 스키마 파일을 다운로드합니다. 이 파일은 이미 NoSQL Workbench 모델 형식으로 되어 있습니다.

1. JSON 스키마 파일을 NoSQL Workbench로 가져옵니다. 자세한 내용은 [기존 데이터 모델 가져오기](workbench.Modeler.ImportExisting.md) 섹션을 참조하세요.

1. NOSQL Workbench로 가져온 후 데이터 모델을 편집할 수 있습니다. 자세한 내용은 [기존 데이터 모델 편집](workbench.Modeler.Edit.md) 섹션을 참조하세요.

# DynamoDB에서 반복 결제 스키마 설계
<a name="data-modeling-schema-recurring-payments"></a>

## 반복 결제 비즈니스 사용 사례
<a name="data-modeling-schema-recurring-payments-use-case"></a>

이 사용 사례에서는 DynamoDB를 사용하여 반복 결제 시스템을 구현하는 방법을 설명합니다. 데이터 모델에는 *계정*, *구독*, *영수증*과 같은 엔터티가 있습니다. 사용 사례의 구체적인 내용은 다음과 같습니다.
+ 각 *계정*에는 여러 *구독*이 있을 수 있습니다.
+ *구독*에는 다음 결제를 처리해야 하는 `NextPaymentDate`가 있고 고객에게 이메일 미리 알림이 전송되는 `NextReminderDate`가 있습니다
+ 결제가 처리될 때 저장되고 업데이트되는 *구독* 항목이 있습니다(평균 항목 크기는 약 1KB이며 처리량은 *계정* 및 *구독* 수에 따라 다름).
+ 또한 *결제* 처리자는 테이블에 저장되고 [TTL](TTL.md) 속성을 사용하여 일정 기간 후에 만료되도록 설정된 프로세스의 일부로 *영수증*을 생성합니다

## 반복 결제 엔터티 관계 다이어그램
<a name="data-modeling-schema-recurring-payments-erd"></a>

다음은 반복 결제 시스템 스키마 설계에 사용할 엔터티 관계 다이어그램(ERD)입니다.

![\[계정, 구독 및 영수증 엔터티를 보여주는 반복 결제 시스템 ERD.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-1-ERD.png)


## 반복 결제 시스템 액세스 패턴
<a name="data-modeling-schema-recurring-payments-access-patterns"></a>

다음은 반복 결제 시스템 스키마를 설계할 때 고려할 액세스 패턴입니다.

1. `createSubscription`

1. `createReceipt`

1. `updateSubscription`

1. `getDueRemindersByDate`

1. `getDuePaymentsByDate`

1. `getSubscriptionsByAccount`

1. `getReceiptsByAccount`

## 반복 결제 스키마 설계
<a name="data-modeling-schema-recurring-payments-design-evolution"></a>

일반 이름 `PK` 및 `SK`는 계정, 구독 및 영수증 엔터티와 같은 다양한 유형의 엔터티를 동일한 테이블에 저장할 수 있도록 키 속성에 사용됩니다. 사용자는 먼저 구독을 생성하여 제품에 대한 대금을 매달 같은 날에 지불하는 데 동의합니다. 매월 어느 날에 결제를 처리할지 사용자가 선택할 수 있습니다. 결제가 처리되기 전에 전송되는 미리 알림도 있습니다. 애플리케이션은 매일 실행되는 두 개의 배치 작업, 즉 해당 날짜 기한의 미리 알림을 전송하는 배치 작업과 해당 날짜 기한의 모든 결제를 처리하는 배치 작업을 통해 작동합니다.

**1단계: 액세스 패턴 1(`createSubscription`) 처리**

액세스 패턴 1(`createSubscription`)은 구독을 처음 만드는 데 사용되며 `SKU`, `NextPaymentDate`, `NextReminderDate`, `PaymentDetails` 등의 세부 정보가 설정됩니다. 이 단계에서는 구독이 하나인 계정 한 개에 대한 테이블 상태를 보여 줍니다. 항목 컬렉션에 여러 개의 구독이 있을 수 있으므로 이는 일대다 관계입니다.

![\[계정의 구독 세부 정보를 보여주는 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-2-Step1.png)


**2단계: 액세스 패턴 2(`createReceipt`) 및 3(`updateSubscription`) 처리**

액세스 패턴 2(`createReceipt`)는 영수증 항목을 만드는 데 사용됩니다. 매월 결제가 처리되면 결제 처리자는 기본 테이블에 영수증을 다시 기록합니다. 항목 컬렉션에 여러 개의 영수증이 있을 수 있으므로 이는 일대다 관계입니다. 또한 결제 처리자는 구독 항목을 업데이트하여(액세스 패턴 3(`updateSubscription`)) 다음 달의 `NextReminderDate` 또는 `NextPaymentDate`를 업데이트합니다.

![\[다음 구독 알림 날짜를 보여주는 영수증 세부 정보 및 구독 항목 업데이트.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-3-Step2.png)


**3단계: 액세스 패턴 4(`getDueRemindersByDate`) 처리**

애플리케이션은 당일 결제 미리 알림을 배치로 처리합니다. 따라서 애플리케이션은 계정이 아닌 날짜라는 다른 차원에서 구독에 액세스해야 합니다. 이는 [글로벌 보조 인덱스(GSI)](GSI.md)의 좋은 사용 사례입니다. 이 단계에서는 `NextReminderDate`를 GSI 파티션 키로 사용하는 인덱스 `GSI-1`을 추가합니다. 모든 항목을 복제할 필요는 없습니다. 이 GSI는 [희소 인덱스](data-modeling-blocks.md#data-modeling-blocks-sparse-index)이며 영수증 항목은 복제되지 않습니다. 또한 모든 속성을 프로젝션할 필요는 없으며, 속성의 일부만 포함하면 됩니다. 아래 이미지는 `GSI-1`의 스키마를 보여주며 애플리케이션이 미리 알림 이메일을 보내는 데 필요한 정보를 제공합니다.

![\[이메일 주소와 같은 세부 정보가 포함된 GSI-1 스키마. 애플리케이션에서 알림 이메일을 보내야 합니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-4-Step3.png)


**4단계: 액세스 패턴 5(`getDuePaymentsByDate`) 처리**

애플리케이션은 미리 알림과 마찬가지로 당일 결제를 배치로 처리합니다. 이 단계에서는 `GSI-2`를 추가하며, `NextPaymentDate`를 GSI 파티션 키로 사용합니다. 모든 항목을 복제할 필요는 없습니다. 영수증 항목은 복제되지 않으므로 이 GSI는 희소 인덱스입니다. 아래 이미지는 `GSI-2`의 스키마를 보여줍니다.

![\[결제를 처리하기 위한 세부 정보가 포함된 GSI-2 스키마. NextPaymentDate는 GSI-2의 파티션 키입니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-5-Step4.png)


**5단계: 액세스 패턴 6(`getSubscriptionsByAccount`) 및 7(`getReceiptsByAccount`) 처리**

애플리케이션은 계정 식별자(`PK`)를 대상으로 하며 범위 연산자를 사용하여 `SK`가 'SUB\$1'로 시작하는 모든 항목을 가져오는 [쿼리](Query.md)를 기본 테이블에서 사용하여 계정의 모든 구독을 검색할 수 있습니다. 또한 애플리케이션은 동일한 쿼리 구조로 범위 연산자를 사용하여 `SK`가 'REC\$1'로 시작하는 모든 항목을 가져옴으로써 모든 영수증을 검색할 수 있습니다. 이를 통해 액세스 패턴 6(`getSubscriptionsByAccount`) 및 7(`getReceiptsByAccount`)을 충족할 수 있습니다. 애플리케이션은 이러한 액세스 패턴을 사용하므로 사용자는 현재 구독과 지난 6개월 동안의 과거 영수증을 볼 수 있습니다. 이 단계에서 테이블 스키마는 변경되지 않으며, 액세스 패턴 6(`getSubscriptionsByAccount`)의 구독 항목만 대상으로 지정하는 방법을 아래에서 확인할 수 있습니다.

![\[기본 테이블에 대한 쿼리 작업 결과. 특정 계정의 구독을 보여줍니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-6-Step5.png)


모든 액세스 패턴과 스키마 설계에서 이를 처리하는 방법이 아래 표에 요약되어 있습니다.


| 액세스 패턴 | 기본 테이블/GSI/LSI | 연산 | 파티션 키 값 | 정렬 키 값 | 
| --- | --- | --- | --- | --- | 
| createSubscription | 기본 테이블 | PutItem | ACC\$1account\$1id | SUB\$1<SUBID>\$1SKU<SKUID> | 
| createReceipt | 기본 테이블 | PutItem | ACC\$1account\$1id | REC\$1<RecieptDate>\$1SKU<SKUID> | 
| updateSubscription | 기본 테이블 | UpdateItem | ACC\$1account\$1id | SUB\$1<SUBID>\$1SKU<SKUID> | 
| getDueRemindersByDate | GSI-1 | 쿼리 | <NextReminderDate> |  | 
| getDuePaymentsByDate | GSI-2 | 쿼리 | <NextPaymentDate> |  | 
| getSubscriptionsByAccount | 기본 테이블 | 쿼리 | ACC\$1account\$1id | SK begins\$1with “SUB\$1” | 
| getReceiptsByAccount | 기본 테이블 | 쿼리 | ACC\$1account\$1id | SK begins\$1with “REC\$1” | 

## 반복 결제 최종 스키마
<a name="data-modeling-schema-recurring-payments-final-schema"></a>

다음은 최종 스키마 설계입니다. 이 스키마 설계를 JSON 파일로 다운로드하려면 GitHub의 [DynamoDB 예제](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ReocurringPayments/ReocurringPaymentsSchema.json)를 참조하세요.

**기본 테이블**

![\[계정 정보와 구독 및 영수증 세부 정보를 보여주는 기본 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-7-Base.png)


**GSI-1**

![\[이메일 주소, NextPaymentDate와 같은 구독 세부 정보가 포함된 GSI-1 스키마.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-8-GSI1.png)


**GSI-2**

![\[PaymentAmount 및 PaymentDay와 같은 결제 세부 정보가 포함된 GSI-2 스키마.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-9-GSI2.png)


## 이 스키마 설계와 함께 NoSQL Workbench 사용
<a name="data-modeling-schema-recurring-payments-nosql"></a>

이 최종 스키마를 DynamoDB 데이터 모델링, 데이터 시각화, 쿼리 개발 기능을 제공하는 시각적 도구인 [NoSQL Workbench](workbench.md)로 가져와서 새 프로젝트를 추가로 탐색하고 편집할 수 있습니다. 시작하려면 다음 단계를 따릅니다.

1. NoSQL Workbench 다운로드 자세한 내용은 [DynamoDB용 NoSQL Workbench 다운로드](workbench.settingup.md) 섹션을 참조하세요.

1. 위에 나열된 JSON 스키마 파일을 다운로드합니다. 이 파일은 이미 NoSQL Workbench 모델 형식으로 되어 있습니다.

1. JSON 스키마 파일을 NoSQL Workbench로 가져옵니다. 자세한 내용은 [기존 데이터 모델 가져오기](workbench.Modeler.ImportExisting.md) 섹션을 참조하세요.

1. NOSQL Workbench로 가져온 후 데이터 모델을 편집할 수 있습니다. 자세한 내용은 [기존 데이터 모델 편집](workbench.Modeler.Edit.md) 섹션을 참조하세요.

# DynamoDB에서 디바이스 상태 업데이트 모니터링
<a name="data-modeling-device-status"></a>

이 사용 사례에서는 DynamoDB를 사용하여 DynamoDB에서 디바이스 상태 업데이트(또는 디바이스 상태 변경)를 모니터링하는 방법을 설명합니다.

## 사용 사례
<a name="data-modeling-schema-device-status-use-case"></a>

IoT 사용 사례(예: 스마트 팩토리)에서는 운영자가 많은 디바이스를 모니터링해야 하며 운영자는 주기적으로 상태 또는 로그를 모니터링 시스템에 전송합니다. 디바이스에 문제가 발생할 경우 디바이스의 상태가 *정상*에서 *경고*로 바뀝니다. 디바이스는 비정상적인 동작의 심각도 및 유형에 따라 다양한 로그 수준 또는 상태를 갖게 됩니다. 시스템에서는 운영자를 배정하여 디바이스를 점검하도록 하며, 필요한 경우 운영자가 감독자에게 문제를 에스컬레이션할 수 있습니다.

이 시스템의 몇 가지 일반적인 액세스 패턴은 다음과 같습니다.
+ 디바이스에 대한 로그 항목 생성
+ 특정 디바이스 상태에 대한 모든 로그 가져오기(최근 로그 먼저 표시)
+ 두 날짜 사이의 지정된 운영자에 대한 모든 로그 가져오기
+ 지정된 감독자에게 에스컬레이션된 모든 로그 가져오기
+ 지정된 감독자에게 에스컬레이션되고 특정 디바이스 상태가 포함된 모든 로그 가져오기
+ 특정 날짜에 지정된 감독자에게 에스컬레이션되고 특정 디바이스 상태가 포함된 모든 로그 가져오기

## 엔터티 관계 다이어그램
<a name="data-modeling-schema-device-status-erd"></a>

다음은 디바이스 상태 업데이트를 모니터링하는 데 사용할 엔터티 관계 다이어그램(ERD)입니다.

![\[디바이스 상태 업데이트의 ERD. 여기에는 엔터티(디바이스 및 운영자)가 표시됩니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-1-ERD.jpg)


## 액세스 패턴
<a name="data-modeling-schema-device-status-access-patterns"></a>

디바이스 상태 업데이트를 모니터링하는 데 고려할 액세스 패턴은 다음과 같습니다.

1. `createLogEntryForSpecificDevice`

1. `getLogsForSpecificDevice`

1. `getWarningLogsForSpecificDevice`

1. `getLogsForOperatorBetweenTwoDates`

1. `getEscalatedLogsForSupervisor`

1. `getEscalatedLogsWithSpecificStatusForSupervisor`

1. `getEscalatedLogsWithSpecificStatusForSupervisorForDate`

## 스키마 설계 진화
<a name="data-modeling-schema-device-status-design-evolution"></a>

**1단계: 액세스 패턴 1(`createLogEntryForSpecificDevice`) 및 2(`getLogsForSpecificDevice`) 처리**

디바이스 추적 시스템의 규모 조정 단위는 개별 디바이스입니다. 이 시스템에서는 `deviceID`가 디바이스를 고유하게 식별합니다. 따라서 `deviceID`는 파티션 키로 적합한 후보입니다. 각 디바이스는 주기적으로(예: 5분마다) 추적 시스템에 정보를 전송합니다. 이렇게 정렬하면 날짜가 논리적인 정렬 기준이 되므로 정렬 키도 됩니다. 이 경우 샘플 데이터는 다음과 같습니다.

![\[여러 디바이스의 상태를 저장하는 테이블. DeviceID는 프라이머리 키이고 상태 업데이트 날짜는 정렬 키입니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-2-Step1.png)


특정 디바이스에 대한 로그 항목을 가져오려는 경우 파티션 키 `DeviceID="d#12345"`를 사용하여 [쿼리](Query.md) 작업을 수행할 수 있습니다.

**2단계: 액세스 패턴 3(`getWarningLogsForSpecificDevice`) 처리**

`State`는 키가 아닌 속성이므로 현재 스키마로 액세스 패턴 3을 처리하려면 [필터 표현식](Query.FilterExpression.md)이 필요합니다. DynamoDB에서는 키 조건 표현식을 사용하여 데이터를 읽은 후 필터 표현식이 적용됩니다. 예를 들어 `d#12345`에 대한 경고 로그를 가져오는 경우, 파티션 키 `DeviceID="d#12345"`를 사용한 쿼리 작업은 위 테이블에서 4개 항목을 읽은 다음 *경고* 상태가 없는 1개 항목을 필터링합니다. 이 접근 방식은 규모가 커지면 효율적이지 않습니다. 제외된 항목의 비율이 낮거나 쿼리가 자주 수행되지 않는 경우 필터 표현식은 쿼리되는 항목을 제외하는 좋은 방법이 될 수 있습니다. 하지만 테이블에서 많은 항목이 검색되고 대부분의 항목이 필터링되는 경우에는 테이블 설계를 계속 발전시켜 더 효율적으로 실행되도록 할 수 있습니다.

[복합 정렬 키](data-modeling-blocks.md#data-modeling-blocks-composite)를 사용하여 이 액세스 패턴을 처리하는 방법을 변경해 보겠습니다. 정렬 키가 `State#Date`로 변경된 [DeviceStateLog\$13.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_3.json)에서 샘플 데이터를 가져올 수 있습니다. 이 정렬 키는 `State`, `#` 및 `Date` 속성으로 구성됩니다. 이 예에서는 `#`이 구분 기호로 사용됩니다. 데이터는 이제 다음과 같습니다.

![\[복합 정렬 키 State#Date를 사용하여 가져온 디바이스 d#12345 상태 업데이트 데이터.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-3-Step2.png)


디바이스에 대한 경고 로그만 가져오려는 경우 이 스키마를 사용하면 쿼리의 대상이 더욱 명확해집니다. 쿼리의 키 조건은 파티션 키 `DeviceID="d#12345"` 및 정렬 키 `State#Date begins_with “WARNING”`을 사용합니다. 이 쿼리는 *경고* 상태의 관련 항목 3개만 읽습니다.

**3단계: 액세스 패턴 4(`getLogsForOperatorBetweenTwoDates`) 처리**

예제 데이터와 함께 `Operator` 속성이 `DeviceStateLog` 테이블에 추가된 [DeviceStateLog\$14.jsonD](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_4.json)를 가져올 수 있습니다.

![\[특정 날짜 사이의 운영자 로그를 가져오기 위해 운영자 속성을 사용한 DeviceStateLog 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-4-Step3.png)


`Operator`는 현재 파티션 키가 아니므로 `OperatorID`를 기반으로 이 테이블에서 직접 키-값 조회를 수행할 수 있는 방법이 없습니다. `OperatorID`에 대한 글로벌 보조 인덱스를 사용하여 새 [항목 컬렉션](WorkingWithItemCollections.md)을 생성해야 합니다. 액세스 패턴에는 날짜를 기반으로 한 조회가 필요하므로 Date는 [글로벌 보조 인덱스(GSI)](GSI.md)의 정렬 키 속성입니다 현재 GSI는 다음과 같습니다.

![\[특정 운영자의 로그를 가져오기 위해 OperatorID와 Date를 파티션 키와 정렬 키로 사용한 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-5-Step3.png)


액세스 패턴 4(`getLogsForOperatorBetweenTwoDates`)의 경우 `2020-04-11T05:58:00`과 `2020-04-24T14:50:00` 사이로 파티션 키 `OperatorID=Liz` 및 정렬 키 `Date`를 사용하여 이 GSI를 쿼리할 수 있습니다.

![\[두 날짜 사이의 운영자 관련 로그를 가져오기 위해 OperatorID 및 Date를 사용한 GSI 쿼리.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-6-GSI1_1.png)


**4단계: 액세스 패턴 5(`getEscalatedLogsForSupervisor`), 6(`getEscalatedLogsWithSpecificStatusForSupervisor`), 7(`getEscalatedLogsWithSpecificStatusForSupervisorForDate`) 처리**

이러한 액세스 패턴을 처리하기 위해 [희소 인덱스](data-modeling-blocks.md#data-modeling-blocks-sparse-index)를 사용합니다.

글로벌 보조 인덱스는 기본적으로 희소 인덱스이므로 인덱스의 프라이머리 키 속성을 포함하는 기본 테이블의 항목만 실제로 인덱스에 표시됩니다. 이는 모델링되는 액세스 패턴과 관련이 없는 항목을 제외하는 또 다른 방법입니다.

예제 데이터와 함께 `EscalatedTo` 속성이 `DeviceStateLog` 테이블에 추가된 [DeviceStateLog\$16.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_6.json)을 가져올 수 있습니다. 앞서 언급했듯이 모든 로그가 감독자에게 에스컬레이션되는 것은 아닙니다.

![\[특정 감독자에게 에스컬레이션된 모든 로그를 가져오기 위해 EscalatedTo 속성을 사용한 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-7-Step4.png)


이제 파티션 키가 `EscalatedTo`이고 정렬 키가 `State#Date`인 새 GSI를 생성할 수 있습니다. `EscalatedTo`와 `State#Date` 속성이 모두 있는 항목만 인덱스에 표시됩니다.

![\[EscalatedTo 및 State#Date 속성을 가진 모든 항목을 가져오기 위한 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-8-Step4.png)


나머지 액세스 패턴은 다음과 같이 요약됩니다.

    액세스 패턴 5(getEscalatedLogsForSupervisor)의 경우 파티션 키 EscalatedTo="Sara"를 사용하여 에스컬레이션 GSI에 대한 쿼리를 수행할 수 있습니다.   액세스 패턴 6(getEscalatedLogsWithSpecificStatusForSupervisor)의 경우 파티션 키 EscalatedTo="Sara" 및 정렬 키 State\$1Date Begins\$1with "WARNING"을 사용하여 에스컬레이션 GSI에 대한 쿼리를 수행할 수 있습니다.   액세스 패턴 7(getEscalatedLogsWithSpecificStatusForSupervisorForDate)의 경우 파티션 키 EscalatedTo="Sara" 및 정렬 키 State\$1Date begins\$1with "WARNING4\$12020-04-27"을 사용하여 에스컬레이션 GSI에 대한 쿼리를 수행할 수 있습니다.    

모든 액세스 패턴과 스키마 설계에서 이를 처리하는 방법이 아래 표에 요약되어 있습니다.


| 액세스 패턴 | 기본 테이블/GSI/LSI | 연산 | 파티션 키 값 | 정렬 키 값 | 기타 조건/필터 | 
| --- | --- | --- | --- | --- | --- | 
| createLogEntryForSpecificDevice | 기본 테이블 | PutItem | DeviceID=deviceId | State\$1Date=state\$1date |  | 
| getLogsForSpecificDevice | 기본 테이블 | 쿼리 | DeviceID=deviceId | State\$1Date begins\$1with "state1\$1" | ScanIndexForward = False | 
| getWarningLogsForSpecificDevice | 기본 테이블 | 쿼리 | DeviceID=deviceId | State\$1Date begins\$1with "WARNING" |  | 
| getLogsForOperatorBetweenTwoDates | GSI-1 | 쿼리 | Operator=operatorName | Date between date1 and date2 |  | 
| getEscalatedLogsForSupervisor | GSI-2 | 쿼리 | EscalatedTo=supervisorName |  |  | 
| getEscalatedLogsWithSpecificStatusForSupervisor | GSI-2 | 쿼리 | EscalatedTo=supervisorName | State\$1Date begins\$1with "state1\$1" |  | 
| getEscalatedLogsWithSpecificStatusForSupervisorForDate | GSI-2 | 쿼리 | EscalatedTo=supervisorName | State\$1Date begins\$1with "state1\$1date1" |  | 

## 최종 스키마
<a name="data-modeling-schema-device-status-final-schema"></a>

다음은 최종 스키마 설계입니다. 이 스키마 설계를 JSON 파일로 다운로드하려면 GitHub의 [DynamoDB 예제](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples)를 참조하세요.

**기본 테이블**

![\[디바이스 ID, 상태, 날짜와 같은 디바이스 상태 메타데이터가 포함된 기본 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-9-Table.png)


**GSI-1**

![\[GSI-1 설계. 프라이머리 키와 속성(DeviceID, State#Date, State)이 표시됩니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-10-GSI1.png)


**GSI-2**

![\[GSI-2 설계. 프라이머리 키와 속성(DeviceID, Operator, Date, State)이 표시됩니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-11-GSI2.png)


## 이 스키마 설계와 함께 NoSQL Workbench 사용
<a name="data-modeling-schema-device-status-nosql"></a>

이 최종 스키마를 DynamoDB 데이터 모델링, 데이터 시각화, 쿼리 개발 기능을 제공하는 시각적 도구인 [NoSQL Workbench](workbench.md)로 가져와서 새 프로젝트를 추가로 탐색하고 편집할 수 있습니다. 시작하려면 다음 단계를 따릅니다.

1. NoSQL Workbench 다운로드 자세한 내용은 [DynamoDB용 NoSQL Workbench 다운로드](workbench.settingup.md) 섹션을 참조하세요.

1. 위에 나열된 JSON 스키마 파일을 다운로드합니다. 이 파일은 이미 NoSQL Workbench 모델 형식으로 되어 있습니다.

1. JSON 스키마 파일을 NoSQL Workbench로 가져옵니다. 자세한 내용은 [기존 데이터 모델 가져오기](workbench.Modeler.ImportExisting.md) 섹션을 참조하세요.

1. NOSQL Workbench로 가져온 후 데이터 모델을 편집할 수 있습니다. 자세한 내용은 [기존 데이터 모델 편집](workbench.Modeler.Edit.md) 섹션을 참조하세요.

# DynamoDB를 온라인 상점용 데이터 스토어로 사용
<a name="data-modeling-online-shop"></a>

이 사용 사례에서는 DynamoDB를 온라인 상점(또는 온라인 매장)의 데이터 스토어로 사용하는 방법을 설명합니다.

## 사용 사례
<a name="data-modeling-schema-online-shop"></a>

온라인 매장을 통해 사용자는 다양한 제품을 둘러보고 최종적으로 구매할 수 있습니다. 생성된 청구서를 기반으로 고객은 할인 코드 또는 기프트 카드를 사용하여 결제한 후 나머지 금액을 신용 카드로 결제할 수 있습니다. 구매한 제품은 여러 창고 중 한 곳에서 픽업되어 제공된 주소로 배송됩니다. 온라인 매장의 일반적인 액세스 패턴은 다음과 같습니다.
+ 지정된 customerId의 고객 가져오기
+ 지정된 productId의 제품 가져오기
+ 지정된 warehouseId의 창고 가져오기
+ productId를 기준으로 모든 창고의 제품 재고 가져오기
+ 지정된 orderId의 주문 가져오기
+ 지정된 orderId의 모든 제품 가져오기
+ 지정된 orderId의 청구서 가져오기
+ 지정된 orderId의 모든 배송물 가져오기
+ 지정된 날짜 범위에서 지정된 productId의 모든 주문 가져오기
+ 지정된 invoiceId의 청구서 가져오기
+ 지정된 invoiceId에 대한 모든 결제 가져오기
+ 지정된 shipmentId의 배송 세부 정보 가져오기
+ 지정된 warehouseId의 모든 배송물 가져오기
+ 지정된 warehouseId의 모든 제품 재고 가져오기
+ 지정된 날짜 범위의 특정 customerId에 대한 모든 청구서 가져오기
+ 지정된 날짜 범위의 특정 customerId가 주문한 모든 제품 가져오기

## 엔터티 관계 다이어그램
<a name="data-modeling-schema-online-shop-erd"></a>

다음은 DynamoDB를 온라인 상점용 데이터 스토어로 모델링하는 데 사용할 엔터티 관계 다이어그램(ERD)입니다.

![\[제품, 주문, 결제, 고객 등의 엔터티를 포함하는 온라인 저장소의 데이터 모델에 대한 ERD.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-1-ERD.png)


## 액세스 패턴
<a name="data-modeling-schema-online-shop-access-patterns"></a>

DynamoDB를 온라인 상점의 데이터 스토어로 사용할 때 고려할 액세스 패턴은 다음과 같습니다.

1. `getCustomerByCustomerId`

1. `getProductByProductId`

1. `getWarehouseByWarehouseId`

1. `getProductInventoryByProductId`

1. `getOrderDetailsByOrderId`

1. `getProductByOrderId`

1. `getInvoiceByOrderId`

1. `getShipmentByOrderId`

1. `getOrderByProductIdForDateRange`

1. `getInvoiceByInvoiceId`

1. `getPaymentByInvoiceId`

1. `getShipmentDetailsByShipmentId`

1. `getShipmentByWarehouseId`

1. `getProductInventoryByWarehouseId`

1. `getInvoiceByCustomerIdForDateRange`

1. `getProductsByCustomerIdForDateRange`

## 스키마 설계 진화
<a name="data-modeling-schema-online-shop-design-evolution"></a>

[DynamoDB용 NoSQL Workbench](workbench.md)를 사용하여 [AnOnlineShop\$11.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_1.json)을 가져와 `AnOnlineShop`이라는 새 데이터 모델과 `OnlineShop`이라는 새 테이블을 생성합니다. 파티션 키와 정렬 키에는 일반 이름 `PK` 및 `SK`를 사용합니다. 이는 동일한 테이블에 다양한 유형의 엔터티를 저장하기 위해 사용되는 방법입니다.

**1단계: 액세스 패턴 1(`getCustomerByCustomerId`) 처리**

[AnOnlineShop\$12.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_2.json)을 가져와 액세스 패턴 1(`getCustomerByCustomerId`)을 처리합니다. 일부 엔터티는 다른 엔터티와 관계가 없으므로 해당 엔터티에 대해 동일한 `PK` 및 `SK` 값을 사용합니다. 예제 데이터에서 키는 나중에 추가될 다른 엔터티와 `customerId`를 구별하기 위해 접두사 `c#`을 사용합니다. 이 방법은 다른 엔티티에 대해서도 반복됩니다.

이 액세스 패턴을 처리하기 위해 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 작업을 `PK=customerId` 및 `SK=customerId`와 함께 사용할 수 있습니다.

**2단계: 액세스 패턴 2(`getProductByProductId`) 처리**

[AnOnlineShop\$13.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_3.json)을 가져와 `product` 엔터티에 대한 액세스 패턴 2(`getProductByProductId`)를 처리합니다. 제품 엔터티에는 접두사 `p#`이 붙고 동일한 정렬 키 속성이 `customerID`와 `productID`를 저장하는 데 사용되었습니다. 일반 이름 지정 및 [수직 파티셔닝](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning)을 통해 효과적인 단일 테이블 설계를 위한 항목 컬렉션을 생성할 수 있습니다.

이 액세스 패턴을 처리하기 위해 `GetItem` 작업을 `PK=productId` 및 `SK=productId`와 함께 사용할 수 있습니다.

**3단계: 액세스 패턴 3(`getWarehouseByWarehouseId`) 처리**

[AnOnlineShop\$14.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_4.json)을 가져와 `warehouse` 엔터티에 대한 액세스 패턴 3(`getWarehouseByWarehouseId`)을 처리합니다. 현재 동일한 테이블에 `customer`, `product` 및 `warehouse` 엔터티가 추가되어 있습니다. 이들은 접두사와 `EntityType` 속성을 사용하여 구별됩니다. 유형 속성(또는 접두사 이름 지정)은 모델의 가독성을 향상시킵니다. 동일한 속성에 서로 다른 엔터티에 대한 영숫자 ID를 저장하면 가독성에 영향을 미칩니다. 이러한 식별자가 없으면 한 엔터티와 다른 엔터티를 구별하기가 어려울 수 있습니다.

이 액세스 패턴을 처리하기 위해 `GetItem` 작업을 `PK=warehouseId` 및 `SK=warehouseId`와 함께 사용할 수 있습니다.

**기본 테이블 **- 

![\[웨어하우스 데이터를 ID별로 가져오기 위한 접두사와 EntityType이 포함된 DynamoDB 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-2-Step3.png)


**4단계: 액세스 패턴 4(`getProductInventoryByProductId`) 처리**

[AnOnlineShop\$15.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_5.json)을 가져와 액세스 패턴 4(`getProductInventoryByProductId`)를 처리합니다. `warehouseItem` 엔터티는 각 창고의 제품 수를 추적하는 데 사용됩니다. 이 항목은 일반적으로 제품이 창고에서 추가되거나 제거될 때 업데이트됩니다. ERD에서 볼 수 있듯이 `product`와 `warehouse` 간에는 다대다 관계가 있습니다. 여기서는 `product`에서 `warehouse`로의 일대다 관계를 `warehouseItem`으로 모델링합니다. 나중에 `warehouse`에서 `product`로의 일대다 관계도 모델링합니다.

액세스 패턴 4는 `PK=ProductId` 및 `SK begins_with “w#“`에 대한 쿼리를 통해 처리될 수 있습니다.

`begins_with()` 및 정렬 키에 적용할 수 있는 기타 표현식에 대한 자세한 내용은 [키 조건 표현식](Query.KeyConditionExpressions.md)을 참조하세요.

**기본 테이블 **- 

![\[해당 웨어하우스의 제품 인벤토리를 추적하기 위해 ProductID 및 WarehouseID를 쿼리하는 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-3-Step4.png)


**5단계: 액세스 패턴 5(`getOrderDetailsByOrderId`) 및 6(`getProductByOrderId`) 처리**

[AnOnlineShop\$16.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_6.json)을 가져와 테이블에 더 많은 `customer`, `product` 및 `warehouse`를 추가합니다. 그런 다음 [AnOnlineShop\$17.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_7.json)을 가져와 액세스 패턴 5(`getOrderDetailsByOrderId`) 및 6(`getProductByOrderId`)을 처리할 수 있도록 `order`에 대한 항목 컬렉션을 생성합니다. orderItem 엔터티로 모델링된 `order`와 `product` 간의 일대다 관계를 볼 수 있습니다.

액세스 패턴 5(`getOrderDetailsByOrderId`)를 처리하기 위해 `PK=orderId`를 사용하여 테이블을 쿼리합니다. 그러면 `customerId` 및 주문한 제품을 포함하여 주문에 대한 모든 정보가 제공됩니다.

**기본 테이블 **- 

![\[주문한 모든 제품에 대한 정보를 가져오기 위해 orderId를 사용하여 쿼리하는 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-4-Step5.png)


액세스 패턴 6(`getProductByOrderId`)을 해결하려면 `order`의 제품만 읽어야 합니다. 이를 위해 `PK=orderId` 및 `SK begins_with “p#”`을 사용하여 테이블을 쿼리합니다.

**기본 테이블 **- 

![\[주문 제품을 가져오기 위해 orderId 및 productId를 사용하여 쿼리하는 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-5-Step5.png)


**6단계: 액세스 패턴 7(`getInvoiceByOrderId`) 처리**

[AnOnlineShop\$18.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_8.json)을 가져와 *주문* 항목 컬렉션에 `invoice` 엔터티를 추가하고 액세스 패턴 7(`getInvoiceByOrderId`)을 처리합니다. 이 액세스 패턴을 처리하기 위해 쿼리 작업을 `PK=orderId` 및 `SK begins_with “i#”`과 함께 사용할 수 있습니다.

**기본 테이블 **- 

![\[orderID별 인보이스를 가져오기 위해 주문 항목 컬렉션에 인보이스 엔터티가 포함된 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-6-Step6.png)


**7단계: 액세스 패턴 8(`getShipmentByOrderId`) 처리**

[AnOnlineShop\$19.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_9.json)을 가져와 *주문* 항목 컬렉션에 `shipment` 엔터티를 추가하여 액세스 패턴 8(`getShipmentByOrderId`)을 처리합니다. 단일 테이블 설계에 더 많은 유형의 엔터티를 추가하여 동일한 수직 파티셔닝 모델을 확장하고 있습니다. *주문* 항목 컬렉션에 포함된 `order` 엔터티가 `shipment`, `orderItem` 및 `invoice` 엔터티와 갖는 다양한 관계를 확인해 보세요.

`orderId`를 기준으로 배송물을 가져오기 위해 `PK=orderId` 및 `SK begins_with “sh#”`을 사용하여 쿼리 작업을 수행할 수 있습니다.

**기본 테이블 **- 

![\[주문 ID별 배송을 가져오기 위해 주문 항목 컬렉션에 배송 엔터티가 추가된 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-7-Step7.png)


**8단계: 액세스 패턴 9(`getOrderByProductIdForDateRange`) 처리**

이전 단계에서 *주문* 항목 컬렉션을 만들었습니다. 이 액세스 패턴에는 전체 테이블을 스캔하고 관련 레코드를 필터링하여 대상 항목을 가져와야 하는 새로운 조회 차원(`ProductID` 및 `Date`)이 있습니다. 이 액세스 패턴을 처리하려면 [글로벌 보조 인덱스(GSI)](GSI.md)를 생성해야 합니다. [AnOnlineShop\$110.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_10.json)을 가져와 여러 *주문* 항목 컬렉션에서 `orderItem` 데이터를 검색할 수 있는 GSI를 사용하여 새 항목 컬렉션을 만듭니다. 이제 데이터에는 각각 `GSI1`의 파티션 키와 정렬 키가 될 `GSI1-PK` 및 `GSI1-SK`가 있습니다.

DynamoDB는 GSI의 주요 속성이 포함된 항목을 테이블에서 GSI에 자동으로 채웁니다. GSI에 추가 삽입을 수동으로 수행할 필요가 없습니다.

액세스 패턴 9를 처리하려면 `GSI1-PK=productId` 및 `GSI1SK between (date1, date2)`를 사용하여 `GSI1`에 대해 쿼리를 수행합니다.

**기본 테이블 **- 

![\[여러 주문 항목 컬렉션에서 주문 데이터를 가져오도록 GSI를 사용한 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-8-Step8-Base.png)


**GSI1:**

![\[제품 ID 및 날짜별 주문을 가져오기 위해 ProductID 및 Date를 파티션 키와 정렬 키로 사용하는 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-9-Step8-GSI.png)


**9단계: 액세스 패턴 10(`getInvoiceByInvoiceId`) 및 11(`getPaymentByInvoiceId`) 처리**

[AnOnlineShop\$111.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_11.json)을 가져와서 `invoice`와 관련된 액세스 패턴 10(`getInvoiceByInvoiceId`) 및 11(`getPaymentByInvoiceId`)을 처리합니다. 이는 서로 다른 두 가지 액세스 패턴이지만 동일한 키 조건을 사용하여 구현됩니다. `Payments`는 `invoice` 엔터티에 대한 맵 데이터 유형의 속성으로 정의됩니다.

**참고**  
`GSI1-PK` 및 `GSI1-SK`는 여러 엔터티에 대한 정보를 저장하도록 오버로드되므로 동일한 GSI에서 여러 액세스 패턴을 제공할 수 있습니다. GSI 오버로딩에 대한 자세한 내용은 [DynamoDB의 글로벌 보조 인덱스 오버로딩](bp-gsi-overloading.md) 섹션을 참조하세요.

액세스 패턴 10 및 11을 처리하려면 `GSI1-PK=invoiceId` 및 `GSI1-SK=invoiceId`를 사용하여 `GSI1`에 대해 쿼리를 수행합니다.

**GSI1:**

![\[인보이스 ID별로 인보이스 및 결제를 가져오기 위해 invoiceID를 파티션 키와 정렬 키로 사용하는 GSI 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-10-Step9.png)


**10단계: 액세스 패턴 12(`getShipmentDetailsByShipmentId`) 및 13(`getShipmentByWarehouseId`) 처리**

[AnOnlineShop\$112.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_12.json)을 가져와 액세스 패턴 12(`getShipmentDetailsByShipmentId`) 및 13(`getShipmentByWarehouseId`)을 처리합니다.

단일 쿼리 작업으로 주문에 대한 모든 세부 정보를 검색할 수 있도록 `shipmentItem` 엔터티가 기본 테이블의 *주문* 항목 컬렉션에 추가됩니다.

**기본 테이블 **- 

![\[모든 주문 세부 정보를 가져오기 위해 주문 항목 컬렉션에 shipmentItem 엔터티가 포함된 테이블 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-11-Step10.png)


`GSI1` 파티션 및 정렬 키는 이미 `shipment`와 `shipmentItem` 간의 일대다 관계를 모델링하는 데 사용되었습니다. 액세스 패턴 12(`getShipmentDetailsByShipmentId`)를 처리하려면 `GSI1-PK=shipmentId` 및 `GSI1-SK=shipmentId`를 사용하여 `GSI1`에 대해 쿼리를 수행합니다.

**GSI1:**

![\[배송 ID별로 배송 세부 정보를 가져오기 위해 shipmentId를 파티션 키 및 정렬 키로 사용하는 GSI1 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-12-Step10-GSI.png)


액세스 패턴 13(`getShipmentByWarehouseId`)에 대해 `warehouse`와 `shipment` 간의 새로운 일대다 관계를 모델링하려면 또 다른 GSI(`GSI2`)를 만들어야 합니다. 이 액세스 패턴을 처리하려면 `GSI2-PK=warehouseId` 및 `GSI2-SK begins_with “sh#”`를 사용하여 `GSI2`에 대해 쿼리를 수행합니다.

**GSI2:**

![\[창고별 배송물을 가져오기 위해 warehouseId 및 shipmentId를 파티션 키와 정렬 키로 사용하는 GSI2 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-13-Step10-GSI2.png)


**11: 액세스 패턴 14(`getProductInventoryByWarehouseId`) 15(`getInvoiceByCustomerIdForDateRange`) 및 16(`getProductsByCustomerIdForDateRange`) 처리**

[AnOnlineShop\$113.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_13.json)을 가져와서 다음 액세스 패턴 세트와 관련된 데이터를 추가합니다. 액세스 패턴 14(`getProductInventoryByWarehouseId`)를 처리하려면 `GSI2-PK=warehouseId` 및 `GSI2-SK begins_with “p#”`를 사용하여 `GSI2`에 대해 쿼리를 수행합니다.

**GSI2:**

![\[액세스 패턴 14를 처리하기 위해 warehouseId 및 productId를 파티션 키와 정렬 키로 사용하는 GSI2 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-14-Step11-GSI2.png)


액세스 패턴 15(`getInvoiceByCustomerIdForDateRange`)를 처리하려면 `GSI2-PK=customerId` 및 `GSI2-SK between (i#date1, i#date2)`를 사용하여 `GSI2`에 대해 쿼리를 수행합니다.

**GSI2:**

![\[액세스 패턴 15를 처리하기 위해 customerId 및 인보이스 날짜 범위를 파티션 키와 정렬 키로 사용하는 GSI2 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-15-Step11-GSI2.png)


액세스 패턴 16(`getProductsByCustomerIdForDateRange`)을 처리하려면 `GSI2-PK=customerId` 및 `GSI2-SK between (p#date1, p#date2)`를 사용하여 `GSI2`에 대해 쿼리를 수행합니다.

**GSI2:**

![\[액세스 패턴 16을 처리하기 위해 customerId 및 제품 날짜 범위를 파티션 키와 정렬 키로 사용하는 GSI2 설계.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-16-Step11-GSI2.png)


**참고**  
[NoSQL Workbench](workbench.md)에서 *패싯*은 애플리케이션의 서로 다른 DynamoDB 데이터 액세스 패턴을 나타냅니다. 패싯을 사용하면 패싯의 제약 조건을 충족하지 않는 레코드를 확인할 필요 없이 테이블에서 데이터의 하위 집합을 볼 수 있습니다. 패싯은 시각적 데이터 모델링 도구로 간주되며 DynamoDB에서 사용 가능한 구성체로 존재하지 않습니다. 패싯은 순전히 액세스 패턴 모델링에 도움이 되기 때문입니다.  
[AnOnlineShop\$1facets.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_facets.json)을 가져와 이 사용 사례의 패싯을 확인합니다.

모든 액세스 패턴과 스키마 설계에서 이를 처리하는 방법이 아래 표에 요약되어 있습니다.


| 액세스 패턴 | 기본 테이블/GSI/LSI | 연산 | 파티션 키 값 | 정렬 키 값 | 
| --- | --- | --- | --- | --- | 
| getCustomerByCustomerId | 기본 테이블 | GetItem |  PK=customerId | SK=customerId | 
| getProductByProductId | 기본 테이블 | GetItem |  PK=productId | SK=productId | 
| getWarehouseByWarehouseId | 기본 테이블 | GetItem |  PK=warehouseId | SK=warehouseId | 
| getProductInventoryByProductId | 기본 테이블 | 쿼리 |  PK=productId | SK begins\$1with "w\$1" | 
| getOrderDetailsByOrderId | 기본 테이블 | 쿼리 |  PK=orderId |  | 
| getProductByOrderId | 기본 테이블 | 쿼리 |  PK=orderId | SK begins\$1with "p\$1" | 
| getInvoiceByOrderId |  기본 테이블 | 쿼리 |  PK=orderId | SK begins\$1with "i\$1" | 
| getShipmentByOrderId |  기본 테이블 | 쿼리 |  PK=orderId | SK begins\$1with "sh\$1" | 
| getOrderByProductIdForDateRange |  GSI1 | 쿼리 |  PK=productId | SK between date1 and date2 | 
| getInvoiceByInvoiceId |  GSI1 | 쿼리 |  PK=invoiceId | SK=invoiceId | 
| getPaymentByInvoiceId |  GSI1 | 쿼리 |  PK=invoiceId | SK=invoiceId | 
| getShipmentDetailsByShipmentId |  GSI1 | 쿼리 |  PK=shipmentId | SK=shipmentId | 
| getShipmentByWarehouseId |  GSI2 | 쿼리 |  PK=warehouseId | SK begins\$1with "sh\$1" | 
| getProductInventoryByWarehouseId |  GSI2 | 쿼리 |  PK=warehouseId | SK begins\$1with "p\$1" | 
| getInvoiceByCustomerIdForDateRange |  GSI2 | 쿼리 |  PK=customerId | SK between i\$1date1 and i\$1date2 | 
| getProductsByCustomerIdForDateRange |  GSI2 | 쿼리 |  PK=customerId | SK between p\$1date1 and p\$1date2 | 

### 온라인 상점 최종 스키마
<a name="data-modeling-schema-online-store-final-schema"></a>

다음은 최종 스키마 설계입니다. 이 스키마 설계를 JSON 파일로 다운로드하려면 GitHub의 [DynamoDB 설계 패턴](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples)을 참조하세요.

**기본 테이블** - 

![\[EntityName 및 Name과 같은 속성이 있는 온라인 쇼핑몰용 기본 테이블의 최종 스키마.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-17-Final-BaseTable.png)


**GSI1**

![\[EntityType과 같은 속성이 있는 온라인 쇼핑몰 기본 테이블의 최종 GSI1 스키마.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-18-Final-GSI1.png)


**GSI2**

![\[EntityType과 같은 속성이 있는 온라인 쇼핑몰 기본 테이블의 최종 GSI2 스키마.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-19-Final-GSI2.png)


## 이 스키마 설계와 함께 NoSQL Workbench 사용
<a name="data-modeling-schema-online-shop-nosql"></a>

이 최종 스키마를 DynamoDB 데이터 모델링, 데이터 시각화, 쿼리 개발 기능을 제공하는 시각적 도구인 [NoSQL Workbench](workbench.md)로 가져와서 새 프로젝트를 추가로 탐색하고 편집할 수 있습니다. 시작하려면 다음 단계를 따릅니다.

1. NoSQL Workbench 다운로드 자세한 내용은 [DynamoDB용 NoSQL Workbench 다운로드](workbench.settingup.md) 섹션을 참조하세요.

1. 위에 나열된 JSON 스키마 파일을 다운로드합니다. 이 파일은 이미 NoSQL Workbench 모델 형식으로 되어 있습니다.

1. JSON 스키마 파일을 NoSQL Workbench로 가져옵니다. 자세한 내용은 [기존 데이터 모델 가져오기](workbench.Modeler.ImportExisting.md) 섹션을 참조하세요.

1. NOSQL Workbench로 가져온 후 데이터 모델을 편집할 수 있습니다. 자세한 내용은 [기존 데이터 모델 편집](workbench.Modeler.Edit.md) 섹션을 참조하세요.

# DynamoDB의 관계형 데이터 모델링 모범 사례
<a name="bp-relational-modeling"></a>

이 섹션에서는 Amazon DynamoDB의 관계형 데이터 모델링 모범 사례를 제공합니다. 먼저 기존 데이터 모델링 개념을 소개합니다. 그런 다음 기존 관계형 데이터베이스 관리 시스템에 비해 DynamoDB를 사용할 때의 이점, 즉 JOIN 작업의 필요성이 없어지고 오버헤드가 줄어드는 것에 대해 설명합니다.

또한 효율적으로 확장되는 DynamoDB 테이블을 설계하는 방법을 설명합니다. 마지막으로 DynamoDB에서 관계형 데이터를 모델링하는 방법에 대한 예를 제공합니다.

**Topics**
+ [기존 관계형 데이터베이스 모델](#SQLtoNoSQL.relational-modeling2)
+ [DynamoDB를 통해 JOIN 작업의 필요성을 없애는 방법](#bp-relational-modeling-joins)
+ [DynamoDB 트랜잭션이 쓰기 프로세스의 오버헤드를 제거하는 방법](#bp-relational-modeling-transactions)
+ [DynamoDB의 관계형 데이터를 모델링하는 첫 번째 단계](bp-modeling-nosql.md)
+ [DynamoDB의 관계형 데이터 모델링에 대한 예](bp-modeling-nosql-B.md)

## 기존 관계형 데이터베이스 모델
<a name="SQLtoNoSQL.relational-modeling2"></a>

기존의 관계형 데이터베이스 관리 시스템(RDBMS)은 데이터를 정규화된 관계형 구조로 저장합니다. 관계형 데이터 모델의 목적은 정규화를 통해 데이터 중복을 줄여 참조 무결성을 지원하고 데이터 이상 현상을 줄이는 것입니다.

다음 스키마는 일반 주문 입력 애플리케이션을 위한 관계형 데이터 모델의 예입니다. 이 애플리케이션은 가상의 제조업체의 운영 및 비즈니스 지원 시스템을 뒷받침하는 HR 스키마를 지원합니다.

![\[RDBMS 스키마의 예\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/RDBMS.png)


비관계형 데이터베이스 서비스인 DynamoDB는 기존의 관계형 데이터베이스 관리 시스템에 비해 많은 이점을 제공합니다.

## DynamoDB를 통해 JOIN 작업의 필요성을 없애는 방법
<a name="bp-relational-modeling-joins"></a>

RDBMS는 구조 쿼리 언어(SQL)를 사용하여 데이터를 애플리케이션에 반환합니다. 데이터 모델의 정규화로 인해 이러한 쿼리에서는 일반적으로 `JOIN` 연산자를 사용하여 하나 이상의 테이블에서 데이터를 결합해야 합니다.

예를 들어, 각 항목을 출하할 수 있는 모든 창고의 재고량을 기준으로 정렬된 구매 주문 항목 목록을 생성하려면, 앞서 스키마에 대해 다음 SQL 쿼리를 발행할 수 있습니다.

```
SELECT * FROM Orders
  INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
  INNER JOIN Products ON Products.Product_ID = Order_Items.Product_ID
  INNER JOIN Inventories ON Products.Product_ID = Inventories.Product_ID
  ORDER BY Quantity_on_Hand DESC
```

이런 유형의 SQL 쿼리는 데이터에 유연하게 액세스 할 수 있는 API를 제공할 수 있지만, 상당히 많은 처리량이 필요합니다. 쿼리의 각 조인은 각 테이블의 데이터를 스테이징한 다음 조합하여 결과 집합을 반환해야 하므로 쿼리의 런타임 복잡성이 증가합니다.

쿼리를 실행하는 데 걸리는 시간에 영향을 줄 수 있는 추가 요소로는 테이블 크기 및 조인되는 열의 인덱스 여부가 있습니다. 앞의 쿼리는 여러 테이블을 대상으로 복잡한 쿼리를 시작한 후, 결과 세트를 분류합니다.

`JOINs`에 대한 필요를 없애는 것이 NoSQL 데이터 모델링의 핵심입니다. 이것이 바로 Amazon.com을 지원하기 위해 DynamoDB를 구축한 이유이자 DynamoDB가 모든 규모에서 일관된 성능을 제공할 수 있는 이유입니다. SQL 쿼리 및 `JOINs`의 런타임 복잡성을 고려할 때 RDBMS 성능은 규모에 따라 일정하지 않으므로 고객 애플리케이션이 확장됨에 따라 성능 문제가 발생합니다.

데이터를 정규화하면 디스크에 저장되는 데이터의 양이 줄어들지만 성능에 영향을 미치는 가장 제한적인 리소스는 CPU 시간과 네트워크 지연 시간인 경우가 많습니다.

DynamoDB는 항목에 대한 단일 요청으로 애플리케이션 쿼리에 완전히 응답하도록 `JOINs`을 제거(및 데이터 비정규화를 촉진)하고 데이터베이스 아키텍처를 최적화하고 두 제약 조건을 모두 최소화하도록 구축되었습니다. 이러한 특성 덕분에 DynamoDB는 어떤 규모에서든 한 자릿수 밀리초 수준의 성능을 제공할 수 있습니다. DynamoDB 작업의 런타임 복잡성은 데이터 크기와 관계없이 일반적인 액세스 패턴에서 일정하기 때문입니다.

## DynamoDB 트랜잭션이 쓰기 프로세스의 오버헤드를 제거하는 방법
<a name="bp-relational-modeling-transactions"></a>

RDBMS를 느리게 할 수 있는 또 다른 요인은 트랜잭션을 사용하여 정규화된 스키마에 쓰는 것입니다. 예시에서 볼 수 있는 것처럼 대부분의 온라인 트랜잭션 처리(OLTP) 애플리케이션이 사용하는 관계형 데이터 구조는 구분한 후 RDBMS에 저장할 때 여러 논리적 테이블로 배포해야 합니다.

즉 ACID를 준수하는 트랜잭션 프레임워크로 애플리케이션이 쓰기 처리 중에 객체 읽기를 시도할 때 발생할 수 있는 교착 상태와 데이터 무결성 문제를 방지해야 합니다. 이러한 트랜잭션 프레임워크는 관계형 스키마와 결합될 때 쓰기 프로세스에 상당한 오버헤드를 추가할 수 있습니다.

DynamoDB에서 트랜잭션을 구현하면 RDBMS에서 발생하는 일반적인 규모 조정 문제를 방지할 수 있습니다. 트랜잭션이 단일 API 호출로 실행되고 해당 단일 트랜잭션에서 액세스할 수 있는 항목 수가 제한되기 때문입니다. 장기 실행 트랜잭션은 트랜잭션이 닫히지 않기 때문에 장기간 또는 영구적으로 데이터 잠금을 유지하여 운영상의 문제를 일으킬 수 있습니다.

DynamoDB에서 이러한 문제를 방지하기 위해 트랜잭션은 두 개의 고유한 API 작업인 `TransactWriteItems` 및 `TransactGetItems`를 사용하여 구현되었습니다. 이러한 API 작업에는 RDBMS에서 흔히 볼 수 있는 시작 및 종료 시맨틱이 없습니다. 또한 DynamoDB는 트랜잭션 내에 100개의 항목 액세스 제한을 두어 장기 실행 트랜잭션을 유사하게 방지합니다. DynamoDB 트랜잭션에 대해 자세히 알아보려면 [트랜잭션 작업](transactions.md)을 참조하세요.

이런 이유로 비즈니스에서 트래픽이 많은 쿼리에 지연 시간이 낮은 응답이 요구되는 경우, NoSQL 시스템을 활용하는 것이 기술적 및 경제적으로 합리적입니다. Amazon DynamoDB는 이를 방지해 관계형 시스템의 확장성을 제한하는 문제를 해결하는 데 도움을 줍니다.

RDBMS의 성능은 일반적으로 다음과 같은 이유로 제대로 확장되지 않습니다.
+ 고가의 조인을 사용해 필요한 쿼리 결과 보기를 재수집해야 합니다.
+ 데이터를 정규화해서, 여러 테이블에 저장을 하는 데, 디스크 쓰기 작업에 여러 쿼리가 요구됩니다.
+ ACID 준수 트랜잭션 시스템의 경우 일반적으로 성능과 관련된 '비용'이 발생합니다.

DynamoDB는 다음 이유 때문에 효과적으로 조정이 됩니다.
+ 스키마 유연성 덕분에 DynamoDB가 복잡한 계층적 데이터를 단일 항목으로 저장할 수 있습니다.
+ 복합 키 설계 덕분에 관련 항목을 동일한 테이블에 함께 가까이 저장할 수 있습니다.
+ 트랜잭션은 한 번의 작업으로 수행되며 장시간 실행되는 작업을 피하기 위해 액세스할 수 있는 항목 수를 100개로 제한합니다.

데이터 스토어에 대한 쿼리가 훨씬 더 간단합니다(아래 형식인 경우가 많음).

```
SELECT * FROM Table_X WHERE Attribute_Y = "somevalue"
```

DynamoDB는 이전 예의 RDBMS보다 요청 데이터 반환이 효과적입니다.

# DynamoDB의 관계형 데이터를 모델링하는 첫 번째 단계
<a name="bp-modeling-nosql"></a>

**참고**  
NoSQL 설계에는 RDBMS 설계와 다른 사고 방식이 요구됩니다. RDBMS의 경우, 액세스 패턴을 생각하지 않고 정규화된 데이터 모델을 생성할 수 있습니다. 그런 후 나중에 새로운 질문과 쿼리에 대한 요구 사항이 생길 때 이를 확장할 수 있습니다. 대조적으로 Amazon DynamoDB의 경우 대답해야 할 질문을 모르기 전까지는 스키마 설계를 시작할 수 없습니다. 사전에 비즈니스 문제와 애플리케이션 사용 사례를 이해해야 합니다.

효율적으로 확장되는 DynamoDB 테이블 설계를 시작하려면, 몇 단계를 거쳐야 하는 데 먼저 지원해야 하는 OSS/BSS(운영 지원 시스템 및 비즈니스 지원 시스템)에서 요구되는 액세스 패턴을 파악해야 합니다.
+ 새 애플리케이션의 경우, 활동과 목표에 대한 사용자 사례를 검토합니다. 파악한 다양한 사용 사례를 문서화하고, 여기에 필요한 액세스 패턴을 분석합니다.
+ 기존 애플리케이션의 경우, 쿼리 로그를 분석해 현재 시스템을 사용하고 있는 사람의 수와 핵심 액세스 패턴을 파악해야 합니다.

이런 프로세스를 끝내면 다음과 같은 형태를 가진 목록이 준비되어야 합니다.


**주문 입력 애플리케이션의 액세스 패턴**  

| 패턴 번호 | 액세스 패턴 설명 | 
| --- | --- | 
| 1 | 직원 ID별로 직원 세부 정보 조회 | 
| 2 | 직원 이름별 직원 세부 정보 쿼리 | 
| 3 | 직원의 전화번호 찾기 | 
| 4 | 고객의 전화번호 찾기 | 
| 5 | 날짜 범위 내에서 고객에 대한 주문 가져오기 | 
| 6 | 날짜 범위 내의 모든 미결 주문 표시 | 
| 7 | 최근 채용된 모든 직원 표시 | 
| 8 | 창고의 모든 직원 찾기 | 
| 9 | 제품 주문 시 모든 항목 가져오기 | 
| 10 | 모든 웨어하우스에서 제품에 대한 인벤토리 가져오기 | 
| 11 | 계정 담당자별로 고객 가져오기 | 
| 12 | 계정 담당자별로 주문 가져오기 | 
| 13 | 직함으로 직원 가져오기 | 
| 14 | 제품 및 창고별로 인벤토리 가져오기 | 
| 15 | 전체 제품 인벤토리 가져오기 | 

실제 사용하는 애플리케이션의 경우, 목록이 훨씬 더 길 것입니다. 그러나 이는 프로덕션 환경에서 찾을 수 있는 다양하고 복잡한 쿼리 패턴을 보여줍니다.

DynamoDB 스키마 설계에 대한 최신 접근 방식은 집계 지향 원칙을 사용하여 엄격한 개체 경계가 아닌 액세스 패턴을 기반으로 데이터를 그룹화합니다. 이 접근 방식은 다음과 같이 여러 설계 패턴을 고려합니다.
+ *단일 테이블 설계* - 복합 정렬 키, 오버로드된 글로벌 보조 인덱스 및 인접 목록 패턴을 사용하여 여러 엔터티 유형을 하나의 테이블에 저장
+ *다중 테이블 설계* - 독립된 운영 특성과 낮은 액세스 상관관계를 가진 엔터티에 대해 별도의 테이블을 교차 엔터티 쿼리에 대한 전략적 GSI와 함께 사용
+ *집계 설계* - 항상 함께 액세스할 때 관련 데이터 임베딩(주문 \$1 OrderItems) 또는 관계 식별을 위한 항목 컬렉션 사용(제품 \$1 인벤토리)

이러한 접근 방식 중에서 선택하는 방법은 특정 액세스 패턴, 데이터 특성 및 운영 요구 사항에 따라 달라집니다. 이런 요소들을 사용해 데이터를 구조화, 애플리케이션이 테이블이나 인덱스에 대한 한 번의 쿼리로 특정 액세스 패턴에 필요한 것을 검색하도록 만들 수 있습니다.

**참고**  
단일 테이블 설계와 다중 테이블 설계 중에서 선택하는 것은 특정 요구 사항에 따라 달라집니다. 단일 테이블 설계는 개체의 액세스 상관관계가 높고 운영 특성이 유사한 경우에 적합합니다. 다중 테이블 설계는 엔터티에 독립적인 운영 요구 사항, 다양한 액세스 패턴이 있거나 명확한 운영 경계가 필요한 경우에 선호됩니다. 이 가이드의 예제는 전략적 집계 및 비정규화를 사용하는 다중 테이블 접근 방식을 보여줍니다.

DynamoDB용 NoSQL Workbench를 사용하여 파티션 키 설계를 시각화하려면 [NoSQL Workbench로 데이터 모델 빌드](workbench.Modeler.md) 섹션을 참조하세요.

# DynamoDB의 관계형 데이터 모델링에 대한 예
<a name="bp-modeling-nosql-B"></a>

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

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

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

## 직원 테이블 설계
<a name="employee-table-design"></a>

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

테이블은 각 직원이 고유한 엔터티이므로 정렬 키 없이 단순 파티션 키(employee\$1id)를 사용합니다. 4개의 GSI를 사용하면 다양한 속성을 기준으로 효율적인 쿼리를 수행할 수 있습니다.
+ *EmployeeByName GSI* - 모든 직원 속성과 함께 INCLUDE 프로젝션을 사용하여 이름으로 전체 직원 세부 정보 검색을 지원하고 employee\$1id를 정렬 키로 사용하여 중복될 수 있는 이름을 처리합니다.
+ *EmployeeByWarehouse GSI* - 필수 속성(이름, job\$1title, hire\$1date)만 있는 INCLUDE 프로젝션을 사용하여 스토리지 비용을 최소화하는 동시에 웨어하우스 기반 쿼리를 지원합니다.
+ *EmployeeByJobTitle GSI* - 보고 및 조직 분석을 위해 INCLUDE 프로젝션을 사용하여 역할 기반 쿼리를 활성화합니다.
+ *EmployeeByHireDate GSI* - 최근 채용과 관련하여 효율적인 날짜 범위 쿼리를 활성화하기 위해 hire\$1date를 정렬 키로 사용하여 정적 파티션 키 값 "EMPLOYEE"를 사용합니다. 직원 추가/업데이트는 일반적으로 1,000WCU 미만이므로 단일 파티션이 핫 파티션 문제 없이 쓰기 로드를 처리할 수 있습니다.


**직원 테이블 - 기본 테이블 구조**  

| employee\$1id(PK) | 이름 | phone\$1number | warehouse\$1id | job\$1title | hire\$1date | entity\$1type | 
| --- | --- | --- | --- | --- | --- | --- | 
| emp\$1001 | John Smith | ["\$11-555-0101"] | wh\$1sea | Manager | 2024-03-15 | 피고용인 | 
| emp\$1002 | Jane Doe | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | 연결 | 2025-01-10 | 피고용인 | 
| emp\$1003 | Bob Wilson | ["\$11-555-0104"] | wh\$1pdx | 연결 | 2025-06-20 | 피고용인 | 
| emp\$1004 | Alice Brown | ["\$11-555-0105"] | wh\$1pdx | Supervisor | 2023-11-05 | 피고용인 | 
| emp\$1005 | Charlie Davis | ["\$11-555-0106"] | wh\$1sea | 연결 | 2025-12-01 | 피고용인 | 


**EmployeeByName GSI - 직원 이름 쿼리 지원**  

| 이름(GSI-PK) | employee\$1id(GSI-SK) | phone\$1number | warehouse\$1id | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | --- | 
| Alice Brown | emp\$1004 | ["\$11-555-0105"] | wh\$1pdx | Supervisor | 2023-11-05 | 
| Bob Wilson | emp\$1003 | ["\$11-555-0104"] | wh\$1pdx | 연결 | 2025-06-20 | 
| Charlie Davis | emp\$1005 | ["\$11-555-0106"] | wh\$1sea | 연결 | 2025-12-01 | 
| Jane Doe | emp\$1002 | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | 연결 | 2025-01-10 | 
| John Smith | emp\$1001 | ["\$11-555-0101"] | wh\$1sea | Manager | 2024-03-15 | 


**EmployeeByWarehouse GSI - 웨어하우스 쿼리 지원**  

| warehouse\$1id(GSI-PK) | employee\$1id(GSI-SK) | 이름 | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | 
| wh\$1pdx | emp\$1003 | Bob Wilson | 연결 | 2025-06-20 | 
| wh\$1pdx | emp\$1004 | Alice Brown | Supervisor | 2023-11-05 | 
| wh\$1sea | emp\$1001 | John Smith | Manager | 2024-03-15 | 
| wh\$1sea | emp\$1002 | Jane Doe | 연결 | 2025-01-10 | 
| wh\$1sea | emp\$1005 | Charlie Davis | 연결 | 2025-12-01 | 


**EmployeeByJobTitle GSI - 직함 쿼리 지원**  

| job\$1title(GSI-PK) | employee\$1id(GSI-SK) | 이름 | warehouse\$1id | hire\$1date | 
| --- | --- | --- | --- | --- | 
| 연결 | emp\$1002 | Jane Doe | wh\$1sea | 2025-01-10 | 
| 연결 | emp\$1003 | Bob Wilson | wh\$1pdx | 2025-06-20 | 
| 연결 | emp\$1005 | Charlie Davis | wh\$1sea | 2025-12-01 | 
| Manager | emp\$1001 | John Smith | wh\$1sea | 2024-03-15 | 
| Supervisor | emp\$1004 | Alice Brown | wh\$1pdx | 2023-11-05 | 


**EmployeeByHireDate GSI - 최근 고용 쿼리 지원**  

| entity\$1type(GSI-PK) | hire\$1date(GSI-SK) | employee\$1id | 이름 | warehouse\$1id | 
| --- | --- | --- | --- | --- | 
| 피고용인 | 2023-11-05 | emp\$1004 | Alice Brown | wh\$1pdx | 
| 피고용인 | 2024-03-15 | emp\$1001 | John Smith | wh\$1sea | 
| 피고용인 | 2025-01-10 | emp\$1002 | Jane Doe | wh\$1sea | 
| 피고용인 | 2025-06-20 | emp\$1003 | Bob Wilson | wh\$1pdx | 
| 피고용인 | 2025-12-01 | emp\$1005 | Charlie Davis | wh\$1sea | 

## 고객 테이블 설계
<a name="customer-table-design"></a>

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

이 표는 DynamoDB의 스키마 유연성을 보여주는 목록 속성을 사용하여 고객당 여러 전화번호를 지원합니다. 단일 GSI는 계정 담당자 워크플로를 활성화합니다.
+ *CustomerByAccountRep GSI* - 전체 고객 레코드 검색 없이 계정 담당자 고객 관리를 지원하기 위해 이름 및 이메일 속성과 함께 INCLUDE 프로젝션 사용


**고객 테이블 - 기본 테이블 구조**  

| customer\$1id(PK) | 이름 | phone\$1number | 이메일 | account\$1rep\$1id | 
| --- | --- | --- | --- | --- | 
| cust\$1001 | Acme Corp | ["\$11-555-1001"] | contact@acme.com | rep\$1001 | 
| cust\$1002 | TechStart Inc | ["\$11-555-1002", "\$11-555-1003"] | info@techstart.com | rep\$1001 | 
| cust\$1003 | 글로벌 거래자 | ["\$11-555-1004"] | sales@globaltraders.com | rep\$1002 | 
| cust\$1004 | BuildRight LLC | ["\$11-555-1005"] | orders@buildright.com | rep\$1002 | 
| cust\$1005 | FastShip Co | ["\$11-555-1006"] | support@fastship.com | rep\$1003 | 


**CustomerByAccountRep GSI - 계정 담당자 쿼리 지원**  

| account\$1rep\$1id(GSI-PK) | customer\$1id(GSI-SK) | 이름 | 이메일 | 
| --- | --- | --- | --- | 
| rep\$1001 | cust\$1001 | Acme Corp | contact@acme.com | 
| rep\$1001 | cust\$1002 | TechStart Inc | info@techstart.com | 
| rep\$1002 | cust\$1003 | 글로벌 거래자 | sales@globaltraders.com | 
| rep\$1002 | cust\$1004 | BuildRight LLC | orders@buildright.com | 
| rep\$1003 | cust\$1005 | FastShip Co | support@fastship.com | 

## 주문 테이블 설계
<a name="order-table-design"></a>

주문 테이블은 주문 헤더 및 주문 항목에 대해 별도의 항목과 함께 수직 파티셔닝을 사용합니다. 이 설계를 사용하면 효율적인 액세스를 위해 모든 주문 구성 요소를 동일한 파티션 내에 유지하면서 효율적인 제품 기반 쿼리를 수행할 수 있습니다. 각 주문은 여러 항목으로 구성됩니다.
+ *주문 헤더* - PK=order\$1id, SK=order\$1id의 주문 메타데이터 포함
+ *주문 항목* - PK=order\$1id, SK=product\$1id가 있는 개별 품목, 직접 제품 쿼리 활성화

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

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

4개의 GSI가 최적화된 프로젝션으로 다양한 쿼리 패턴을 지원합니다.
+ *OrderByCustomerDate GSI* - 주문 요약 및 항목 세부 정보와 함께 INCLUDE 프로젝션을 사용하여 날짜 범위 필터링으로 고객 주문 기록을 지원합니다.
+ *OpenOrdersByDate GSI(Sparse, Sharded)* - 5개의 샤드가 있는 다중 속성 파티션 키(상태 \$1 샤드)를 사용하여 파티션 간에 5,000WPS(초당 쓰기 수)를 분산합니다(각 1,000WPS, DynamoDB의 파티션당 1,000WCU 제한과 일치). OPEN 주문(총 주문의 20%)만 인덱싱하므로 GSI 스토리지 비용을 줄이는 데 도움이 될 수 있습니다. 클라이언트 측 결과 병합을 사용하여 샤드 5개 모두에 병렬 쿼리 필요
+ *OrderByAccountRep GSI* - 주문 요약 속성과 함께 INCLUDE 프로젝션을 사용하여 전체 주문 세부 정보 없이 계정 담당자 워크플로 지원
+ *ProductInOrders GSI* - OrderItem 레코드(PK=order\$1id, SK=product\$1id)에서 생성된 이 GSI를 사용하면 쿼리가 특정 제품이 포함된 모든 주문을 찾을 수 있습니다. 제품 수요 분석을 위해 주문 컨텍스트(customer\$1id, order\$1date, 수량)와 함께 INCLUDE 프로젝션을 사용합니다.


**순서 테이블 - 기본 테이블 구조(수직 분할)**  

| PK | SK | customer\$1id | order\$1date | status | account\$1rep\$1id | quantity | 가격 | 샤드 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| ord\$1001 | ord\$1001 | cust\$1001 | 2025-11-15 | CLOSED | rep\$1001 |  |  |  | 
| ord\$1001 | prod\$1100 |  |  |  |  | 5 | 25.00 |  | 
| ord\$1002 | ord\$1002 | cust\$1001 | 2025-12-20 | OPEN | rep\$1001 |  |  | 0 | 
| ord\$1002 | prod\$1101 |  |  |  |  | 10 | 15.00 |  | 
| ord\$1003 | ord\$1003 | cust\$1002 | 2026-01-05 | OPEN | rep\$1001 |  |  | 2 | 
| ord\$1003 | prod\$1100 |  |  |  |  | 3 | 25.00 |  | 


**OrderByCustomerDate GSI - 고객 주문 쿼리 지원**  

| customer\$1id(GSI-PK) | order\$1date(GSI-SK) | order\$1id | status | total\$1amount | order\$1items | 샤드 | 
| --- | --- | --- | --- | --- | --- | --- | 
| cust\$1001 | 2025-11-15 | ord\$1001 | CLOSED | 225.00 | [\$1product\$1id: "prod\$1100", qty: 5\$1] |  | 
| cust\$1001 | 2025-12-20 | ord\$1002 | OPEN | 150.00 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 0 | 
| cust\$1002 | 2026-01-05 | ord\$1003 | OPEN | 175.00 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 2 | 
| cust\$1003 | 2025-10-10 | ord\$1004 | CLOSED | 250.00 | [\$1product\$1id: "prod\$1101", qty: 5\$1] |  | 
| cust\$1004 | 2026-01-03 | ord\$1005 | OPEN | 200.00 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 1 | 


**OpenOrdersByDate GSI(Sparse, Sharded) - 높은 처리량 미결 주문 쿼리 지원**  

| 상태(GSI-PK-1) | 샤드(GSI-PK-2) | order\$1date(SK) | order\$1id | customer\$1id | account\$1rep\$1id | order\$1items | total\$1amount | 
| --- | --- | --- | --- | --- | --- | --- | --- | 
| OPEN | 0 | 2025-12-20 | ord\$1002 | cust\$1001 | rep\$1001 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 150.00 | 
| OPEN | 1 | 2026-01-03 | ord\$1005 | cust\$1004 | rep\$1002 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 200.00 | 
| OPEN | 2 | 2026-01-05 | ord\$1003 | cust\$1002 | rep\$1001 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 175.00 | 


**OrderByAccountRep GSI - 계정 담당자 주문 쿼리 지원**  

| account\$1rep\$1id(GSI-PK) | order\$1date(GSI-SK) | order\$1id | customer\$1id | status | total\$1amount | 
| --- | --- | --- | --- | --- | --- | 
| rep\$1001 | 2025-11-15 | ord\$1001 | cust\$1001 | CLOSED | 225.00 | 
| rep\$1001 | 2025-12-20 | ord\$1002 | cust\$1001 | OPEN | 150.00 | 
| rep\$1001 | 2026-01-05 | ord\$1003 | cust\$1002 | OPEN | 175.00 | 
| rep\$1002 | 2025-10-10 | ord\$1004 | cust\$1003 | CLOSED | 250.00 | 
| rep\$1002 | 2026-01-03 | ord\$1005 | cust\$1004 | OPEN | 200.00 | 


**ProductInOrders GSI - 제품 주문 쿼리 지원**  

| product\$1id(GSI-PK) | order\$1id(GSI-SK) | customer\$1id | order\$1date | quantity | 
| --- | --- | --- | --- | --- | 
| prod\$1100 | ord\$1001 | cust\$1001 | 2025-11-15 | 5 | 
| prod\$1100 | ord\$1003 | cust\$1002 | 2026-01-05 | 3 | 
| prod\$1101 | ord\$1002 | cust\$1001 | 2025-12-20 | 10 | 

## 제품 테이블 설계
<a name="product-table-design"></a>

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

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


**제품 테이블 - 기본 테이블 구조(항목 수집 패턴)**  

| product\$1id(PK) | warehouse\$1id(SK) | product\$1name | category | unit\$1price | inventory\$1quantity | total\$1inventory | 
| --- | --- | --- | --- | --- | --- | --- | 
| prod\$1100 | prod\$1100 | 위젯 A | 하드웨어 | 25.00 |  | 500 | 
| prod\$1100 | wh\$1sea |  |  |  | 200 |  | 
| prod\$1100 | wh\$1pdx |  |  |  | 150 |  | 
| prod\$1100 | wh\$1atl |  |  |  | 150 |  | 
| prod\$1101 | prod\$1101 | 도구 B | 전자 제품 | 50.00 |  | 300 | 
| prod\$1101 | wh\$1sea |  |  |  | 100 |  | 
| prod\$1101 | wh\$1pdx |  |  |  | 200 |  | 

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

주요 설계 최적화에는 다음이 포함됩니다.
+ *구문 분석 GSI* - OpenOrdersByDate는 OPEN 주문(총 20%)만 인덱싱하므로 GSI 스토리지 비용을 줄이는 데 도움이 될 수 있습니다.
+ *항목 수집 패턴* - 제품 테이블은 PK=product\$1id, SK=warehouse\$1id를 사용하여 인벤토리를 저장하여 별도의 인벤토리 테이블을 제거합니다.
+ *Order \$1 OrderItems 집계* - 100% 액세스 상관 관계로 인해 단일 항목으로 포함됩니다.
+ *전략적 비정규화* - 효율적인 쿼리를 위해 주문 테이블에 account\$1rep\$1id가 중복됩니다.

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


| 일련 번호 | 액세스 패턴 | 쿼리 조건 | 
| --- | --- | --- | 
|  1  |  직원 ID별로 직원 세부 정보 조회  |  직원 테이블: GetItem(employee\$1id="emp\$1001")  | 
|  2  |  직원 이름별 직원 세부 정보 쿼리  |  EmployeeByName GSI: Query(name="John Smith")  | 
|  3  |  직원의 전화번호 찾기  |  직원 테이블: GetItem(employee\$1id="emp\$1001")  | 
|  4  |  고객의 전화번호 찾기  |  고객 테이블: GetItem(customer\$1id="cust\$1001")  | 
|  5  |  날짜 범위 내에서 고객에 대한 주문 가져오기  |  OrderByCustomerDate GSI: Query(customer\$1id="cust\$1001", order\$1date BETWEEN "2025-01-01" AND "2025-12-31")  | 
|  6  |  날짜 범위 내의 모든 미결 주문 표시  |  OpenOrdersByDate GSI: 다중 속성 PK(status="OPEN" \$1 shard=0-4), SK=order\$1date BETWEEN "2025-01-01" AND "2025-12-31"와 함께 5개의 샤드 쿼리, 결과 병합  | 
|  7  |  최근 채용된 모든 직원 표시  |  EmployeeByHireDate GSI: Query(entity\$1type="EMPLOYEE", hire\$1date >= "2025-01-01")  | 
|  8  |  창고의 모든 직원 찾기  |  EmployeeByWarehouse GSI: Query(warehouse\$1id="wh\$1sea")  | 
|  9  |  제품 주문 시 모든 항목 가져오기  |  ProductInOrders GSI: Query(product\$1id="prod\$1100")  | 
|  10  |  모든 웨어하우스에서 제품에 대한 인벤토리 가져오기  |  제품 테이블: Query(product\$1id="prod\$1100")  | 
|  11  |  계정 담당자별로 고객 가져오기  |  CustomerByAccountRep GSI: Query(account\$1rep\$1id="rep\$1001")  | 
|  12  |  계정 담당자별로 주문 가져오기  |  OrderByAccountRep GSI: Query(account\$1rep\$1id="rep\$1001")  | 
|  13  |  직함으로 직원 가져오기  |  EmployeeByJobTitle GSI: Query(job\$1title="Manager")  | 
|  14  |  제품 및 창고별로 인벤토리 가져오기  |  제품 테이블: GetItem(product\$1id="prod\$1100", warehouse\$1id="wh\$1sea")  | 
|  15  |  전체 제품 인벤토리 가져오기  |  제품 테이블: GetItem(product\$1id="prod\$1100", warehouse\$1id="prod\$1100")  | 