DynamoDB: Building NoSQL Database-Driven Applications 노트

발생일: 2021.02.16

키워드: DynamoDB, 다이나모디비

문제:

 

DynamoDB에 코세라 강좌가 있어 들으면서 정리했던 노트다.

https://www.coursera.org/learn/dynamodb-nosql-database-driven-apps

 

지금보면 너무 기초적인 것 같은데, 당시 흐릿하게 개념이 잡혔던 터라 전체 컨셉을 이해하기에 좋았다.

지금 보더라도 유심히 기억해야할 만한 특징인 볼드로 표시해뒀다.

 

 

내용:

 

Relational Databases and the Problem that Need Solving

 

- RDS(Relational Data System)의 특징
    - 정형 데이터를 분석하기 좋음
    - 스토리지를 많이 차지하고
    - 컴퓨팅 비용이 큼

 

- CAP 정리: 다음과 같은 세 가지 조건을 모두 만족하는 분산 컴퓨터 시스템이 존재하지 않음을 증명한 정리
    - 일관성(Consistency): 모든 노드가 같은 순간에 같은 데이터를 볼 수 있다.
    - 가용성(Availability): 모든 요청이 성공 또는 실패 결과를 반환할 수 있다.
    - 분할내성(Partition tolerance): 메시지 전달이 실패하거나 시스템 일부가 망가져도 시스템이 계속 동작할 수 있다.

 

- Partiton Tolerance 는 시스템에서 보장해야 함. 그래서 C 와 A 중 둘 중 하나를 선택해야 함.

 

- NoSQL 이 유리하지 않은 경우
    - 피크를 예상할 수 있는 self-managed  환경. 수평 확장이 항상 옵션은 아님
    - 정형 데이터로만 구성된 경우
    - 여러 테이블의 조인이 필요한 경우. NoSQL은 cross-table 관계가 없음. 조인을 할 수가 없음


- NoSQL 디비 타입
    - Key-Value
        - key-value 기반으로 키 조합으로 데이터를 가져올 수 있음
        - DynamoDB
        - 예: 유저 세션 데이터 저장하기에 적합
    - Column-based
        - 많은 데이터를 빨리 가져오는데 유리함
        - Cassandra, HBase
    - Document-based
        - tagged elements로 저장
        - MongoDB


- DynamoDB
    - Managed System
    - read/write capacity units 을 조정해서 성능 레벨을 관리할 수 있음
    - 스트림, 트랜잭션, 액셀러레이터, 글로벌 테이블 등의 기능 제공
    - low latency, redundant storage, managed durability, fault tolerance 
    - 1일 10조 콜, 초당 2000만 콜 처리 가능
    - TTL(Time To Live) 설정 가능 

DynamoDB

 

- 테이블
    - primary key = partition key + (sort key)
    - primary key가 중복되면 안됨
    - attirbutes


- read request 의 consistent
    - strongly: 무거움
    - eventually 


- 제한
    - 한 레코드는 400KB 제한
        - 더 큰 건 S3에 저장해야 함
    - key 길이
        - Partition Key: 1byte 이상, 2048 byte 이하
        - Sort key: 1byte ~ 1024byte

 


테이블 생성

- Partition Key와 Sort Key 조합이 Unique 하면 됨


- 테이블 목록
    - $ aws dynamodb list-tables


- 아이템 추가
    - $ aws dynamodb put-item --table-name Music --item file://song1.json


- 조회
    - $ aws dynamodb query --table-name Music --key-condition-expression "Artist = :v1 AND SongTiItle = :v2" --expression-attribute-values file://values1.json
    - v1, v2 = 변수
    - "S": "Rock" 의 의미 = 타입: 값
    - pk 와 sk 를 제외한 다른 속성은 쿼리(query)할 수 없음

 

- 삭제
    - $ aws dynamodb delete-item --table-name Music --key file://key.json
    - { "Artist": { "S": "David Bowie" }, "SongTitle": { "S": "Changes" } }

Query and Scan

- Query: PK 또는 PK + SK로 조회
    - 정렬키만 있으면 조회 

 

- Scan: 테이블의 전체 테이블 조회
    - $ aws dynamodb scan --table-name Music

 

- 필터: 속성으로 조회해야 하는데, 키가 아니라서 쿼리를 할 수 없는 경우
    - key-condition 과 동일하게 --filter-expression 을 쓰면 됨

 

- Scan + Filter 로 키가 아닌 걸 조회할 수 있으나,
    - scan 은 전체 테이블을 읽는 것이므로 주의해야 함

Secondary Index
- 다른 속성을 키로 갖는 테이블 복제본을 만드는 것
- Local secondary index: 다른 정렬키를 생성
- Global secondary index: 다른 파티션키와 정렬키를 생성


