1. Secondary Index를 사용하는 이유

 

다음과 같은 은행 계좌를 관리하는 테이블을 생각해보자.

 

OriginCountry가 Germany인 모든 데이터를 불러온다고 가정하자. Query를 통해서 불러오기 위해선 AccountID와 CreationDate을 알고 있는 상황이어야 하지만

실제 Query상황에서는 이를 알 수 있는 방법이 없기 때문에 scan을 사용해야한다. scan을 사용하면 모든 데이터에 대해 검색을 실행하기 때문에 그만큼 실행속도가 느리다.

 

 

 

 

그러나 GSI를 사용하게 되면 다음과 같이 Primary Key를 원하는대로 바꿔서 테이블을 새로 생성 할 수 있다.

 

GSI Table(우측 테이블)을 생성하면서 기존 OriginCountry 속성을 Partition Key로 설정하였다.

기존 테이블에서 scan으로 처리하였던 요청을 query의 "KeyCondition" 함수를 이용하여 "OriginCountry eq Germany" 조건을 이용하게 되면 모두다 읽어버리는 scan보다 Read Capacity가 적게 들게된다.

 

출처 : https://www.youtube.com/watch?v=ihMOlb8EZKE

 

 

 

2. Secondary Index의 제약사항

 

다음 링크를 참조 바랍니다.

https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/Limits.html#limits-api [DynamoDB의 제한 값]

 

 

 

 

3. Secondary Index 설계 원칙

3.1. 인덱스의 수를 최소한으로 유지한다.

  • GSI의 경우, 별도의 용량단위를 부여받기 때문에, 원본 테이블의 sync를 맞추기 위해서는 추가로 용량을 소모하며 이를 줄이는 것이 비용, Storage, I/O 관점에서 효율적이다.
  • 그러므로, 자주 query가 이루어지지 않는 속성에 대해서는 Secondary Index를 생성하지 않는다

 

3.2. LSI 이용시 index에 없는 속성을 projection하지 않는다.

  • LSI의 경우, LSI테이블에 존재하지 않고 원본 테이블에 존재하는 속성에 대해서 쿼리가 가능하다.
  • 다음 다이어그램은 위 경우를 모식화한 것이다.

 

  • LSI에 존재하지 않는 속성에 대해 query를 하게 되면 원본 테이블에서 해당 속성에 대해 찾게 된다.
  • 그 결과, 그만큼 지연시간이 걸리며, row전체를 조회한 다음 projection을 실행하기 때문에 불필요한 데이터에 RCU를 소모하게 된다.
  • 결론적으로, LSI 역시 GSI처럼 필요없는 속성은 인덱스에 포함해서는 안된다.

 

3.3. Sparse Index의 활용

  • Dense Index와 Sparse Index에 대해서는 3.3.1과 3.3.2를 참고한다.
  • 밑의 인덱스 개념을 한마디로 쉽게 설명하면 다음문장과 같다.
  • "테이블에서 Partition Key다음으로 유일하고 특징을 나타내는 속성을 GSI의 Partition Key로 설정하여 Partition을 상황에 맞게 효율적으로 커스트마이징 한다."

 

3.3.1. Dense index

  • 하나의 레코드에 하나의 포인터가 할당된다.
  • 데이터의 모든 키(Partition Key, Sort Key, 일반키 등...)가 인덱스에서 표현이 가능한 형태이다.
  • 이러한 형태는 Primary Key를 이용하여 1회의 Disk I/O 만으로 원하는 레코드를 찾을 수 있는 장점이 있다.

 

 

 

 

 

 

3.3.2. Sparse index

  • Dense Index처럼 모든 키를 가지고 있기 되면 용량이 커진다.
  • 용량이 커지는 단점을 보완하기 위해 데이터 블록마다 하나의 key-pointer쌍을 가지게 하였다.
  • 이 같은 설계를 통해 여러개의 데이터를 하나의 블록으로 합침으로써 Dense Index보다 레코드의 개수를 줄일 수 있다.
  • 레코드의 개수가 줄어든 만큼 Disk I/O는 줄어들지만, 블록안의 속성에 대해 검색할 경우에는 추가적인 disk I/O를 요구할 수 있다.

 

 

 

 

 

 

 

