

# 로컬 보조 인덱스
<a name="LSI"></a>

일부 애플리케이션은 기본 테이블의 기본 키만 사용하여 데이터를 쿼리하지만, 대체 정렬 키가 유용한 상황이 있습니다. 애플리케이션에서 정렬 키를 선택할 수 있도록 Amazon DynamoDB 테이블에 하나 이상의 로컬 보조 인덱스를 생성하고 이러한 인덱스에 대해 `Query` 또는 `Scan` 요청을 실행할 수 있습니다.

**Topics**
+ [시나리오: 로컬 보조 인덱스 사용](#LSI.Scenario)
+ [속성 프로젝션](#LSI.Projections)
+ [로컬 보조 인덱스 생성](#LSI.Creating)
+ [로컬 보조 인덱스에서 데이터 읽기](#LSI.Reading)
+ [항목 읽기 및 로컬 보조 인덱스](#LSI.Writes)
+ [로컬 보조 인덱스에 대해 프로비저닝된 처리량 고려 사항](#LSI.ThroughputConsiderations)
+ [로컬 보조 인덱스에 대한 스토리지 고려 사항](#LSI.StorageConsiderations)
+ [로컬 보조 인덱스의 항목 컬렉션](#LSI.ItemCollections)
+ [로컬 보조 인덱스로 작업: Java](LSIJavaDocumentAPI.md)
+ [로컬 보조 인덱스로 작업: .NET](LSILowLevelDotNet.md)
+ [DynamoDB AWS CLI의 로컬 보조 인덱스로 작업](LCICli.md)

## 시나리오: 로컬 보조 인덱스 사용
<a name="LSI.Scenario"></a>

예를 들어 `Thread` 테이블을 고려할 수 있습니다. 이 테이블은 [AWS 토론 포럼](https://forums.aws.amazon.com/)과 같은 애플리케이션에 유용합니다. 다음 그림은 테이블에서 항목을 구성하는 방식을 보여 줍니다. (일부 속성만 표시)

![\[포럼 이름, 주제, 마지막 게시물 시간, 댓글 수에 대한 목록이 포함되어 있는 스레드 테이블입니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/LSI_01.png)


DynamoDB는 파티션 키 값이 동일한 모든 항목을 연속적으로 저장합니다. 이 예에서는 특정 `ForumName`에 의해 `Query` 작업으로 해당 포럼의 모든 스레드를 즉시 찾을 수 있습니다. 동일한 파티션 키 값을 가진 항목 그룹 내에서는 항목이 정렬 키 값을 기준으로 정렬됩니다. 정렬 키(`Subject`)가 쿼리에도 제공된 경우 DynamoDB는 반환된 결과를 좁힐 수 있습니다. 예를 들어, "S3" 포럼에서 문자 "a"로 시작하는 `Subject`를 포함한 모든 스레드가 반환됩니다.

일부 요청은 더 복잡한 데이터 액세스 패턴이 필요할 수 있습니다. 예:
+ 보기 및 회신 횟수가 가장 많은 포럼 스레드
+ 특정 포럼에서 메시지 수가 가장 많은 스레드
+ 특정 기간 동안 특정 포럼에 게시된 스레드 수

이러한 질문에 답변하려면 `Query` 작업으로 충분하지 않습니다. 대신, 전체 테이블을 `Scan`해야 합니다. 항목이 수백 개인 테이블은 많은 양의 프로비저닝 읽기 처리량을 소비하며 완료되는 데 오랜 시간이 소요됩니다.

하지만, `Replies` 또는 `LastPostDateTime` 같이 키가 아닌 속성에 하나 이상의 를 지정할 수 있습니다.

*로컬 보조 인덱스*는 지정된 파티션 키 값에 대해 대체 정렬 키를 유지합니다. 또한 로컬 보조 인덱스에는 기본 테이블에서 나온 일부 또는 모든 속성의 복사본이 포함되어 있습니다. 테이블을 생성할 때 로컬 보조 인덱스로 프로젝션되는 속성을 지정할 수 있습니다. 로컬 보조 인덱스의 데이터는 기본 테이블과 동일한 파티션 키를 기준으로 구성되지만, 다른 정렬 키를 사용합니다. 따라서 이와 같이 다른 차원에서 데이터 항목을 효율적으로 액세스할 수 있습니다. 쿼리 또는 스캔 유연성을 높이려면 테이블당 최대 5개의 로컬 보조 인덱스를 만들 수 있습니다.

애플리케이션이 특정 포럼에서 최근 3개월간 게시된 모든 스레드를 찾아야 하는 경우를 가정하겠습니다. 로컬 보조 인덱스가 없을 경우 애플리케이션은 전체 `Thread` 테이블을 `Scan`하고 지정된 기간에 속하지 않는 게시물을 무시해야 합니다. 로컬 보조 인덱스가 있으면 `Query` 작업에서 정렬 키로 `LastPostDateTime`을 사용하여 데이터를 빠르게 찾을 수 있습니다.

다음 다이어그램은 `LastPostIndex`라는 로컬 보조 인덱스를 보여 줍니다.. 파티션 키는 `Thread` 테이블과 동일하지만 정렬 키는 `LastPostDateTime`입니다.

![\[포럼 이름, 주제 및 마지막 게시물 시간에 대한 목록이 포함되어 있는 LastPostIndex 테이블입니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/LSI_02.png)


모든 로컬 보조 인덱스는 다음 조건을 충족해야 합니다.
+ 파티션 키는 기본 테이블의 키와 동일합니다.
+ 정렬 키는 정확히 하나의 스칼라 속성으로 구성됩니다.
+ 기본 테이블의 정렬 키가 인덱스로 프로젝션되며, 이 인덱스는 키가 아닌 속성으로 작동합니다.

이 예제에서는 파티션 키가 `ForumName`이고 로컬 보조 인덱스의 정렬 키는 `LastPostDateTime`입니다. 또한 기본 테이블의 정렬 키 값(이 예제의 경우 `Subject`)이 인덱스로 프로젝션되지만 인덱스 키의 부분이 아닙니다. 애플리케이션에 `ForumName` 및 `LastPostDateTime`을 기준으로 하는 목록이 필요할 경우 `Query`에 `LastPostIndex` 요청을 실행합니다. 쿼리 결과가 `LastPostDateTime` 기준으로 정렬되고 오름차순 또는 내림차순으로 반환할 수 있습니다. 또한 쿼리는 특정 기간 내 `LastPostDateTime`이 있는 항목만 반환하는 등의 키 조건을 적용할 수 있습니다.

모든 로컬 보조 인덱스에는 기본 테이블의 파티션 및 정렬 키가 자동적으로 포함되며, 키가 아닌 속성을 인덱스로 프로젝션할 수 있습니다(선택 사항). 인덱스를 쿼리할 경우 DynamoDB에서 이와 같이 프로젝션된 속성을 효율적으로 가져올 수 있습니다. 로컬 보조 인덱스를 쿼리하는 경우 쿼리에서는 인덱스로 프로젝션되지 *않은* 속성도 검색할 수 있습니다. DynamoDB는 기본 테이블에서 이러한 속성을 자동으로 가져오지만, 지연 시간이 길어지고 프로비저닝된 처리량 비용이 높아집니다.

모든 로컬 보조 인덱스에서 개별 파티션 키 값마다 최대 10GB의 데이터를 저장할 수 있습니다. 이 수치에는 기본 테이블과 인덱스에서 동일한 파티션 키 값을 가진 모든 항목이 포함됩니다. 자세한 내용은 [로컬 보조 인덱스의 항목 컬렉션](#LSI.ItemCollections) 섹션을 참조하세요.

## 속성 프로젝션
<a name="LSI.Projections"></a>

`LastPostIndex`에서 애플리케이션은 `ForumName`와 `LastPostDateTime`을 쿼리 기준으로 사용할 수 있었습니다. 그러나 추가 속성을 검색하려면, DynamoDB는 `Thread` 테이블에 대해 추가 읽기 작업을 수행해야 합니다. 이러한 추가 읽기를 *가져오기(fetch)*라고 하며 쿼리에 필요한 총 할당 처리량이 증가할 수 있습니다.

웹 페이지를 "S3"의 모든 스레드와 각 스레드에 대한 댓글 수 목록으로 채우고, 최근 댓글부터 시작하여 마지막 댓글 날짜/시간을 기준으로 정렬하려는 경우를 가정해 보세요. 이 목록을 채우려면 다음과 같은 속성이 필요합니다.
+ `Subject`
+ `Replies`
+ `LastPostDateTime`

이 데이터를 쿼리하고 가져오기 작업을 회피하는 가장 효율적인 방법은 아래 그림과 같이 `Replies` 속성을 테이블에서 로컬 보조 인덱스로 프로젝션하는 것입니다.

![\[포럼 이름, 마지막 게시물 시간, 주제 및 답변에 대한 목록이 포함되어 있는 LastPostIndex 테이블입니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/LSI_03.png)




*프로젝션*은 테이블에서 보조 인덱스로 복사되는 속성 집합입니다. 테이블의 파티션 키와 정렬 키는 항상 인덱스로 프로젝션되지만, 다른 속성을 프로젝션하여 애플리케이션의 쿼리 요건을 지원하는 것도 가능합니다. 따라서 인덱스에 쿼리를 실행할 때는 마치 속성이 자체 테이블에 저장되어 있는 것처럼 Amazon DynamoDB가 프로젝션의 모든 속성에 액세스할 수 있습니다.

보조 인덱스를 생성할 때 인덱스에 프로젝션될 속성을 지정해야 합니다. DynamoDB는 이를 위해 다음과 같은 세 가지 옵션을 제공합니다.
+ *KEYS\$1ONLY* - 인덱스의 각 항목은 테이블 파티션 키 및 정렬 키 값, 그리고 인덱스 키 값으로만 구성됩니다. `KEYS_ONLY` 옵션은 보조 인덱스의 크기를 최소화합니다.
+ *INCLUDE* – `KEYS_ONLY`에서 설명한 속성 외에도 지정하는 키가 아닌 다른 속성이 보조 인덱스에 포함됩니다.
+ *ALL* – 보조 인덱스에 소스 테이블의 모든 속성이 포함됩니다. 모든 테이블 데이터가 인덱스에 복제되므로 `ALL` 프로젝션은 보조 인덱스의 크기를 최대화합니다.

이전 다이어그램에서는 키가 아닌 `Replies` 속성이 `LastPostIndex`로 프로젝션됩니다. 애플리케이션은 전체 `LastPostIndex` 테이블 대신에 `Thread`을 쿼리하여 `Subject`, `Replies` 및 `LastPostDateTime`로 웹 페이지를 채울 수 있습니다. 키가 아닌 다른 속성을 요청할 경우 DynamoDB는 `Thread` 테이블에서 이러한 속성을 가져와야 합니다.

애플리케이션의 관점에서, 기본 테이블에서 추가 속성을 가져오는 작업은 자동으로 수행되므로 애플리케이션 로직을 다시 쓸 필요는 없습니다. 하지만, 이와 같은 가져오기 작업을 실행할 경우 로컬 보조 인덱스를 이용하는 성능 상의 이점이 크게 감소할 수 있습니다.

속성을 로컬 보조 인덱스로 프로젝션할 경우에는 프로비저닝된 처리량 비용과 스토리지 비용 간 균형을 고려해야 합니다.
+ 대기 시간이 가장 낮은 몇 개의 속성만 액세스해야 할 경우 해당 속성만 로컬 보조 인덱스로 프로젝션하는 방법을 고려해 볼 수 있습니다. 인덱스가 작을수록 스토리지 비용과 쓰기 비용이 절감됩니다. 간혹 가져와야 하는 속성이 있을 경우 할당 처리량의 비용이 이러한 속성의 장기 저장 비용보다 크게 증가할 수 있습니다.
+ 애플리케이션이 키가 아닌 일부 속성에 빈번하게 액세스할 경우 해당 속성을 로컬 보조 인덱스로 프로젝션하는 방법을 고려해야 합니다. 로컬 보조 인덱스의 추가 스토리지 비용이 빈번한 테이블 스캔 수행으로 발생하는 비용보다 경제적입니다.
+ 키가 아닌 속성 대부분에 자주 액세스해야 하는 경우 해당 속성이나 심지어 전체 기본 테이블을 글로벌 보조 인덱스로 프로젝션할 수 있습니다. 그러면 가져오기가 필요하지 않으므로 유연성이 극대화되고 프로비저닝된 처리량 소비가 최소화됩니다. 하지만, 모든 속성을 프로젝션하면 스토리지 비용이 최대 2배까지 증가할 수 있습니다.
+ 애플리케이션이 테이블에 빈번하게 쿼리하지 않지만 테이블 데이터에 대해 많은 쓰기 또는 업데이트 작업을 수행해야 할 경우 *KEYS\$1ONLY*를 프로젝션할 수 있습니다. 이 경우 로컬 보조 인덱스는 크기는 최소화되지만 쿼리 작업에 필요할 경우 계속 사용할 수 있습니다.

## 로컬 보조 인덱스 생성
<a name="LSI.Creating"></a>

테이블에서 하나 이상의 로컬 보조 인덱스를 생성하려면 `CreateTable` 작업의 `LocalSecondaryIndexes` 파라미터를 사용합니다. 테이블이 생성되면 테이블에 로컬 보조 인덱스가 생성됩니다. 테이블을 삭제할 경우 해당 테이블의 로컬 보조 인덱스도 삭제됩니다.

키가 아닌 하나의 속성을 로컬 보조 인덱스의 정렬 키로 지정해야 합니다. 선택한 속성은 스칼라 `String`, `Number` 또는 `Binary`여야 합니다. 다른 스칼라 유형, 문서 유형 및 집합 유형은 허용되지 않습니다. 데이터 형식에 대한 전체 목록은 [데이터 타입](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes) 단원을 참조하세요.

**중요**  
로컬 보조 인덱스가 있는 테이블의 경우 파티션 키 값당 10GB 크기 한도가 적용됩니다. 하나의 파티션 키 값의 총 크기가 10GB를 초과하지 않는 이상 로컬 보조 인텍스가 있는 테이블은 모든 수의 항목을 저장할 수 있습니다. 자세한 내용은 [항목 컬렉션 크기 제한](#LSI.ItemCollections.SizeLimit) 섹션을 참조하세요.

모든 데이터 형식의 속성을 로컬 보조 인덱스로 프로젝션할 수 있습니다. 여기에는 스칼라, 문서, 집합이 포함됩니다. 데이터 형식에 대한 전체 목록은 [데이터 타입](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes) 단원을 참조하세요.

## 로컬 보조 인덱스에서 데이터 읽기
<a name="LSI.Reading"></a>

`Query` 및 `Scan` 작업을 사용하여 로컬 보조 인덱스에서 항목을 가져올 수 있습니다. `GetItem` 및 `BatchGetItem` 작업은 로컬 보조 인덱스에 사용할 수 없습니다.

### 로컬 보조 인덱스 쿼리
<a name="LSI.Querying"></a>

DynamoDB 테이블에서 각 항목에 대한 파티션 키 값과 정렬 키 값의 조합은 고유해야 합니다. 하지만, 로컬 보조 인덱스에서 정렬 키 값은 특정 파티션 키 값에 대해 고유하지 않아도 됩니다. 로컬 보조 인덱스에 정렬 키 값이 같은 여러 항목이 있을 경우 `Query` 작업은 파티션 키 값이 같은 항목을 모두 반환합니다. 응답 시 일치하는 항목이 특정 순서로 반환되지 않습니다.

최종적으로 일관된 읽기 또는 강력히 일관된 읽기를 사용하여 로컬 보조 인덱스를 쿼리할 수 있습니다. 원하는 유형의 일관성을 지정하려면 `ConsistentRead` 작업의 `Query` 파라미터를 사용합니다. 로컬 보조 인덱스에서 강력히 일관된 읽기는 항상 업데이트된 최신 값을 반환합니다. 쿼리가 기본 테이블에서 추가 속성을 가져와야 하는 경우 해당 속성은 인덱스와 일관성을 유지합니다.

**Example**  
특정 포럼의 토론 스레드에서 데이터를 요청하는 `Query`에서 다음과 같은 데이터가 반환된 경우를 가정해 보세요.  

```
{
    "TableName": "Thread",
    "IndexName": "LastPostIndex",
    "ConsistentRead": false,
    "ProjectionExpression": "Subject, LastPostDateTime, Replies, Tags",
    "KeyConditionExpression": 
        "ForumName = :v_forum and LastPostDateTime between :v_start and :v_end",
    "ExpressionAttributeValues": {
        ":v_start": {"S": "2015-08-31T00:00:00.000Z"},
        ":v_end": {"S": "2015-11-31T00:00:00.000Z"},
        ":v_forum": {"S": "EC2"}
    }
}
```
이 쿼리에서  
+ DynamoDB는 `LastPostIndex`에 액세스하고 `ForumName` 파티션 키를 사용하여 "EC2"의 인덱스 항목을 찾습니다. 이 키가 있는 모든 인덱스 항목은 빠른 검색을 위해 인접한 상태로 저장됩니다.
+ 이 포럼 내에서 DynamoDB는 인덱스를 사용하여 지정된 `LastPostDateTime` 조건과 일치하는 키를 조회합니다.
+ `Replies` 속성은 인덱스로 프로젝션되므로 DynamoDB는 프로비저닝된 처리량을 추가로 소비하지 않고 이 속성을 가져올 수 있습니다.
+ `Tags` 속성은 인덱스로 프로젝션되지 않으므로 DynamoDB는 `Thread` 테이블에 액세스하여 이 속성을 가져와야 합니다.
+ 결과가 반환되고, `LastPostDateTime`을 기준으로 정렬됩니다. 인덱스 항목이 파티션 키 값을 기준으로 정렬된 다음 정렬 키를 기준으로 정렬되고, `Query`는 저장된 순서로 인덱스 항목을 반환합니다. `ScanIndexForward` 파라미터를 사용하여 결과를 내림차순으로 정렬할 수 있습니다.
`Tags` 속성은 로컬 보조 인덱스로 프로젝션되지 않으므로 DynamoDB는 추가 읽기 용량 단위를 사용하여 이 속성을 기본 테이블에서 가져와야 합니다. 이 쿼리를 자주 실행해야 하는 경우에는 기본 테이블에서 가져오기가 되지 않도록 `Tags`를 `LastPostIndex`로 프로젝션해야 합니다. 그러나 가끔씩만 `Tags`에 액세스해야 하는 경우에는 인덱스에 `Tags`을 프로젝션하기 위한 추가 스토리지 비용을 지불할 가치가 없을 것입니다.

### 로컬 보조 인덱스 스캔
<a name="LSI.Scanning"></a>

`Scan`을 사용하여 로컬 보조 인덱스에서 모든 데이터를 검색할 수 있습니다. 요청에 기본 테이블 이름과 인덱스 이름을 제공해야 합니다. DynamoDB는 `Scan`을 사용하여 인덱스에 있는 모든 데이터를 읽고 애플리케이션에 반환합니다. 또한 일부 데이터만 반환하고 나머지 데이터를 무시하도록 요청할 수 있습니다. 이와 같이 하려면 `FilterExpression` API의 `Scan` 파라미터를 사용합니다. 자세한 내용은 [스캔에 대한 필터 표현식](Scan.md#Scan.FilterExpression) 섹션을 참조하세요.

## 항목 읽기 및 로컬 보조 인덱스
<a name="LSI.Writes"></a>

DynamoDB는 자동으로 모든 로컬 보조 인덱스를 관련 기본 테이블과 동기화 상태로 유지합니다. 애플리케이션이 인덱스에 직접 쓰기를 수행하는 경우는 없습니다. 하지만, DynamoDB가 이러한 인덱스를 유지하는 방식이 어떤 영향을 미치는지 이해하는 것이 중요합니다.

로컬 보조 인덱스를 생성할 때 인덱스의 정렬 키로 사용할 속성을 지정해야 합니다. 해당 속성의 데이터 형식을 지정합니다. 다시 말해, 기본 테이블에 항목을 쓸 때마다 항목이 인덱스 키 속성을 정의할 경우 해당 형식이 인덱스 키 스키마의 데이터 형식과 일치해야 함을 의미합니다. `LastPostIndex`의 경우, 인덱스의 `LastPostDateTime` 정렬 키가 `String` 데이터 유형으로 정의됩니다. `Thread` 테이블에 항목을 추가하고 `LastPostDateTime`(예: `Number`)에 다른 데이터 형식을 지정할 경우 DynamoDB에서 데이터 형식 불일치로 인해 `ValidationException`을 반환합니다.

기본 테이블의 항목과 로컬 보조 인덱스의 항목이 일대일 관계를 가질 필요는 없습니다. 하지만 실제로 이러한 동작은 많은 애플리케이션에서 유리할 수 있습니다.

로컬 보조 인덱스가 많은 테이블은 인덱스가 적은 테이블보다 쓰기 작업에 더 높은 비용이 발생합니다. 자세한 내용은 [로컬 보조 인덱스에 대해 프로비저닝된 처리량 고려 사항](#LSI.ThroughputConsiderations) 섹션을 참조하세요.

**중요**  
로컬 보조 인덱스가 있는 테이블의 경우 파티션 키 값당 10GB 크기 한도가 적용됩니다. 하나의 파티션 키 값의 총 크기가 10GB를 초과하지 않는 이상 로컬 보조 인텍스가 있는 테이블은 모든 수의 항목을 저장할 수 있습니다. 자세한 내용은 [항목 컬렉션 크기 제한](#LSI.ItemCollections.SizeLimit) 섹션을 참조하세요.

## 로컬 보조 인덱스에 대해 프로비저닝된 처리량 고려 사항
<a name="LSI.ThroughputConsiderations"></a>

DynamoDB에서 테이블을 생성할 때는 해당 테이블의 예상 워크로드를 위한 읽기 및 쓰기 용량 단위를 프로비저닝해야 합니다. 이러한 워크로드에는 테이블의 로컬 보조 인덱스에 대한 읽기 및 쓰기 작업이 포함됩니다.

프로비저닝된 처리 용량에 대한 현재 요율을 보려면 [Amazon DynamoDB 요금](https://aws.amazon.com/dynamodb/pricing)을 참조하세요.

### 읽기 용량 단위
<a name="LSI.ThroughputConsiderations.Reads"></a>

로컬 보조 인덱스를 쿼리할 경우 소비되는 읽기 용량 단위의 수는 데이터가 액세스되는 방식에 따라 달라집니다.

테이블 쿼리와 마찬가지로, 인덱스 쿼리는 `ConsistentRead`값에 따라 최종적으로 일관된 읽기 또는 강력한 일관된 읽기를 사용할 수 있습니다. 하나의 강력한 일관된 읽기(Strongly Consistent Read)는 하나의 읽기 용량 단위를 소비하고 하나의 최종적 일관된 읽기(Eventually Consistent Read)는 그 절반만 소비합니다. 따라서, 최종적 일관된 읽기(Eventually Consistent Read)를 선택할 경우 읽기 용량 단위 요금을 줄일 수 있습니다.

인덱스 키 및 프로젝션된 속성만 요구하는 인덱스 쿼리의 경우 DynamoDB는 테이블에 대한 쿼리와 같은 방식으로 프로비저닝된 읽기 작업을 계산합니다. 유일한 차이는 기본 테이블 항목의 크기가 아닌 인덱스 항목의 크기를 기준으로 계산이 이루어진다는 점입니다. 읽기 용량 단위의 수는 반환된 모든 항목에서 프로젝션된 모든 속성 크기의 합계입니다. 그런 다음 결과가 다음 4KB 경계로 반올림됩니다. DynamoDB에서 프로비저닝된 처리량을 계산하는 방법에 대한 자세한 내용은 [DynamoDB 프로비저닝된 용량 모드](provisioned-capacity-mode.md) 단원을 참조하세요.

로컬 보조 인덱스로 프로젝션되지 않은 속성을 읽는 인덱스 쿼리의 경우 DynamoDB는 인덱스에서 프로젝션된 속성을 읽을 뿐만 아니라 기본 테이블에서 이러한 속성을 가져와야 합니다. 이와 같은 가져오기 작업은 `Select` 작업의 `ProjectionExpression` 또는 `Query` 파라미터에 프로젝션되지 않은 속성을 포함할 때 발생합니다. 가져오기를 수행하면 쿼리 응답이 더욱 지연되며 프로비저닝된 처리량 비용도 증가합니다. 위에서 설명한 로컬 보조 인덱스에서 읽기 이외에 가져온 모든 기본 테이블 항목의 읽기 용량 단위에도 요금이 청구됩니다. 이 요금은 요청된 속성뿐만 아니라 테이블에서 각각의 항목 전체를 읽는 데 부과되는 것입니다.

`Query` 작업에서 반환된 결과의 최대 크기는 1MB입니다. 여기에는 반환된 모든 항목의 속성 이름 및 값의 크기가 포함됩니다. 하지만 로컬 보조 인덱스에 대한 쿼리로 DynamoDB가 기본 테이블에서 항목 속성을 가져올 경우 결과에 있는 데이터의 최대 크기는 더 작을 수 있습니다. 이 경우 결과 크기는 다음의 합계입니다.
+ 인덱스에서 일치하는 항목의 크기(다음 4KB로 반올림됨)
+ 기본 테이블에서 일치하는 각 항목의 크기(각 항목이 개별적으로 다음 4KB로 반올림됨)

이 수식을 사용하면 쿼리 작업에서 반환한 결과의 최대 크기는 계속 1MB입니다.

예를 들어 각 항목의 크기가 300바이트인 테이블을 가정해 보겠습니다. 이 테이블에 로컬 보조 인덱스가 있지만 각 항목에서 200바이트만 인덱스로 프로젝션되었습니다. 이제 이 인덱스를 `Query`할 때 쿼리에서 각 항목의 테이블을 가져와야 하고 쿼리에서 4개 항목을 반환한다고 가정합니다. DynamoDB는 다음과 같이 합계를 계산합니다.
+ 인덱스에서 일치하는 항목의 크기는 200바이트 × 4개 항목 = 800바이트이며, 이 값은 4KB로 반올림됩니다.
+ 기본 테이블에서 일치하는 각 항목의 크기는 300바이트(4KB로 반올림됨) × 4개 항목 = 16KB입니다.

따라서 결과에 포함된 데이터의 총 크기는 20KB입니다.

### 쓰기 용량 단위
<a name="LSI.ThroughputConsiderations.Writes"></a>

테이블에 항목을 추가, 업데이트 또는 삭제하고 로컬 보조 인덱스를 업데이트할 경우 테이블의 할당 쓰기 용량 단위가 소비됩니다. 쓰기에 프로비저닝된 총 처리량 비용은 테이블에 쓰기 작업으로 소비된 쓰기 용량 단위와 로컬 보조 인덱스를 업데이트하여 소비된 쓰기 용량 단위의 합계입니다.

로컬 보조 인덱스에 항목을 쓰는 비용은 몇 가지 요인에 따라 달라집니다.
+ 인덱싱된 속성을 정의하는 테이블에 새 항목을 쓰거나 이전에 정의되지 않은 인덱싱된 속성을 정의하도록 기존 항목을 업데이트하는 경우 항목을 인덱스에 넣으려면 1번의 쓰기 작업이 필요합니다.
+ 테이블을 업데이트하여 인덱스 키 속성 값이 A에서 B로 변경될 경우 두 번의 쓰기, 즉, 인덱스에서 이전 항목을 삭제하는 쓰기와 새 항목을 인덱스에 추가하는 쓰기가 필요합니다.  
+ 항목이 인덱스에 있지만 테이블 쓰기로 인해 인덱스 속성이 삭제된 경우 인덱스에서 기존 항목 프로젝션을 삭제하는 한 번의 쓰기가 필요합니다.
+ 항목이 업데이트되기 전후에 인덱스에 항목이 없을 경우 인덱스에 추가 쓰기 비용이 발생하지 않습니다.

이러한 모든 요인은 인덱스에 있는 각 항목의 크기가 쓰기 용량 단위를 계산하기 위한 1KB 항목 크기보다 작거나 같은 경우를 가정합니다. 이보다 큰 인덱스 항목에서는 추가 쓰기 용량 단위가 필요합니다. 쿼리에서 어떤 속성을 반환해야 하는지 고려하고 해당 속성만 인덱스에 프로젝션함으로써 쓰기 비용을 최소화할 수 있습니다.

## 로컬 보조 인덱스에 대한 스토리지 고려 사항
<a name="LSI.StorageConsiderations"></a>

애플리케이션에서 테이블에 항목을 쓰는 경우 DynamoDB는 해당 속성이 표시되어야 하는 모든 로컬 보조 인덱스에 올바른 속성 하위 집합을 자동으로 복사합니다. AWS 계정에는 기본 테이블의 항목 스토리지 비용 및 해당 테이블에 있는 로컬 보조 인덱스의 속성 스토리지 비용이 청구됩니다.

인덱스 항목에서 사용하는 공간의 양은 다음의 합계입니다.
+ 기본 테이블 기본 키(파티션 및 정렬 키)의 크기(바이트)
+ 인덱스 키 속성의 크기(바이트)
+ 프로젝션된 속성(있는 경우)의 크기(바이트)
+ 인덱스 항목당 오버헤드의 100바이트

로컬 보조 인덱스의 스토리지 요구 사항을 추정하려면 인덱스의 평균 항목 크기를 추정한 다음 인덱스에 있는 항목의 수를 곱합니다.

테이블에 특정 속성이 정의되지 않은 항목이 포함되어 있지만 해당 속성이 인덱스 정렬 키로 정의된 경우 DynamoDB에서 해당 항목의 데이터를 인덱스에 쓰지 않습니다.

## 로컬 보조 인덱스의 항목 컬렉션
<a name="LSI.ItemCollections"></a>

**참고**  
다음 단원은 로컬 보조 인덱스가 있는 테이블에만 적용됩니다.

DynamoDB에서 *항목 컬렉션*은 테이블에서 동일한 파티션 키 값과 해당하는 모든 로컬 보조 인덱스가 있는 항목의 그룹입니다. 이 단원 전반에 사용된 예제에서 `Thread` 테이블의 파티션 키는 `ForumName`이며 `LastPostIndex`의 파티션 키도 `ForumName`입니다. 동일한 `ForumName`을 가진 모든 테이블과 인덱스 항목은 동일한 항목 컬렉션에 포함됩니다. 예를 들어 `Thread` 테이블과 `LastPostIndex` 로컬 보조 인덱스에는 포럼 `EC2`에 대한 한 가지 항목 컬렉션과 포럼 `RDS`에 대한 다른 항목 컬렉션이 있습니다.

다음 그림은 포럼 `S3`의 항목 컬렉션을 보여 줍니다.

![\[파티션 키 값 S3가 동일한 테이블 및 로컬 보조 인덱스 항목이 포함된 DynamoDB 항목 모음입니다.\]](http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/images/LSI_04.png)


이 그림에서 항목 컬렉션은 `Thread` 및 `LastPostIndex`의 모든 항목으로 구성되어 있으며, 여기에서 `ForumName` 파티션 키 값은 "S3"입니다. 테이블에 다른 로컬 보조 인덱스가 있을 경우 해당 인덱스에서 `ForumName`이 "S3"인 모든 항목도 항목 컬렉션에 포함됩니다.

DynamoDB에서 다음 작업 중 하나를 사용하여 항목 컬렉션에 대한 정보를 반환할 수 있습니다.
+ `BatchWriteItem`
+ `DeleteItem`
+ `PutItem`
+ `UpdateItem`
+ `TransactWriteItems`

각 작업은 `ReturnItemCollectionMetrics` 파라미터를 지원합니다. 이 파라미터를 `SIZE`로 설정할 경우 인덱스에 있는 각 항목 컬렉션의 크기에 대한 정보를 볼 수 있습니다.

**Example**  
다음은`UpdateItem`를 `Thread`로 설정한 상태에서 `ReturnItemCollectionMetrics` 테이블에서 `SIZE` 작업을 실행하는 출력의 예제입니다. 업데이트된 항목에 `ForumName` 값 "EC2"가 있으므로 출력에 항목 컬렉션에 대한 정보가 포함됩니다.  

```
{
    ItemCollectionMetrics: {
        ItemCollectionKey: {
            ForumName: "EC2"
        },
        SizeEstimateRangeGB: [0.0, 1.0]
    }
}
```
`SizeEstimateRangeGB` 객체는 이 항목 컬렉션의 크기가 0\$11GB임을 나타냅니다. DynamoDB는 이 크기 추정 값을 주기적으로 업데이트하므로 다음에 항목이 수정되면 숫자가 달라질 수 있습니다.

### 항목 컬렉션 크기 제한
<a name="LSI.ItemCollections.SizeLimit"></a>

로컬 보조 인덱스가 하나 이상 포함된 테이블의 항목 컬렉션의 최대 크기는 10GB입니다. 이는 로컬 보조 인덱스가 없는 테이블의 항목 컬렉션에는 적용되지 않으며, 글로벌 보조 인덱스의 항목 컬렉션에도 적용되지 않습니다. 로컬 보조 인덱스가 하나 이상 있는 테이블만 영향을 받습니다.

항목 컬렉션이 10GB 한도를 초과할 경우 DynamoDB에서 `ItemCollectionSizeLimitExceededException`을 반환할 수 있으며, 항목 컬렉션에 더 이상 항목을 추가하거나 항목 컬렉션에 있는 항목의 크기를 늘리지 못할 수 있습니다. (항목 컬렉션의 크기를 줄이는 읽기 및 쓰기 작업은 계속 할 수 있습니다.) 다른 항목 컬렉션에는 계속 항목을 추가할 수 있습니다.

항목 컬렉션의 크기를 줄이려면 다음 중 하나를 수행합니다.
+ 해당 파티션 키 값이 있는 불필요한 항목을 삭제합니다. 기본 테이블에서 이러한 항목을 삭제하면 DynamoDB도 동일한 파티션 키 값을 가진 인덱스 항목을 제거합니다.
+ 속성을 제거하거나 속성 크기를 줄여 항목을 업데이트합니다. 이러한 속성이 로컬 보조 인덱스로 프로젝션될 경우 DynamoDB에서도 해당 인덱스 항목의 크기를 줄입니다.
+ 동일한 파티션 키와 정렬 키로 새 테이블을 만든 다음 기존 테이블의 항목을 새 테이블로 이동합니다. 이 방법은 테이블에 자주 액세스하지 않는 과거 데이터가 있을 경우 효과적입니다. 이 과거 데이터를 Amazon Simple Storage Service(Amazon S3)에 보관하는 방법도 고려할 수 있습니다.

항목 컬렉션의 총 크기가 10GB 미만으로 감소하면 동일한 파티션 키 값을 가진 항목을 다시 추가할 수 있습니다.

항목 컬렉션의 크기를 모니터링하기 위해 애플리케이션을 활용하는 것이 가장 좋습니다. `ReturnItemCollectionMetrics`, `SIZE`, `BatchWriteItem` 또는 `DeleteItem`을 사용할 때마다 `PutItem` 파라미터를 `UpdateItem`로 설정하는 것이 그중 한 가지 방법입니다. 애플리케이션은 출력에 있는 `ReturnItemCollectionMetrics` 객체를 조사하고 항목 컬렉션이 사용자 정의 한도(예: 8GB)를 초과할 때마다 오류 메시지를 기록해야 합니다. 한도를 10GB보다 작게 설정할 경우 이른 단계에서 경고를 받을 수 있으므로 항목 컬렉션이 한도에 가까워지면 미리 조치를 수행할 수 있습니다.

### 항목 컬렉션 및 파티션
<a name="LSI.ItemCollections.OnePartition"></a>

로컬 보조 인덱스가 하나 이상 포함된 테이블에서 각 항목 컬렉션은 한 파티션에 저장됩니다. 이러한 항목 컬렉션의 전체 크기는 해당 파티션의 용량(10GB)으로 제한됩니다. 데이터 모델에 크기 제한이 없는 항목 컬렉션이 포함되어 있거나 향후 일부 항목 컬렉션이 10GB를 초과할 것으로 합리적으로 예상되는 애플리케이션의 경우 대신 글로벌 보조 인덱스를 사용하는 것이 좋습니다.

테이블 데이터가 각각의 파티션 키 값에서 고르게 분산되도록 애플리케이션을 설계해야 합니다. 로컬 보조 인덱스가 있는 테이블의 경우 애플리케이션이 단일 파티션의 단일 항목 컬렉션 내에 읽기 및 쓰기 작업의 "핫스폿"을 만들지 않아야 합니다.

# 로컬 보조 인덱스로 작업: Java
<a name="LSIJavaDocumentAPI"></a>

AWS SDK for Java Document API를 사용하여 하나 이상의 로컬 보조 인덱스가 포함된 Amazon DynamoDB 테이블을 만들고, 테이블의 인덱스를 설명하고, 인덱스를 사용하여 쿼리를 수행할 수 있습니다.

다음은 AWS SDK for Java Document API를 사용하여 테이블 작업을 할 때 따라야 할 공통 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. 해당하는 요청 객체를 만들어 작업의 필수 및 선택적 파라미터를 제공합니다.

1. 이전 단계에서 만든 클라이언트가 제공한 적절한 메서드를 호출합니다.

**Topics**
+ [로컬 보조 인덱스가 있는 테이블 생성](#LSIJavaDocumentAPI.CreateTableWithIndex)
+ [로컬 보조 인덱스가 있는 테이블 설명](#LSIJavaDocumentAPI.DescribeTableWithIndex)
+ [로컬 보조 인덱스 쿼리](#LSIJavaDocumentAPI.QueryAnIndex)
+ [예: Java 문서 API를 사용하는 로컬 보조 인덱스](LSIJavaDocumentAPI.Example.md)

## 로컬 보조 인덱스가 있는 테이블 생성
<a name="LSIJavaDocumentAPI.CreateTableWithIndex"></a>

로컬 보조 인덱스는 테이블을 만들 때 동시에 만들어야 합니다. 이렇게 하려면 `createTable` 메서드를 사용하여 하나 이상의 로컬 보조 인덱스 사양을 입력합니다. 다음 Java 코드 예제는 보유한 음악 파일에 있는 곡의 정보를 담은 테이블을 만듭니다. 파티션 키는 `Artist`이고 정렬 키는 `SongTitle`입니다. 보조 인덱스인 `AlbumTitleIndex`는 앨범 제목을 사용해 쿼리를 쉽게 수행하는 데 사용합니다.

다음은 DynamoDB Document API를 사용하여 로컬 보조 인덱스가 있는 테이블을 생성하는 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. `CreateTableRequest` 클래스 인스턴스를 만들어 요청 정보를 입력합니다.

   이때 입력해야 하는 정보는 테이블 이름, 기본 키, 그리고 프로비저닝된 처리량 값입니다. 로컬 보조 인덱스의 경우 인덱스 이름, 인덱스 정렬 키의 이름 및 데이터 형식, 인덱스의 키 스키마, 속성 프로젝션을 입력해야 합니다.

1. 요청 객체를 파라미터로 입력하여 `createTable` 메서드를 호출합니다.

다음 Java 코드 예는 앞의 단계를 보여줍니다. 이 코드는 `Music` 속성에 보조 인덱스가 있는 테이블(`AlbumTitle`)을 생성합니다. 인덱스에 프로젝션되는 속성은 테이블 파티션 키 및 정렬 키와 인덱스 정렬 키뿐입니다.

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName);

//ProvisionedThroughput
createTableRequest.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits((long)5).withWriteCapacityUnits((long)5));

//AttributeDefinitions
ArrayList<AttributeDefinition> attributeDefinitions= new ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition().withAttributeName("Artist").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("SongTitle").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("AlbumTitle").withAttributeType("S"));

createTableRequest.setAttributeDefinitions(attributeDefinitions);

//KeySchema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
tableKeySchema.add(new KeySchemaElement().withAttributeName("SongTitle").withKeyType(KeyType.RANGE));  //Sort key

createTableRequest.setKeySchema(tableKeySchema);

ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>();
indexKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
indexKeySchema.add(new KeySchemaElement().withAttributeName("AlbumTitle").withKeyType(KeyType.RANGE));  //Sort key

Projection projection = new Projection().withProjectionType(ProjectionType.INCLUDE);
ArrayList<String> nonKeyAttributes = new ArrayList<String>();
nonKeyAttributes.add("Genre");
nonKeyAttributes.add("Year");
projection.setNonKeyAttributes(nonKeyAttributes);

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
    .withIndexName("AlbumTitleIndex").withKeySchema(indexKeySchema).withProjection(projection);

ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<LocalSecondaryIndex>();
localSecondaryIndexes.add(localSecondaryIndex);
createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes);

Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
```

DynamoDB에서 테이블을 만들고 테이블 상태가 `ACTIVE`로 설정될 때까지 기다려야 합니다. 그런 다음 테이블에 데이터 항목을 입력할 수 있습니다.

## 로컬 보조 인덱스가 있는 테이블 설명
<a name="LSIJavaDocumentAPI.DescribeTableWithIndex"></a>

테이블의 로컬 보조 인덱스에 관한 자세한 내용은 `describeTable` 메서드를 참조하세요. 각 인덱스에 대해 인덱스의 이름, 키 스키마 및 프로젝션된 속성에 액세스할 수 있습니다.

다음은 AWS SDK for Java Document API를 사용하여 테이블의 로컬 보조 인덱스 정보에 액세스하는 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. `Table` 클래스의 인스턴스를 만듭니다. 테이블 이름을 입력해야 합니다.

1. `describeTable` 객체의 `Table` 메서드를 호출합니다.

다음 Java 코드 예는 앞의 단계를 보여줍니다.

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);

TableDescription tableDescription = table.describe();

List<LocalSecondaryIndexDescription> localSecondaryIndexes 
    = tableDescription.getLocalSecondaryIndexes();

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.

Iterator<LocalSecondaryIndexDescription> lsiIter = localSecondaryIndexes.iterator();
while (lsiIter.hasNext()) {

    LocalSecondaryIndexDescription lsiDescription = lsiIter.next();
    System.out.println("Info for index " + lsiDescription.getIndexName() + ":");
    Iterator<KeySchemaElement> kseIter = lsiDescription.getKeySchema().iterator();
    while (kseIter.hasNext()) {
        KeySchemaElement kse = kseIter.next();
        System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType());
    }
    Projection projection = lsiDescription.getProjection();
    System.out.println("\tThe projection type is: " + projection.getProjectionType());
    if (projection.getProjectionType().toString().equals("INCLUDE")) {
        System.out.println("\t\tThe non-key projected attributes are: " + projection.getNonKeyAttributes());
    }
}
```

## 로컬 보조 인덱스 쿼리
<a name="LSIJavaDocumentAPI.QueryAnIndex"></a>

테이블을 `Query`할 때와 거의 동일한 방식으로 로컬 보조 인덱스에서 `Query` 작업을 사용할 수 있습니다. 인덱스 이름, 인덱스 정렬 키의 쿼리 기준, 반환하려는 속성을 지정해야 합니다. 이 예제에서 인덱스는 `AlbumTitleIndex`이고 인덱스 정렬 키는 `AlbumTitle`입니다.

인덱스로 프로젝션된 속성만 반환됩니다. 키가 아닌 속성을 선택하도록 이 쿼리를 수정할 수도 있지만, 그렇게 하려면 비교적 많은 비용이 드는 테이블 가져오기 작업이 필요합니다. 테이블 가져오기에 대한 자세한 내용은 [속성 프로젝션](LSI.md#LSI.Projections) 단원을 참조하세요.

다음은 AWS SDK for Java Document API를 사용하여 로컬 보조 인덱스를 쿼리하는 단계입니다.

1. `DynamoDB` 클래스의 인스턴스를 만듭니다. 

1. `Table` 클래스의 인스턴스를 만듭니다. 테이블 이름을 입력해야 합니다.

1. `Index` 클래스의 인스턴스를 만듭니다. 인덱스 이름을 입력해야 합니다.

1. `query` 클래스의 `Index` 메서드를 호출합니다.

다음 Java 코드 예는 앞의 단계를 보여줍니다.

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);
Index index = table.getIndex("AlbumTitleIndex");

QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"));

ItemCollection<QueryOutcome> items = index.query(spec);

Iterator<Item> itemsIter = items.iterator();

while (itemsIter.hasNext()) {
    Item item = itemsIter.next();
    System.out.println(item.toJSONPretty());
}
```

### 로컬 보조 인덱스에서 일관된 읽기
<a name="LSIJavaDocumentAPI.ConsistentReads"></a>

최종적으로 일관된 읽기만 지원하는 글로벌 보조 인덱스와 달리, 로컬 보조 인덱스는 최종적으로 일관된 읽기와 강력하게 일관된 읽기를 모두 지원합니다. 로컬 보조 인덱스에서 강력히 일관된 읽기는 항상 업데이트된 최신 값을 반환합니다. 쿼리가 기본 테이블에서 추가 속성을 가져와야 하는 경우 가져온 해당 속성도 마찬가지로 인덱스와 일관성을 유지합니다.

기본적으로 `Query`는 최종적으로 일관된 읽기를 사용합니다. 강력하게 일관된 읽기를 요청하려면 `QuerySpec`에서 `ConsistentRead`를 `true`로 설정합니다. 다음 예제에서는 강력하게 일관된 읽기를 사용하여 `AlbumTitleIndex`를 쿼리합니다.

**Example**  

```
QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"))
    .withConsistentRead(true);
```

**참고**  
강력하게 일관된 읽기는 반환된(반올림된) 데이터 4KB당 하나의 읽기 용량 단위를 소비하는 반면, 최종적으로 일관된 읽기는 그중 절반을 소비합니다. 예를 들어 9KB의 데이터를 반환하는 강력하게 일관된 읽기는 3개의 읽기 용량 단위(9KB/4KB = 2.25, 반올림 3)를 사용하는 반면, 최종적으로 일관된 읽기를 사용하는 동일한 쿼리는 1.5개의 읽기 용량 단위를 사용합니다. 애플리케이션이 약간 오래된 데이터 읽기를 허용할 수 있는 경우 최종적으로 일관된 읽기를 사용하여 읽기 용량 사용량을 줄입니다. 자세한 내용은 [읽기 용량 단위](LSI.md#LSI.ThroughputConsiderations.Reads) 섹션을 참조하세요.

# 예: Java 문서 API를 사용하는 로컬 보조 인덱스
<a name="LSIJavaDocumentAPI.Example"></a>

다음 Java 코드 예제는 Amazon DynamoDB에서 로컬 보조 인덱스로 작업하는 방법을 보여 줍니다. 예를 들어 파티션 키가 `CustomerOrders`이고 정렬 키가 `CustomerId`인 `OrderId`라는 테이블을 만들 수 있습니다. 이 테이블에는 두 개의 로컬 보조 인덱스가 있습니다.
+ `OrderCreationDateIndex` - 정렬 키는 `OrderCreationDate`이며 다음 속성이 인덱스로 프로젝션됩니다.
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex` - 정렬 키는 `IsOpen`이며 모든 테이블 속성이 인덱스로 프로젝션됩니다.

`CustomerOrders` 테이블이 생성되면 프로그램에서 고객 주문을 나타내는 데이터와 함께 테이블을 로드합니다. 그런 다음 로컬 보조 인덱스를 사용하여 데이터를 쿼리합니다. 마지막으로 프로그램에서 `CustomerOrders` 테이블을 삭제합니다.

다음 샘플을 테스트하기 위한 단계별 지침은 [Java 코드 예](CodeSamples.Java.md) 섹션을 참조하세요.

**Example**  

```
package com.example.dynamodb;

import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;

import java.util.HashMap;
import java.util.Map;

public class DocumentAPILocalSecondaryIndexExample {

    static DynamoDbClient client = DynamoDbClient.create();
    public static String tableName = "CustomerOrders";

    public static void main(String[] args) {
        createTable();
        loadData();
        query(null);
        query("IsOpenIndex");
        query("OrderCreationDateIndex");
        deleteTable(tableName);
    }

    public static void createTable() {
        CreateTableRequest request = CreateTableRequest.builder()
            .tableName(tableName)
            .provisionedThroughput(ProvisionedThroughput.builder()
                .readCapacityUnits(1L)
                .writeCapacityUnits(1L)
                .build())
            .attributeDefinitions(
                AttributeDefinition.builder().attributeName("CustomerId").attributeType(ScalarAttributeType.S).build(),
                AttributeDefinition.builder().attributeName("OrderId").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("OrderCreationDate").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("IsOpen").attributeType(ScalarAttributeType.N).build())
            .keySchema(
                KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                KeySchemaElement.builder().attributeName("OrderId").keyType(KeyType.RANGE).build())
            .localSecondaryIndexes(
                LocalSecondaryIndex.builder()
                    .indexName("OrderCreationDateIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("OrderCreationDate").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.INCLUDE)
                        .nonKeyAttributes("ProductCategory", "ProductName")
                        .build())
                    .build(),
                LocalSecondaryIndex.builder()
                    .indexName("IsOpenIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("IsOpen").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.ALL)
                        .build())
                    .build())
            .build();

        System.out.println("Creating table " + tableName + "...");
        client.createTable(request);

        try (DynamoDbWaiter waiter = client.waiter()) {
            WaiterResponse<DescribeTableResponse> response = waiter.waitUntilTableExists(r -> r.tableName(tableName));
            response.matched().response().ifPresent(System.out::println);
        }
    }

    public static void query(String indexName) {
        System.out.println("\n***********************************************************\n");
        System.out.println("Querying table " + tableName + "...");

        if ("IsOpenIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that are open.");
            System.out.println("Only a user-specified list of attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_isopen", AttributeValue.builder().n("1").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and IsOpen = :v_isopen")
                .expressionAttributeValues(values)
                .projectionExpression("OrderCreationDate, ProductCategory, ProductName, OrderStatus")
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else if ("OrderCreationDateIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that were placed after 01/31/2015.");
            System.out.println("Only the projected attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_orddate", AttributeValue.builder().n("20150131").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and OrderCreationDate >= :v_orddate")
                .expressionAttributeValues(values)
                .select(Select.ALL_PROJECTED_ATTRIBUTES)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else {
            System.out.println("\nNo index: All of Bob's orders, by OrderId:\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .keyConditionExpression("CustomerId = :v_custid")
                .expressionAttributeValues(values)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);
        }
    }

    public static void deleteTable(String tableName) {
        System.out.println("Deleting table " + tableName + "...");
        client.deleteTable(DeleteTableRequest.builder().tableName(tableName).build());

        try (DynamoDbWaiter waiter = client.waiter()) {
            waiter.waitUntilTableNotExists(r -> r.tableName(tableName));
        }
    }

    public static void loadData() {
        System.out.println("Loading data into table " + tableName + "...");

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150101").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("The Great Outdoors").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Bike").build(),
            "ProductName", AttributeValue.builder().s("Super Mountain").build(),
            "OrderStatus", AttributeValue.builder().s("ORDER RECEIVED").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150304").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("A Quiet Interlude").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("176493").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150111").build(),
            "ProductCategory", AttributeValue.builder().s("Movie").build(),
            "ProductName", AttributeValue.builder().s("Calm Before The Storm").build(),
            "OrderStatus", AttributeValue.builder().s("SHIPPING DELAY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("859323").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150124").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("E-Z Listening").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("756943").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("Symphony 9").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("645193").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("4").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150222").build(),
            "ProductCategory", AttributeValue.builder().s("Hardware").build(),
            "ProductName", AttributeValue.builder().s("Extra Heavy Hammer").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("5").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150309").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("How To Cook").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("440185").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("6").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150318").build(),
            "ProductCategory", AttributeValue.builder().s("Luggage").build(),
            "ProductName", AttributeValue.builder().s("Really Big Suitcase").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("893927").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("7").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150324").build(),
            "ProductCategory", AttributeValue.builder().s("Golf").build(),
            "ProductName", AttributeValue.builder().s("PGA Pro II").build(),
            "OrderStatus", AttributeValue.builder().s("OUT FOR DELIVERY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("383283").build()));
    }

    private static void putItem(Map<String, AttributeValue> item) {
        client.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
    }
}
```

# 로컬 보조 인덱스로 작업: .NET
<a name="LSILowLevelDotNet"></a>

**Topics**
+ [로컬 보조 인덱스가 있는 테이블 생성](#LSILowLevelDotNet.CreateTableWithIndex)
+ [로컬 보조 인덱스가 있는 테이블 설명](#LSILowLevelDotNet.DescribeTableWithIndex)
+ [로컬 보조 인덱스 쿼리](#LSILowLevelDotNet.QueryAnIndex)
+ [예: AWS SDK for .NET 하위 수준 API를 사용하는 로컬 보조 인덱스](LSILowLevelDotNet.Example.md)

AWS SDK for .NET 하위 수준 API를 사용하여 하나 이상의 로컬 보조 인덱스가 포함된 Amazon DynamoDB 테이블을 만들고, 테이블의 인덱스를 설명하고, 인덱스를 사용하여 쿼리를 수행할 수 있습니다. 이들 작업은 해당되는 하위 수준 DynamoDB API 작업으로 매핑됩니다. 자세한 내용은 [.NET 코드 예시](CodeSamples.DotNet.md) 섹션을 참조하세요.

다음은 .NET 하위 수준 API를 사용하여 테이블 작업을 할 때 따라야 할 공통 단계입니다.

1. `AmazonDynamoDBClient` 클래스의 인스턴스를 만듭니다. 

1. 해당하는 요청 객체를 만들어 작업의 필수 및 선택적 파라미터를 제공합니다.

   예를 들어 `CreateTableRequest` 객체를 만들어 테이블을 생성하거나 `QueryRequest` 객체를 만들어 테이블 또는 인덱스를 쿼리합니다.

1. 이전 단계에서 만든 클라이언트가 제공한 적절한 메서드를 실행합니다.

## 로컬 보조 인덱스가 있는 테이블 생성
<a name="LSILowLevelDotNet.CreateTableWithIndex"></a>

로컬 보조 인덱스는 테이블을 만들 때 동시에 만들 수 있습니다. 이렇게 하려면 `CreateTable`을 사용하고 하나 이상의 로컬 보조 인덱스에 대한 사양을 제공합니다. 다음 C\$1 코드 예제는 보유한 음악 파일에 있는 곡의 정보를 담은 테이블을 만듭니다. 파티션 키는 `Artist`이고 정렬 키는 `SongTitle`입니다. 보조 인덱스인 `AlbumTitleIndex`는 앨범 제목을 사용해 쿼리를 쉽게 수행하는 데 사용합니다.

다음은 .NET 하위 수준 API를 사용하여 로컬 보조 인덱스가 포함된 테이블을 생성하는 단계입니다.

1. `AmazonDynamoDBClient` 클래스의 인스턴스를 만듭니다. 

1. `CreateTableRequest` 클래스 인스턴스를 만들어 요청 정보를 입력합니다.

   이때 입력해야 하는 정보는 테이블 이름, 기본 키, 그리고 프로비저닝된 처리량 값입니다. 로컬 보조 인덱스의 경우 인덱스 이름, 인덱스 정렬 키의 이름 및 데이터 형식, 인덱스의 키 스키마, 속성 프로젝션을 입력해야 합니다.

1. 요청 객체를 파라미터로 입력하여 `CreateTable` 메서드를 실행합니다.

다음 C\$1 코드 예제에서는 이전 단계를 설명합니다. 이 코드는 `Music` 속성에 보조 인덱스가 있는 테이블(`AlbumTitle`)을 생성합니다. 인덱스에 프로젝션되는 속성은 테이블 파티션 키 및 정렬 키와 인덱스 정렬 키뿐입니다.

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest()
{
    TableName = tableName
};

//ProvisionedThroughput
createTableRequest.ProvisionedThroughput = new ProvisionedThroughput()
{
    ReadCapacityUnits = (long)5,
    WriteCapacityUnits = (long)5
};

//AttributeDefinitions
List<AttributeDefinition> attributeDefinitions = new List<AttributeDefinition>();

attributeDefinitions.Add(new AttributeDefinition()
{
    AttributeName = "Artist",
    AttributeType = "S"
});

attributeDefinitions.Add(new AttributeDefinition()
 {
     AttributeName = "SongTitle",
     AttributeType = "S"
 });

attributeDefinitions.Add(new AttributeDefinition()
 {
     AttributeName = "AlbumTitle",
     AttributeType = "S"
 });

createTableRequest.AttributeDefinitions = attributeDefinitions;

//KeySchema
List<KeySchemaElement> tableKeySchema = new List<KeySchemaElement>();

tableKeySchema.Add(new KeySchemaElement() { AttributeName = "Artist", KeyType = "HASH" });  //Partition key
tableKeySchema.Add(new KeySchemaElement() { AttributeName = "SongTitle", KeyType = "RANGE" });  //Sort key

createTableRequest.KeySchema = tableKeySchema;

List<KeySchemaElement> indexKeySchema = new List<KeySchemaElement>();
indexKeySchema.Add(new KeySchemaElement() { AttributeName = "Artist", KeyType = "HASH" });  //Partition key
indexKeySchema.Add(new KeySchemaElement() { AttributeName = "AlbumTitle", KeyType = "RANGE" });  //Sort key

Projection projection = new Projection() { ProjectionType = "INCLUDE" };

List<string> nonKeyAttributes = new List<string>();
nonKeyAttributes.Add("Genre");
nonKeyAttributes.Add("Year");
projection.NonKeyAttributes = nonKeyAttributes;

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
{
    IndexName = "AlbumTitleIndex",
    KeySchema = indexKeySchema,
    Projection = projection
};

List<LocalSecondaryIndex> localSecondaryIndexes = new List<LocalSecondaryIndex>();
localSecondaryIndexes.Add(localSecondaryIndex);
createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

CreateTableResponse result = client.CreateTable(createTableRequest);
Console.WriteLine(result.CreateTableResult.TableDescription.TableName);
Console.WriteLine(result.CreateTableResult.TableDescription.TableStatus);
```

DynamoDB에서 테이블을 만들고 테이블 상태가 `ACTIVE`로 설정될 때까지 기다려야 합니다. 그런 다음 테이블에 데이터 항목을 입력할 수 있습니다.

## 로컬 보조 인덱스가 있는 테이블 설명
<a name="LSILowLevelDotNet.DescribeTableWithIndex"></a>

테이블의 로컬 보조 인덱스에 관한 자세한 내용은 `DescribeTable` API를 참조하세요. 각 인덱스에 대해 인덱스의 이름, 키 스키마 및 프로젝션된 속성에 액세스할 수 있습니다.

다음은 .NET 하위 수준 API를 사용하여 테이블의 로컬 보조 인덱스 정보에 액세스하는 단계입니다.

1. `AmazonDynamoDBClient` 클래스의 인스턴스를 만듭니다. 

1. `DescribeTableRequest` 클래스 인스턴스를 만들어 요청 정보를 입력합니다. 테이블 이름을 입력해야 합니다.

1. 요청 객체를 파라미터로 입력하여 `describeTable` 메서드를 실행합니다.

다음 C\$1 코드 예제에서는 이전 단계를 설명합니다.

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "Music";

DescribeTableResponse response = client.DescribeTable(new DescribeTableRequest() { TableName = tableName });
List<LocalSecondaryIndexDescription> localSecondaryIndexes =
    response.DescribeTableResult.Table.LocalSecondaryIndexes;

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.
foreach (LocalSecondaryIndexDescription lsiDescription in localSecondaryIndexes)
{
    Console.WriteLine("Info for index " + lsiDescription.IndexName + ":");

    foreach (KeySchemaElement kse in lsiDescription.KeySchema)
    {
        Console.WriteLine("\t" + kse.AttributeName + ": key type is " + kse.KeyType);
    }

    Projection projection = lsiDescription.Projection;

    Console.WriteLine("\tThe projection type is: " + projection.ProjectionType);

    if (projection.ProjectionType.ToString().Equals("INCLUDE"))
    {
        Console.WriteLine("\t\tThe non-key projected attributes are:");

        foreach (String s in projection.NonKeyAttributes)
        {
            Console.WriteLine("\t\t" + s);
        }

    }
}
```

## 로컬 보조 인덱스 쿼리
<a name="LSILowLevelDotNet.QueryAnIndex"></a>

테이블을 `Query`할 때와 거의 동일한 방식으로 로컬 보조 인덱스에서 `Query`를 사용할 수 있습니다. 인덱스 이름, 인덱스 정렬 키의 쿼리 기준, 반환하려는 속성을 지정해야 합니다. 이 예제에서 인덱스는 `AlbumTitleIndex`이고 인덱스 정렬 키는 `AlbumTitle`입니다.

인덱스로 프로젝션된 속성만 반환됩니다. 키가 아닌 속성을 선택하도록 이 쿼리를 수정할 수도 있지만, 그렇게 하려면 비교적 많은 비용이 드는 테이블 가져오기 작업이 필요합니다. 테이블 가져오기에 대한 자세한 내용은 [속성 프로젝션](LSI.md#LSI.Projections) 단원을 참조하세요.

다음은 .NET 하위 수준 API를 사용하여 로컬 보조 인덱스를 쿼리하는 단계입니다.

1. `AmazonDynamoDBClient` 클래스의 인스턴스를 만듭니다. 

1. `QueryRequest` 클래스 인스턴스를 만들어 요청 정보를 입력합니다.

1. 요청 객체를 파라미터로 입력하여 `query` 메서드를 실행합니다.

다음 C\$1 코드 예제에서는 이전 단계를 설명합니다.

**Example**  

```
QueryRequest queryRequest = new QueryRequest
{
    TableName = "Music",
    IndexName = "AlbumTitleIndex",
    Select = "ALL_ATTRIBUTES",
    ScanIndexForward = true,
    KeyConditionExpression = "Artist = :v_artist and AlbumTitle = :v_title",
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":v_artist",new AttributeValue {S = "Acme Band"}},
        {":v_title",new AttributeValue {S = "Songs About Life"}}
    },
};

QueryResponse response = client.Query(queryRequest);

foreach (var attribs in response.Items)
{
    foreach (var attrib in attribs)
    {
        Console.WriteLine(attrib.Key + " ---> " + attrib.Value.S);
    }
    Console.WriteLine();
}
```

# 예: AWS SDK for .NET 하위 수준 API를 사용하는 로컬 보조 인덱스
<a name="LSILowLevelDotNet.Example"></a>

다음 C\$1 코드 예제는 Amazon DynamoDB에서 로컬 보조 인덱스로 작업하는 방법을 보여 줍니다. 예를 들어 파티션 키가 `CustomerOrders`이고 정렬 키가 `CustomerId`인 `OrderId`라는 테이블을 만들 수 있습니다. 이 테이블에는 두 개의 로컬 보조 인덱스가 있습니다.
+ `OrderCreationDateIndex` - 정렬 키는 `OrderCreationDate`이며 다음 속성이 인덱스로 프로젝션됩니다.
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex` - 정렬 키는 `IsOpen`이며 모든 테이블 속성이 인덱스로 프로젝션됩니다.

`CustomerOrders` 테이블이 생성되면 프로그램에서 고객 주문을 나타내는 데이터와 함께 테이블을 로드합니다. 그런 다음 로컬 보조 인덱스를 사용하여 데이터를 쿼리합니다. 마지막으로 프로그램에서 `CustomerOrders` 테이블을 삭제합니다.

다음 예제를 테스트하기 위한 단계별 지침은 [.NET 코드 예시](CodeSamples.DotNet.md) 단원을 참조하세요.

**Example**  

```
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.SecurityToken;

namespace com.amazonaws.codesamples
{
    class LowLevelLocalSecondaryIndexExample
    {
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
        private static string tableName = "CustomerOrders";

        static void Main(string[] args)
        {
            try
            {
                CreateTable();
                LoadData();

                Query(null);
                Query("IsOpenIndex");
                Query("OrderCreationDateIndex");

                DeleteTable(tableName);

                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
            catch (AmazonDynamoDBException e) { Console.WriteLine(e.Message); }
            catch (AmazonServiceException e) { Console.WriteLine(e.Message); }
            catch (Exception e) { Console.WriteLine(e.Message); }
        }

        private static void CreateTable()
        {
            var createTableRequest =
                new CreateTableRequest()
                {
                    TableName = tableName,
                    ProvisionedThroughput =
                    new ProvisionedThroughput()
                    {
                        ReadCapacityUnits = (long)1,
                        WriteCapacityUnits = (long)1
                    }
                };

            var attributeDefinitions = new List<AttributeDefinition>()
        {
            // Attribute definitions for table primary key
            { new AttributeDefinition() {
                  AttributeName = "CustomerId", AttributeType = "S"
              } },
            { new AttributeDefinition() {
                  AttributeName = "OrderId", AttributeType = "N"
              } },
            // Attribute definitions for index primary key
            { new AttributeDefinition() {
                  AttributeName = "OrderCreationDate", AttributeType = "N"
              } },
            { new AttributeDefinition() {
                  AttributeName = "IsOpen", AttributeType = "N"
              }}
        };

            createTableRequest.AttributeDefinitions = attributeDefinitions;

            // Key schema for table
            var tableKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              } },                                                  //Partition key
            { new KeySchemaElement() {
                  AttributeName = "OrderId", KeyType = "RANGE"
              } }                                                //Sort key
        };

            createTableRequest.KeySchema = tableKeySchema;

            var localSecondaryIndexes = new List<LocalSecondaryIndex>();

            // OrderCreationDateIndex
            LocalSecondaryIndex orderCreationDateIndex = new LocalSecondaryIndex()
            {
                IndexName = "OrderCreationDateIndex"
            };

            // Key schema for OrderCreationDateIndex
            var indexKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              } },                                                    //Partition key
            { new KeySchemaElement() {
                  AttributeName = "OrderCreationDate", KeyType = "RANGE"
              } }                                                            //Sort key
        };

            orderCreationDateIndex.KeySchema = indexKeySchema;

            // Projection (with list of projected attributes) for
            // OrderCreationDateIndex
            var projection = new Projection()
            {
                ProjectionType = "INCLUDE"
            };

            var nonKeyAttributes = new List<string>()
        {
            "ProductCategory",
            "ProductName"
        };
            projection.NonKeyAttributes = nonKeyAttributes;

            orderCreationDateIndex.Projection = projection;

            localSecondaryIndexes.Add(orderCreationDateIndex);

            // IsOpenIndex
            LocalSecondaryIndex isOpenIndex
                = new LocalSecondaryIndex()
                {
                    IndexName = "IsOpenIndex"
                };

            // Key schema for IsOpenIndex
            indexKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              }},                                                     //Partition key
            { new KeySchemaElement() {
                  AttributeName = "IsOpen", KeyType = "RANGE"
              }}                                                  //Sort key
        };

            // Projection (all attributes) for IsOpenIndex
            projection = new Projection()
            {
                ProjectionType = "ALL"
            };

            isOpenIndex.KeySchema = indexKeySchema;
            isOpenIndex.Projection = projection;

            localSecondaryIndexes.Add(isOpenIndex);

            // Add index definitions to CreateTable request
            createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

            Console.WriteLine("Creating table " + tableName + "...");
            client.CreateTable(createTableRequest);
            WaitUntilTableReady(tableName);
        }

        public static void Query(string indexName)
        {
            Console.WriteLine("\n***********************************************************\n");
            Console.WriteLine("Querying table " + tableName + "...");

            QueryRequest queryRequest = new QueryRequest()
            {
                TableName = tableName,
                ConsistentRead = true,
                ScanIndexForward = true,
                ReturnConsumedCapacity = "TOTAL"
            };


            String keyConditionExpression = "CustomerId = :v_customerId";
            Dictionary<string, AttributeValue> expressionAttributeValues = new Dictionary<string, AttributeValue> {
            {":v_customerId", new AttributeValue {
                 S = "bob@example.com"
             }}
        };


            if (indexName == "IsOpenIndex")
            {
                Console.WriteLine("\nUsing index: '" + indexName
                          + "': Bob's orders that are open.");
                Console.WriteLine("Only a user-specified list of attributes are returned\n");
                queryRequest.IndexName = indexName;

                keyConditionExpression += " and IsOpen = :v_isOpen";
                expressionAttributeValues.Add(":v_isOpen", new AttributeValue
                {
                    N = "1"
                });

                // ProjectionExpression
                queryRequest.ProjectionExpression = "OrderCreationDate, ProductCategory, ProductName, OrderStatus";
            }
            else if (indexName == "OrderCreationDateIndex")
            {
                Console.WriteLine("\nUsing index: '" + indexName
                          + "': Bob's orders that were placed after 01/31/2013.");
                Console.WriteLine("Only the projected attributes are returned\n");
                queryRequest.IndexName = indexName;

                keyConditionExpression += " and OrderCreationDate > :v_Date";
                expressionAttributeValues.Add(":v_Date", new AttributeValue
                {
                    N = "20130131"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else
            {
                Console.WriteLine("\nNo index: All of Bob's orders, by OrderId:\n");
            }
            queryRequest.KeyConditionExpression = keyConditionExpression;
            queryRequest.ExpressionAttributeValues = expressionAttributeValues;

            var result = client.Query(queryRequest);
            var items = result.Items;
            foreach (var currentItem in items)
            {
                foreach (string attr in currentItem.Keys)
                {
                    if (attr == "OrderId" || attr == "IsOpen"
                        || attr == "OrderCreationDate")
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].N);
                    }
                    else
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].S);
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine("\nConsumed capacity: " + result.ConsumedCapacity.CapacityUnits + "\n");
        }

        private static void DeleteTable(string tableName)
        {
            Console.WriteLine("Deleting table " + tableName + "...");
            client.DeleteTable(new DeleteTableRequest()
            {
                TableName = tableName
            });
            WaitForTableToBeDeleted(tableName);
        }

        public static void LoadData()
        {
            Console.WriteLine("Loading data into table " + tableName + "...");

            Dictionary<string, AttributeValue> item = new Dictionary<string, AttributeValue>();

            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "1"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130101"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Book"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "The Great Outdoors"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "PACKING ITEMS"
            };
            /* no ShipmentTrackingId attribute */
            PutItemRequest putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "2"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130221"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Bike"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Super Mountain"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "ORDER RECEIVED"
            };
            /* no ShipmentTrackingId attribute */
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "3"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130304"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "A Quiet Interlude"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "IN TRANSIT"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "176493"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "1"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130111"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Movie"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Calm Before The Storm"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "SHIPPING DELAY"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "859323"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "2"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130124"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "E-Z Listening"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "756943"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "3"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130221"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Symphony 9"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "645193"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "4"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130222"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Hardware"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Extra Heavy Hammer"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "PACKING ITEMS"
            };
            /* no ShipmentTrackingId attribute */
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "5"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130309"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Book"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "How To Cook"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "IN TRANSIT"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "440185"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "6"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130318"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Luggage"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Really Big Suitcase"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "893927"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "7"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130324"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Golf"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "PGA Pro II"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "OUT FOR DELIVERY"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "383283"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);
        }

        private static void WaitUntilTableReady(string tableName)
        {
            string status = null;
            // Let us wait until table is created. Call DescribeTable.
            do
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                    status = res.Table.TableStatus;
                }
                catch (ResourceNotFoundException)
                {
                    // DescribeTable is eventually consistent. So you might
                    // get resource not found. So we handle the potential exception.
                }
            } while (status != "ACTIVE");
        }

        private static void WaitForTableToBeDeleted(string tableName)
        {
            bool tablePresent = true;

            while (tablePresent)
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                }
                catch (ResourceNotFoundException)
                {
                    tablePresent = false;
                }
            }
        }
    }
}
```

# DynamoDB AWS CLI의 로컬 보조 인덱스로 작업
<a name="LCICli"></a>

AWS CLI를 사용하여 하나 이상의 로컬 보조 인덱스가 있는 Amazon DynamoDB 테이블을 생성하고, 테이블의 인덱스를 설명하고, 인덱스를 사용하여 쿼리를 수행할 수 있습니다.

**Topics**
+ [로컬 보조 인덱스가 있는 테이블 생성](#LCICli.CreateTableWithIndex)
+ [로컬 보조 인덱스가 있는 테이블 설명](#LCICli.DescribeTableWithIndex)
+ [로컬 보조 인덱스 쿼리](#LCICli.QueryAnIndex)

## 로컬 보조 인덱스가 있는 테이블 생성
<a name="LCICli.CreateTableWithIndex"></a>

로컬 보조 인덱스는 테이블을 생성할 때 동시에 생성해야 합니다. 이렇게 하려면 `create-table` 파라미터를 사용하고 하나 이상의 로컬 보조 인덱스에 대한 사양을 제공합니다. 다음 예제는 보유한 음악 파일에 있는 곡의 정보를 담은 테이블(`Music`)을 생성합니다. 파티션 키는 `Artist`이고 정렬 키는 `SongTitle`입니다. 보조 인덱스인 `AlbumTitle` 속성의 `AlbumTitleIndex`는 앨범 제목을 사용해 쿼리를 쉽게 수행하는 데 사용합니다.

```
aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions AttributeName=Artist,AttributeType=S AttributeName=SongTitle,AttributeType=S \
        AttributeName=AlbumTitle,AttributeType=S  \
    --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5 \
    --local-secondary-indexes \
        "[{\"IndexName\": \"AlbumTitleIndex\",
        \"KeySchema\":[{\"AttributeName\":\"Artist\",\"KeyType\":\"HASH\"},
                      {\"AttributeName\":\"AlbumTitle\",\"KeyType\":\"RANGE\"}],
        \"Projection\":{\"ProjectionType\":\"INCLUDE\",  \"NonKeyAttributes\":[\"Genre\", \"Year\"]}}]"
```

DynamoDB에서 테이블을 만들고 테이블 상태가 `ACTIVE`로 설정될 때까지 기다려야 합니다. 그런 다음 테이블에 데이터 항목을 입력할 수 있습니다. [describe table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/describe-table.html)을 사용하여 테이블 생성 상태를 결정할 수 있습니다.

## 로컬 보조 인덱스가 있는 테이블 설명
<a name="LCICli.DescribeTableWithIndex"></a>

테이블의 로컬 보조 인덱스에 관한 자세한 내용은 `describe-table` 파라미터를 참조하세요. 각 인덱스에 대해 인덱스의 이름, 키 스키마 및 프로젝션된 속성에 액세스할 수 있습니다.

```
aws dynamodb describe-table --table-name Music
```

## 로컬 보조 인덱스 쿼리
<a name="LCICli.QueryAnIndex"></a>

테이블을 `query`할 때와 거의 동일한 방식으로 로컬 보조 인덱스의 `query` 작업을 사용할 수 있습니다. 인덱스 이름, 인덱스 정렬 키의 쿼리 기준, 반환하려는 속성을 지정해야 합니다. 이 예제에서 인덱스는 `AlbumTitleIndex`이고 인덱스 정렬 키는 `AlbumTitle`입니다.

인덱스로 프로젝션된 속성만 반환됩니다. 키가 아닌 속성을 선택하도록 이 쿼리를 수정할 수도 있지만, 그렇게 하려면 비교적 많은 비용이 드는 테이블 가져오기 작업이 필요합니다. 테이블 가져오기에 대한 자세한 내용은 [속성 프로젝션](LSI.md#LSI.Projections) 단원을 참조하세요.

```
aws dynamodb query \
    --table-name Music \
    --index-name AlbumTitleIndex \
    --key-condition-expression "Artist = :v_artist and AlbumTitle = :v_title" \
    --expression-attribute-values  '{":v_artist":{"S":"Acme Band"},":v_title":{"S":"Songs About Life"} }'
```