백업
- on-demand backup
    - 람다나,
    - AWS Backup 으로 스케쥴링 가능

 

- point-in-time recovery
    - 자동 백업
    - 35일 이내 복원 가능
    - 비동기, 성능 저하 없음

 

- 복원할 땐 read/write capacity가 동일해야 함
    - 복원 시간은 상황에 따라 다름

 

- $ aws dynamodb create-backup --table-name Music --backup-name MusicBackup
- $ aws dynamodb describe-backup --backup-arn [BackupArn]
- $ aws dynamodb restore-table-from-backup --target-table-name MusicRestored --backup-arn [BackupArn]

 


스캔

- $ aws dynamodb scan --table-name Music --max-items 3
    - 최대 1MB 까지 가져옴
    - last process key 로 다음 배치를 가져올 수 있음
    - 페이지네이션처럼 동작함

 

- BatchWriteItem
    - 한 번에 여러 테이블에 여러 레코드를 쓸 수 있음
    - 추가하거나 삭제할 수 있음. 업데이트는 안됨
    - 요청 중에 하나가 실패하더라도 나머지 항목은 실행됨
    - 실패된 아이템은 unprocessed items 응답으로 내려옴
    - { 테이블명: [ { 커맨드: { ... }, 커맨드: { ... }] }
    - $ aws dynamodb batch-write-item --request-items file://batch.json

 

- BatchGetItem
    - 한 번에 여러 테이블에서 여러 레코드를 가져올 수 있음
    - 기본적으로 eventually consistent 임
    - 필요하다면 strongly consistent 를 테이블 단위로 설정할 수 있음
    - $ aws dynamodb batch-get-items --request-items file://...
    - { 테이블명: { Keys: [ ... ] } }
    - 특정 테이블이 실패했다면 unprocessed keys 응답에 있음

 

- BatchWriteItem 과 BatchGetItem 실패 시,
    - unprocessed 응답을 보고 retry 하면 됨


- scan 시 주의
    - 전체 데이터를 훑음
    - 1메가 단위로 끊어서 읽음. LastEvalueatedKey 로 나머지 이어서 가져올 수 있음
    - 필터가 적용되어 있으면, 읽어온 데이터에 값이 없을 수도 있음 (마지막이 아니더라도)

 

- 옵션
    - ExpressionAttributeNames
        - 속성 키가 예약어와 겹치는 등의 이유로 다른 이름으로 매핑하려는 경우
        - # 캐릭터를 씀
        - "#family": "family"
    - ProjectionExpression
        - 필요한 속성만 가져오려는 경우
        - 예: "dragon_name, #family, protection, damage, description"
    - Segment 나 TotalSegments 로 병렬 처리 가능 (가이드)

모니터링
- CloudTrail
    - 테이블 액션, role, service 모니터링
    - 이벤트 로그를 볼 수 있음

 

- CloudWatch Logs
    - 알람 설정 가능
    - API에 따라 처리할 수 있음

 

X-Ray
    - 바틀넥이나 에러 처리할 때 쓸 수 있음

 

- CloudWatch Metric
    - 각종 지표
    - RCU, WCU 볼 수 있음
    - 1 RCU = 1 strongly consistent read = 2 eventually consistent read (4KB 이내, 초당 처리 기준)
    - 1 WCU = 1 write (1KB 이내, 초당 처리 기준)
    - dynamodb provisioned capacity 테이블을 보면 정보가 있음
    - 메트릭스 상세: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/metrics-dimensions.html

- RCU, WCU 도 파티션에 따라 분산됨
    - 예: 5000RCU 가 있고 파티션이 5개이면, 한 파티션에 1000RCU 씩 분산됨

RCU, WCU가 적절한 것 같은데도 퍼포먼스가 제대로 안나올 땐? (프로비전인 경우)
- CloudWatch에서 capacity 관련 메트릭스 확인
    - 테이블에서 시간당 쓰인 RCU, WCU를 볼 수 있음 (글로벌 인덱스나 세컨더리 인덱스도)
    - 메트릭스에서 설정한 것보다 더 많이 쓰였다면 올려줘야 함
    - 알람 맞춰두면 좋음


- RCU와 WCU도 오토스케일링 정책을 적용해서 처리할 수 있음

 

- 그래도 병목이 있으면?
    - 키 스키마 관련 이슈일 수 있음 (파티션 키 최적화가 필요함)
    - large scan 하지 않게 쿼리 최적화가 필요함

파티션키와 최적화
- 테이블을 생성하면 데이터는 파티션에 저장됨
- 파티션은 스토리지 단위이고 물리적 공간(SSD)에 저장됨
- 멀티 리전에 자동으로 복제됨
- 테이블 생성하면 처리량(RCU와 WCU)에 맞춰 적당한 개수의 파티션을 생성함
- 테이블이 커지면 파티션이 추가됨

파티션키
- 모든 테이블은 파티션키가 있음

 

- 파티션키만 있으면
    - 파티션키에 해시함수를 적용한 값으로 파티션 구분

 

- 정렬키가 같이 있으면,
    - 파티션키에 해시함수를 적용한 값으로 파티션을 구분하고
    - 여기에서 sort key 로 데이터를 정렬함

 

- 키 스키마 디자인할 때
    - 파티션키가 저장 위치를 결정함
    - 모든 파티션에 골고루 분산되게 파티션키를 디자인해야 함

 

- RCU, WCU를 직접 설정한 경우 (on-demand 는 해당 안됨)
    - 400RCU가 있고 파티션이 4개라면, 각 파티션은 100RCU를 가짐
    - 200RCU 만큼의 요청이 들어왔을 때, 파티션키가 골고루 분산되어 있다면 각 파티션은 50RCU 씩 처리함
    - 만약 한 파티션에 200RCU만큼의 요청이 몰린다면(hot-partition), 처리량을 넘어서게 됨 (provision throughput exceeded exception 발생)
    - 즉, 전체 RCU를 초과하지 않았는데도 에러가 발생하게 됨
    - 핫파티션이 생기지 않게 파티션키를 디자인해야 함

 

- adaptive capacity 기능
    - 핫파티션이 생겼을 때, 사용되지 않은 쓰루풋을 핫파티션이 사용하게 하는 옵션
    - 기본 적용되어 있음 (아마도 2019년부터?)

 

- burst capacity 기능
    - 5분 동안 사용하지 않는 용량을 남겨두었다가 초과분에 사용함

베스트 프랙티스. 파티션 키 디자인 
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-uniform-load.html
- 골고루 분산되는 속성을 파티션키로 설정해야 함
- 좋은 예
    - 사용자가 많은 경우, 사용자 아이디
    - 디바이스가 비슷한 빈도로 접속하는 경우, 디바이스 아이디
- 나쁜 예
    - HTTP 상태 코드: 200이 대부분을 차지하기 때문
    - 날짜 단위의 아이템 생성 날짜: 요청이 한 곳에 몰림
- 예를 들어, 파티션키를 생성 날짜, 정렬키를 아이템 아이디로 만들면,
    - 오늘 온 요청(파티션키 = '2021-02-18')은 한 파티션(물리적 공간)에 몰리게 됨
- 마찬가지로, 파티션키 종류가 적은 경우엔 분산되는 속성을 파티션키로 하는 게 나음

다른 우회 방법
- 날짜를 꼭 파티션키로 써야하는 경우, 날짜 뒤에 랜덤한 값을 붙여서 골고루 분산되게 할 수 있음
    - 2021-02-18.123 처럼, 0~200 사이의 값을 넣는 것
    - 근데 이렇게 하면 쿼리로 해당 날짜의 데이터를 조회할 수 없음
    - 0~200까지 조합해서 다 합해야 함
- 조회할 때, 날짜 + 주문 아이디 조합 같은 경우라면, 주문 아이디 기반의 난수를 생성해서 골고루 분산되게 하는 방법도 있음
    - 2021-02-18 에 주문번호 10423번이면, 10 % 100 을 적용해서, 2021-02-18.423 처럼 하는 방법
- 시계열 처리가 필요한 경우,
    - 하루에 한 개의 테이블을 생성하고, 해당 테이블의 RCU/WCU을 고정함
    - 파티션키는 날짜, 정렬키를 시간으로 함
    - 하루에 한 테이블에만 처리가 몰리도록 해서 초과되지 않게 관리함

보안
- 암호화가 필요한 데이터(개인정보)
- 기본적으로 암호화됨
- 암호화 키로 별도로 암호화할 수 있음 (암호화 적용해도 성능 영향 거의 없음)
- EncryptionClient
    - 아이템별로 보안 적용할 수 있음 (사이닝)
- 예: 정부의 개인정보에 맞추기 위해
    - 전화번호 같은 것들이 암호화돼서 저장됨
    - client 수준의 암호화도 있음

TTL
- 성능에 영향 없음
- Manage TTL 에서 켜주면 됨
- 아이템에서 TTL 대상 속성을 정해주면 됨
- 세션 구현 예제
    - { PK: username, SK: sessionId, TTL: expireTime }
- TTL 적용 권한에 IAM을 설정할 수 있음
- 백업할 때에도 적용됨
- 만료된 아이템은 48시간 이내에 삭제됨
    - 만료되었는데 삭제되지 않은 경우, 쿼리, 스캔 결과에 나올 수 있음?

글로벌 테이블
- 여러 리전 간 리플리케이션 제공
- 다이나모디비 스트림으로 리얼 타임으로 전달
- 각 리전 간 동일한 스킴의 테이블을 만들고, 글로벌 테이블로 묶으면 됨
- eventually consistent 로 동기화됨

스트림
- time-ordered sequence
24시간 동안 저장됨
- 스트림 이벤트를 받아서 처리할 수 있음
    - 스트림 데이터는 별도로 저장됨
    - 다이나모디비 스트림에 액세스가 필요함
- 프라이머리키 정보가 전달됨
- ES 인덱스 갱신 예

Optimistic Locking
- 동시 업데이트 이슈
- 다이나모디비는 락킹을 지원하지 않음

 

- Optimistic Locking
    - 작업 중인 항목이 마지막으로 읽은 이후 덮어씌워지지 않은지 확인하는 전략

 

- conditional write 로 구현
    - PutItem, UpdateItem, DeleteItem 메서드의 옵션으로  지원
    - ConditionExpression 파라미터
    - 예:
        - 키가 없을 때에만 허용
        - 속성이 특정 값인 경우에만 허용

 

- 버전 넘버로 관리하는 예
    - 아이템에 버전을 명시하는 속성을 별도로 둠
    - 업데이트 전에 아이템의 버전을 가져옴
    - 업데이트할 때, 동일한 버전일 때만 업데이트
    - 버전이 바뀌었다면 실패. 성공할 때까지 다시 수행

LSI
- 파티션 내에서 새로운 인덱스를 만듬
- LSI는 파티션에 대해서 고유할 필요가 없음
- consistent 를 선택해서 조회할 수 있음
- 테이블 생성할 때만 추가됨

GSI
- 파티션 키로 새로 정렬해야 할 때 사용
- 비동기로 업데이트됨 (eventually consistent)
- GSI 를 위한 RCU/WCU를 설정해야 함

보조키
- 사용하되 최소로 유지할 것
- 인덱스 사이즈를 최대한 작게 유지할 것
    - project item
- 자주 사용되는 쿼리인 경우, 모든 속성을 project 할 것
    - LSI의 projected attribute 에 없는 경우, 다이나모디비가 전체 테이블을 스캔함

싱글 테이블
- 연관된 데이터를 하나의 테이블에 넣는 것을 추천
단, 싱글테이블로 만들 땐, 테이블을 만들기 전에 쿼리를 설계해야 함
- 특히, LSI 는 테이블 생성할 때만 할 수 있기 때문이기도 함
- 먼저 관계 모델을 생성한 후에 싱글 테이블로 설계
- 성능과 쿼리를 위한 것임
- 쿼리를 먼저 생성

음원 테이블 설계 예:
- 작가, 노래, 앨범 관계 모델을 설계


- 쿼리를 설계
    - 아티스트별 곡 찾기
    - 장르별 앨범 찾기
    - 연도별 아티스트의 앨범 찾기
    - 노래 이름으로 찾기

- 쿼리를 설계
    - 아티스트별 곡 찾기: 'PK = 아티스트'로 조회
    - 장르별 앨범 찾기: 장르를 GSI로 만들어서 조회
    - 연도별 아티스트의 앨범 찾기: LSI로 출시연도를 설정해서 조회
    - 노래 이름으로 찾기: 노래 이름을 GSI로 만들어서 조회


- 아티스트 이름을 변경하려고 한다면?
    - 데이터가 여러 번 중복되게 됨. 데이터를 비정규화하기 때문.
    - 한 번에 업데이트하기 위해선 트랜잭션이 필요함
    - 한 번의 오퍼레이션으로 처리할 수 있음

시계열 데이터
- 한 시간 단위에 하나의 테이블을 두는 게 나음
- 스캐닝이 자주 필요한 경우가 특히 그럼 (예: 분기별 집계)
- 아니면 AWS TimeStream 을 써도 됨

싱글 테이블을 만들려고 너무 무리하진 말 것
- 드래곤 카드 게임 서비스라면, 아래 두 테이블은 분리하는 것이 좋음
    - 드래곤 정보 테이블
    - 게임 정보 테이블

DAX
- 한자리수 밀리세컨드
- 마이크로세컨드로 원하는 경우. in-memory 캐시
- DAX 액셀러레이터
- 인메모리 캐시임
- API 호환됨
    - 테이블 대신 클러스터만 보면 됨
- 쓰기보다 리드가 훨씬 큰 경우 유리함
- 작은 아이템이 다른 아이템보다 많이 조회되는 경우
    - 예: 쇼핑몰의 오늘의 인기 아이템
- 별도 비용 있음


논의:

 

이후에 다이나모디비를 적용하면서 헷갈렸던 개념들을 정리해둠

ohgyun.tistory.com/808

 

카테고리

분류 전체보기 (730)
About me. (6)
Daylogs (695)
영어공부 (0)
My works - 추억 (29)
비공개 (0)