4. 참고문헌

 

1. DynamoDB Partitioning 원리

  • DynamoDB는 자체 내부 Hash Function이 있다.
  • Partition Key값을 파라미터로 계산된 Hash Value를 기준으로 DynamoDB Table 내부의 파티션이 결정되어 데이터는 적재된다.
  • Primary Key가 복합키인 경우에도 단일 Partition Key로 이루어진 경우와 같은 방식으로 partition key hash값을 계산하여 partition에 적재한다.
  • 그러나 같은 partition key를 가진 데이터들은 물리적으로 가깝게 설계되며, Sort Key 값으로 정렬된다.

 

 

 

 

2. Partition Key 설계시 고려사항

 

2-1. 분산된 워크로드

  • Partition Key는 테이블에서 데이터가 저장되는 논리적 및 물리적 파티션을 결정하는 요소이다.
  • DynamoDB는 결정된 파티션들에게 프로비저닝된 Capacity Unit을 균일하게 분배한다.
  • 즉, Partition Key의 분산도를 고려하지 않고 설계시, 요청에 대해 효율적인 Capacity Unit을 사용할 수 없게 된다.
  • 물론 DynamoDB는 관리형 서비스이기 때문에 어느정도 조절은 해 주지만, 최적화를 위해서는 위 관리형 서비스에 의존하지 않고, 직접 튜닝하는 것이 적절하다.

 

 

 

