효과적인 DynamoDB 디자인 및 활용
발생일: 2020.02.18
키워드: nosql, dynamodb, 다이나모디비
문제:
이번엔 디비를 NoSQL로 AWS의 DynamoDB를 사용해서 구성해보려고 한다.
아키텍처 디자인을 어떻게 해야할까 고민하던 차에, A가 리인벤트 동영상을 추천해줬는데 내용이 좋다.
아래는 보면서 정리해둔 내용.
해결책:
- 한 애플리케이션에 한 개의 테이블
- 파티션 키는 데이터를 골고루 분산하는 용도로 (데이터 분배 결정)
- 고유 값이 많은 속성(카디널리티가 높은 속성)
- 균일한 비율로 무작위로 요청되는 속성
- 정렬 키는 기존 RDS에서 인덱스를 사용하는 느낌으로 (쿼리, 다양화)
- 1:n, m:n 관계 모델링에 활용
- 효율적/선택적 조회
- 범위 조회
- GSI (Global Secondary Index)
- 별도의 속성으로 파티션과 정렬키 설정 가능
- 인덱스 크기 제한 없고, 기존 테이블 대상으로 추가/삭제 가능
- Eventual consistent read 만 가능
- 테이블 당 5개까지 가능
- 테이블과 별도의 읽기/쓰기 용량(RCU/WCU) 할당
- 즉, 테이블 capacity는 충분한데 GSI capacity 가 부족하면 병목 발생
- LSI (Local Secondary Index)
- 테이블과 동일한 파티션키. 파티션 내에서 정렬
- 인덱스는 10기가 단위로 데이터와 함께 저장
- 테이블을 생성할 때만 설정할 수 있음 (추가/삭제 불가)
- 테이블에 할당된 RCU/WCU 사용
- Eventual + Strong consistent 선택 가능
- 테이블 당 5개까지 가능
- 읽기 용량 단위
- 1 RCU = 초당 4KB 처리 단위
- Eventual consistent = 2회
- Strong consistent = 1회
- 4KB로 올림 처리
- 예: item 크기가 7KB 면 8KB로 올림
- 즉, 가능하면 한 번에 많이 읽어와야 함
- 쓰기 용량 단위
- 1 WCU = 초당 1KB 처리
- 1KB 단위로 올림 처리
- 예: 100B 를 써도 1KB로 처리
- 즉, 가능하면 한 번에 많이 써야 함
- 조회 (Get, Query)
- eventual, strong consistent 선택 가능
- 1MB 단위로 응답
- 결과량이 1MB를 넘을 땐 LastEvaluatedKey 를 ExclusiveStartKey 로 활용
- PK 조회 (동일한 값 조회)
- 예: PK = 1
- SK 조회 (범위 값 조회)
- 예: SK in 10~20, SK beginwith 'foo.'
- LSI 조회 (테이블 용량 소진)
- 예: PK = 1, LSI_SK = foo@bar
- GSI 조회 (별도 용량)
- GSI_PK: name = 'foo', GSI_SK: age > 20
- 조회 (Scan)
- 전체 테이블 필터 적용해서 선택적 조회
- 병렬 스캔 가능하지만 RCU 유의해야 함
- 모델링 (집계 처리의 예)
- 예를 들어, 음원의 상세 정보와 월별 다운로드 수를 함께 집계한다면,
- 음원 정보 아이템
- { PK: 'song-129', SK: 'Details', Title, Artist, ... }
- 음원 집계 아이템
- { PK: 'song-129', SK: 'Month-2020-02', Month: '2020-02', Downloads: 482320 }
- 여기서 가장 많이 다운로드 된 음원을 찾으려고 한다면, GSI 설정
- GSI_PK: Month = '2020-02', ScanIndexForwarded = False, Limit = 1 로 조회
- 모델링 (키를 다양하게 조합하고 싶은 경우)
- PK = 아이디, SK = 집계를 원하는 공통 이름으로 설정 (타입), 속성은 타입에 따라 다름
- 예:
- { PK: 'emp-100', SK: 'master', 이름, 기타 기본 정보}
- { PK: 'emp-100', SK: 'state:CA', 이름, 캘리포니아 근속 정보 }
- { PK: 'emp-100, SK: 'state:NY', 이름, 뉴욕 근속 정보 }
- { PK: 'emp-100', SK: 'prevtitle:A', 이름, 이전 타이틀 }
- { PK: 'emp-100', SK: currtitle:B, 이름, 현재 타이틀 }
- SK + 이름 조합을 GSI로 설정
- 모델링 (결합 정렬키)
- 계층 관계를 적용해서 조회하고 싶다면, 정렬키 이름을 계층으로 설정
- 예: SK: 'foo#bar#baz'
- 쓰기 샤딩
- 예를 들어, 한 디바이스에 요청이 몰릴 때
- 디바이스가 쓸 때, 샤드 개수를 서픽스로 붙여서 PK로 할당
- 디바이스 - 샤드 개수를 기록
- 비용
- 쓰기가 가장 비싼 작업
- TTL 을 활용한 삭제와 테이블 삭제는 무료
- 모든 데이터를 보관하려고 하지 말고, 장기간 보관이나 큰 데이터는 S3를 활용
- eventually consistent read 는 50% 저렴. 앱 단에서 잘 설계해서 활용하면 좋음
- 프리티어의 유효기간이 없음
- 25GB 스토리지 + 25 WCU, 25 RCU
- 월 2억 건 정도의 요청량임
- 규모가 커졌을 땐 예약 용량을 활용해서 절약하는 방법 있음
- 필요한 데이터만 읽기
- GSI는 Sparse Index 임 (즉, 데이터가 있는 것만 인덱스를 만듦)
- 아이템에 타입에 따라 필요한 정보를 넣은 후, GSI를 활용
- 한 번에 최대한 많이 쓰고, 필요한 것만 읽는 것이 좋음
- 필요에 따라 auto scaling 쓰면 절약할 수 있음
논의:
한 애플리케이션에 한 개의 테이블을 추천하다고 하는데, 적합한 애플리케이션의 단위는 뭘까?
발표에서는 음원 사이트를 예로 들었는데, 이 테이블에 유저 정보도 포함된 걸까?
글쎄... 지금 생각으론, 주요 서비스 단위로 생각해보면 어떨까 싶다.
예를 들어, 음원서비스라면 이런 식으로 테이블을 나눠야 하지 않을까....?
- 유저: 사용자 기본 정보, 즐겨찾기, 아티스트
- 음원
- 아티스트
- 플레이리스트
음원과 아티스트는 한 테이블에 넣는 게 좋을까... 애매하다.
2020.02.20 추가
이후에 좀 더 리서치를 해봤다.
- 발표에서 의미했던 건, 도메인 또는 MSA 당 한 개의 테이블이라고 이해하면 좋을 것 같다.
- 즉, 위에서 언급했던, 유저, 음원, 아티스트, 플레이리스트는 한 개의 테이블로 구성해도 좋겠다.
다이나모디비와 싱글 테이블 디자인에 대한 건 나중에 정리.