다음 데이터는 실제로 필자가 DynamoDB를 학습하며 설계하였던 테이블이다. (나중에 이 테이블을 보니 최악이었다 :(  ) 

 

 

위 테이블의 경우, 사내 회의실 예약 시스템에 대한 정보를 구현하기 위해 설계한 내역이며, partition key가 INFO_TYPE이며, sort key가 HASH_VALUE로 설계되었다. 

현재 위 테이블의 Partition Key의 값은 단 2개로 나뉘어져 있다. 위 DynamoDB Partition 아키텍처는 다음과 같이 그림으로 나타낼 수 있다.

 

 

 

 

 

위 아키텍처를 염두에 두고 생각해보자. 회의실의 개수는 건물에 한정적이고, 이 한정적인 회의실에서는 수많은 직원들이 회의실을 사용한다.

그러므로 상대적으로 회의실 정보보다 예약 정보가 훨씬 많을 것이고, 데이터에 대한 접근도 예약정보가 훨씬 많을 것이다.

그 결과, 같은 Capacity Unit을 제공받은 partition이지만, I/O요청이 압도적으로 많은 "res" partition key를 가진 파티션은 그만큼 비효율적인 요청처리를 하게 된다.

 

 

 

 

다음 아키텍처는 위 아키텍처를 조금이나마 개선한 아키텍처이다. (극단적인 예시이므로 참고만 해주길 바란다.)

위같이 설계를 바꾼 이유는 다음과 같다.

  • 회의실에 대한 예약은 현재 HASH_VALUE값 자체를 이용하며, [회의날짜]#[회의실]#[회의시작시간]#[회의종료시간] 의 format을 가진다.
  • 시간의 흐름 중에서 뽑은 하나의 시간 표본은 유일하다.
  • HASH_VALUE도 시간 값 이므로, 유일한 값이다.
  • 위 HASH_VALUE를 Partition Key로 설계하면 Partition의 분포도가 좋아진다.
  • 그러므로 한 파티션에 대한 I/O 병목현상이 개선될 수 있다.

 

 

 

 

 

 

2-2. Partition Key에 난수 추가 (Sharding)

  • Sharding은 Partitioning의 한 부분이며, "Horizontal Partitioning"과 같은 의미이다.

  • 기존 설계한 Partition Key에 난수를 추가하게 되면, Hash Function에 의해 결과값이 다양해진다.
  • 그 결과로 DynamoDB 내부의 논리적 및 물리적 파티션의 분포는 분산된다.
  • 파티션이 분산됨에 따라 병렬처리를 개선할 수 있다.

 

2-3. Partition Key를 이용한 효율적인 쓰기 작업 분산

  • DynamoDB는 기본적으로 파티션의 크기와는 상관 없이 균일한 Capacity Unit을 할당받는다.
  • DynamoDB는 관리형 서비스이기 때문에 어느정도 파티션간 Capacity Unit을 프로비저닝 해주지만, 이에 의존한 key설계는 올바르지 못하다.
  • 그러므로, 쓰기 작업에 대해서도 타겟 파티션을 분산시켜 정의할 필요가 있다.

 

 

case1. partition key의 정렬 순서대로 데이터를 쓰는 경우

 

  •  위와 같이 쓰기작업을 정의할 시, 한 파티션의 Capacity Unit을 집중적으로 쓰기 때문에 병목현상이 발생 할 수 있다.

표 출처 : AWS DynamoDB 설명서

 

 

case2. partition key의 순서와 무관하게 분산시켜 데이터를 쓰는 경우

표 출처 : AWS DynamoDB 설명서

  • 위와 같이 쓰기 작업을 개선할 시, 매 작업마다 다른 파티션의 Capacity Unit을 사용하기 때문에 case1 보다 Capacity Unit의 사용률에 대해 보다 유연하게 대처 가능하다.

 

 

 

 

 

3. 참고문헌

1. why we use noSQL

  • 유연성 : rdb와 비교하여, 유연한 스키마를 제공하고 있기 때문에 데이터베이스를 반정형 또는 비정형적인 데이터를 취급하는데 유리하다. 이러한 스키마는 빠르고 반복적인 개발을 유도하게 된다.

  • 확장성 : noSQL 데이터베이스는 고가의 스펙이 뛰어난 서버 대신, 분산형 클러스터를 이용하기 때문에, 서버 운영에 대한 부담 절감과 더 적은 스트리지를 기반으로 운영 할 수 있게 됨에 따라 더 많은 데이터를 담을 수 있게 되었다.

  • 고성능 : 특정 비정형 데이터(document, key-value, graph … ) 및 엑세스 패턴에 대해 특화시킬 수 있기 때문에 특정 워크로드를 유사하게 받아들이는 것이 아닌 그대로 받아들일 수 있게 된다. 이에 따라 유사한 환경을 제공하는 rdb보다 더 뛰어난 성능을 보일 수 있다.

 

 

2. noSQL 데이터 유형

  • key-value : 이 데이터 유형은 rdb와 비교하여 분할성이 크기 때문에 큰 범위로써의 확장을 가능하게 한다. 이로 인해 이 데이터는 게이밍, 광고 및 iot와 같은 사례에서 뛰어난 성능을 발휘한다.

  • document : 어플리케이션 상 데이터는 객체 또는 애플리케이션 코드에서 데이터는 종종 객체 또는json으로 표현된다. 이 형식이 개발자들에게 효율적이며 직관적이기 때문이다. 이 형식을 기반으로하는 데이터베이스를 사용하면 코드 상의 데이터의 형식과 동일한 형식으로 맞출 수 있기 때문에 보다 손쉽게 데이터를 저장하고 query작업을 할 수 있다.

  • graph : SNS와 추천 엔진 등 데이터 간 연결성이 큰 어플리케이션을 쉽게 해주는 데이터이다.

  • in memory : 게이밍과 광고 기술 애플리케이션에는 밀리초의 응답 시간을 필요로 하며, 언제라도 수신 트래픽이 급등할 수 있는 리더보드, 세션 스토어, 실시간 분석을 요구하는 어플리케이션에 특화되어있다.

  • search : 많은 앱에서 로그를 수집하는 환경에서 특화되어있다.

 

 

 

3. RDS vs noSQL

  SQL Databases NOSQL Databases

스키마

구조와 데이터 모델은 사전에 정의되기 때문에 데이터 입력시 레코드에서 사전에 필터링 된 데이터를 입력해야한다. 새로운 유형의 데이터가 저장되기 위해서는 전체 데이터베이스가 변경되어야 한다.

상황에 따라 다르다. 데이터의 분류가 필요없이 즉시 새로운 정보를 추가 할 수 있으며, sql과 달리 다른 필요한 데이터를 같이 저장 할 수 있다.

확장성

수직적이다.

수요를 처리하기 위해서는 더 좋 은 성능의 서버를 이용해야한다.여러 SQL데이터베이스에다 데이터를 저장 할 수 있지만, 이에 따른 추가적인 작업이 일반적으로 필요하다.

수평적이다.

용량을 더 늘리기 위해서는 데이터베이스 관리자가 단숨히 서버나 클라우드 인스턴스를 늘리기만 하면 된다. noSQL데이터베이스는 자동적으로 데이터를 필요한만큼 분산시킨다.

성능

일반적으로 동작하고 있는 시스템에 따라 상이하다. 또한 최고 성능을 위해서 쿼리, 인덱스 및 테이블 구조를 자주 튜닝해야한다.

일반적으로 기본 하드웨어의 클러스터 크기, 네트워크 성능, DB를 호출하는 어플리케이션의 기능에 비례한다.

데이터 호출

ANSI SQL에서 유래한 다양한 명령문으로 데이터를 불러온다

객체를 기반으로 한 API Calling을 데이터를 불러온다.

일관성

강한 일관성

제품마다 상이하다.

예를 들어 MongoDB의 경우는 강한 일관성, Cassandra는 최종 일관성을 기반으로 한다. dynamoDB는 둘다 지원

 

 

최종일관성 : 데이터를 조회 하였을 때, 그 상태의 값 그대로 불러 올 수 있다.

강력한(즉시) 일관성 : 데이터를 조회 할 때, 해당 데이터가 writing상태이면, locking상태가 되어 데이터를 불러올 수 없음.

사진 출처 : https://medium.com/system-design-blog/eventual-consistency-vs-strong-consistency-b4de1f92534d

1. 테이블

  • 데이터 레코드의 집합

 

2. 항목(Item)

  • 테이블에 있는 하나의 레코드 자체를 의미

 

3. 속성(Attribute)

  • 기본적인 데이터 요소로 더 이상 나뉠 수 없는 것, 항목의 조각

  • 속성에 대한 내포 속성은 32 깊이 까지 허용

  • 대부분의 속성은 스칼라(하나의 값만 가질 수 있음)다.

 

 

 

4. primary key

DynamoDB의 PK는 두가지로 구성이 되어 있다.

출처 : https://aws.amazon.com/ko/blogs/database/choosing-the-right-dynamodb-partition-key

4.1 Partition Key

  • 하나의 속성으로 구성되는 단순 기본 키

  • Partition Key의 설계에 따라 데이터의 분산도가 달라진다.
  • Partition Key 선택 기준 (ex. 고객ID, 디바이스 ID, 모델 번호 …)

    • 고유 값이 가장 많은 속성

    • 균일한 비율로 무작위로 요청되는 속성

 

4.2. Sort Key (Range Key)

  • Partition Key와의 조합으로 복합 Primary Key를 구성할 수 있으며, 이를 이용하여 보다 유연하게 DynamoDB에 대한 요청을 처리할 수 있다.

  • Sort Key 선택 기준

    • 1:n, M:N 관계 모델링

    • 효율적 / 선택적 조회

    • 범위 조회

 

 

 

5. Secondary Index

  • Primary Key(Hash Key)만으로 DynamoDB에서 원하는 데이터에 대한 요청을 처리하는 것에 대해 비효율적인 엑세스 패턴이 발생할 수 있다.
  • 이를 방지하고자 테이블에 대해 다른 엑세스 패턴을 설계할 수 있는 Secondary Index개념이 존재하며, RDB개념에서 "View"개념과 비슷하다.
  • Secondary Index에는 GSI(Global Secondary Index) LSI(Local Secondary Index)가 존재한다.
  • GSI의 default limit 개수는 20이다(Support Center를 통해 증가 요청 가능).
  • LSI의 default limit 개수는 5이다.

 

 

GSI(Global Secondary Index)

LSI(Local Secondary Index)

키 속성

  • 기본 테이블과 파티션키, 정렬키 모두 다르게 하여 새로운 primary key를 설정 할 수 있다.

  • 그러나 Hash Key의 항목은 무조건 생성한 GSI의 attribute로 포함되어야 한다.
  • 기본 테이블과 동일한 파티션 키가 있지만, 정렬 키가 다른 인덱스다.

파티션 키 당

인덱싱 크기 제한

  • 크기 제한 없음

  • Partition Key값마다 인덱싱 된 모든 항목의 전체 크기는 10GB 이하.

읽기 일관성

  • 최종 일관성만을 지원한다.

  • 최종 일관성, 강력한 일관성을 지원한다.

프로비저닝된 처리량 소비

  • 읽기 및 쓰기에 대해 기본 테이블과는 다른 별도의 용량단위가 존재한다.

  • 기본 테이블 업데이트 시 발생되는 인덱스 업데이트에도 인덱스 용량단위를 사용한다.

 

  • 기본 테이블의 용량단위를 소비한다.

  • 기본 테이블에 쓰기를 실행할 경우, 기본 테이블의 용량단위를 쓰기 때문에 Capacity Unit이 상대적으로 GSI보다 높게 나올 수 있다.
프로젝션
  • GSI에 존재하는 attribute에 대해서만 projection을 수행할 수 있다,
  • LSI에 존재하지 않고 기본테이블에 존재하는 attrubute에 대해 query 또는 scan할 경우, 기본 테이블에서 해당 속성을 자동으로 가져와 결과를 반환한다.

 

 

6. WCU, RCU

  • AWS의 기본 사상은 "쓰는만큼만 지불"하는 개념이다. 이 개념은 DynamoDB에도 그대로 적용되며, 읽기 및 쓰기하는 용량에 따라서 과금의 정도가 다르다.
  • 읽기에 대한 용량을 RCU(Read Capacity Unit)라 부르고, 쓰기에 대한 용량을 WCU(Write Capacity Unit)라고 부른다.
  • 프리티어로 25 WCU, 25 RCU가 제공된다.
  • ex) 테이블 25개 생성, 각 테이블 별로 1 RCU, 1 WCU의 프로비저닝 된 용량을 할당 -> 총 25 RCU, 25 WCU Free Tier Limit을 넘지 않음
  • ex) 테이블 2개 생성, 각 테이블 별로 15 RCU, 15 WCU의 프로비저닝 된 용량을 할당 -> 총 30 RCU, 30 WCU Free Tier를 제외한 나머지 5 RCU 및 5WCU에 대해 과금

 

6.1. RCU (Read Capacity Unit)

읽기의 경우, Default로 최종적 일관된 읽기가 적용된다.

  • 최종적 일관된 읽기의 경우, 1RCU = 8 KB / s

  • 강력한 일관된 읽기의 경우, 1RCU = 4 KB / s

6.2. WCU (Write Capacity Unit)

  • 1 WCU = 1 KB / s

  • 계산시, 1KB 단위로 올림하여 계산한다.
    ex) item 크기가 3.6KB 항목 하나를 쓸 경우, 4KB로 계산하며, 4WCU를 소비한다.

 

 

 

6.3. 참고 - 일관성

최종적 일관된 읽기(Eventual Consistent Read)

테이블을 읽을 때, 응답은 최근 완료된 쓰기 작업의 결과를 반영하지 않을 수 있다. 그러므로, 응답에는 부실 데이터가 일부 포함될 수 있다.

 

강력한 일관된 읽기(Strongly Consistent Read)

성공한 모든 이전 쓰기 작업의 업데이트를 반영하여 최신 데이터를 기반으로 응답을 반환한다. 그러나 DynamoDB에서 이를 적용하면 다음과 같은 단점을 수반한다.

- 강력한 일관된 읽기는 최종적 일관된 읽기보다 지연 시간이 더 길 수도 있습니다.

- 글로벌 보조 인덱스에서는 강력히 일관된 읽기가 지원되지 않습니다.

- 강력한 일관된 읽기(strongly consistent read)는 네트워크 지연 또는 중단이 발생한 경우에 사용이 어려울 수 있습니다. 이 경우 DynamoDB는 서버 오류(HTTP 500)를 반환할 수도 있습니다.

 

 

 

7. 참고 문헌

 

+ Recent posts