[DynamoDB] Table Primary key design 하기

김지환·2023년 7월 29일
0

TL;DR

AWS DynamoDB Workshop 을 듣고 나서 들었던 이야기를 정리하고 기억하기 위해 포스팅을 남긴다.

DynamoDB 란?

DynamoDB (DDB) 는 aws 에서 제공해주는 key-value database 이다. 아무리 많은 양의 데이터를 저장해도 key-value의 특성상 일정한 latency를 보여준다는 높은 성능을 가지고 있고, 갑작스러운 traffic의 몰림에도 auto scaling 혹은, on-demand 형태로 서비스하면 문제없이 커버할 수 있다는 장점이 있다. ( 단 partition 별 WCU, RCU 의 제한이 있기 때문에 이 부분은 설계적으로 잘 해결해줘야함 )

Tenets of NoSQL Modeling

SQL Database 와 NoSQL Database는 모델링을 하는 방법이 다르다. SQL 같은 경우는 Normalization (정규화)를 통해 중복데이터를 줄이고 목적별로 분리하는 것이 기본이지만, NoSQL은 그렇지 않다.

NoSQL 과 SQL의 가장 큰 차이는 SQL은 테이블을 디자인한 후에 query를 뽑아내면 되지만 NoSQL은 자유로운 Query가 불가능하고, 정해진 Primary Key ( Partition Key + Sort Key ) 를 통해서만 query가 가능하기 때문에 역순으로 설계를 해야한다.

SQL: ERD ( Entity Relational Model) -> Table -> Query
NoSQL: ERD (Entity Relational Model) -> Query Access Pattern -> Table

따라서 이와 같은 순서로 디자인을 하고 Review -> Repeat -> Review  를 반복하여 최적의 모델링을 해야한다.

DDB의 Primary Key

DDB 에서의 Primary Key 는 Partition Key, Sort Key의 조합이다.

  • Partition Key: Partition Key로 정한 Column을 hash 하여 저장하고 O(1)의 속도로 접근할 수 있도록 한다.
  • Sort Key: DDB에서는 오직 Sort Key로 정해진 Column에 대해서면 where 문을 적용할 수 있다. begins_with, <, > , >=, <=, =, between 를 사용할 수 있다.

DDB의 Table을 만들 때 Primary Key를 한 번 정하면 바꿀 수 없기 때문에 신중하게 결정할 필요가 있다.

GSI , LSI

DDB에서는 Primary Key 말고도 secondary index를 활용하여서 복수의 Pirmary Key를 만들 수가 있다.

GSI (Global Secondary Indexes)

GSI 는 새로 설정한 Partition Key, Sort Key를 기반으로 테이블을 하나 더 만드는 것과 같다고 생각하면 된다. 따라서 추가적인 비용이 LSI에 비해서 많이 드는 단점도 있지만, 언제든 만들고 지울 수 있다는 장점이 있다.

GSI 를 설정하고 사용하기 위해서는 query를 날릴 때 해당 GSI를 사용할 것이라는 것을 알려줘야 DDB가 인식하고 해당 GSI를 이용하여 query를 수행한다.

aws dynamodb query --table-name foo --index-name gsi-1 --key-condition-expression ”#op = :op AND #d between :d1 AND :d2”
--expression-attribute-names ‘{#op": ”Operator” , “#d”: “Date”}’
--expression-attribute-values ‘{“:op”: {“S”:”Liz”} , “:d1”: {“S”:”2020-04-20”}, “:d2”:{“S”:”2020-04-25”}}
  • GSI 는 table 별로 20 개까지 설정이 가능
  • RCU/WCU 를 따로 Provisioning 해줄 수 있다. ( 즉 table과 같은 provisioning을 쓰지 않음 )

LSI ( Local Secondary Indexes )

LSI 는 Primary Key와 같이 생성할 때 같이 만들어줘야한다. parition key 는 공유하고, sort key를 하나 더 추가하는 개념. 똑같이 이후에 변경이 불가능하다.

  • RCU/WCU를 테이블과 공유한다.
  • Table별 5개까지 설정이 가능
  • Collection Size 가 10GB 이하이다.

Primary Key design

AWS Workshop의 예제를 토대로 ERD -> access pattern -> query 구현을 진행해보자.

예시로 사용된 게임은 BattleGround와 같은 게임이라고 이해하면 된다.

ERD

  • User
  • Game
  • GameUserMapping

User, Game, GameUserMapping 이란 Entity를 설정한다. 각각은 유저 정보 게임정보 그리고 게임과 유저정보에 대한 매핑 정보이다.

Access Pattern

유저의 프로파일에 접근하는 경우

유저가 게임을 하면 당연히 그 유저의 프로필 정보를 만들어야 한다.
프로필에는 사용자 이름, 게임 통계 및 기타 사용자 정보가 포함되고, 이러한 정보를 사용자가 로그인할 때 보여주고, 다른 사용자가 프로필을 볼 수 도 있다.

  • 유저 프로필 생성
  • 유저 프로필 업데이트
  • 유저 프로필 읽기

위 3가지 access pattern을 추론해볼 수 있다.

pre-game 단계

게임은 온라인 멀티플레이 게임이고, 유저는 특정 맵의 게임을 만들고, 다른 유저들은 해당 게임에 접속이 가능하다. 또한 최대 플레이어는 50명으로 제한되어 있다. 게임이 시작되면 유저는 접근이 불가능하다.

접속하기 위해 게임을 찾을 때 유저는 특정 맵을 원할 수도 있고, 아닐 수도 있다.

  • 게임 만들기
  • open 게임 찾기
  • map 기반의 open 게임 찾기
  • game 정보 보기
  • game의 유저들 보기
  • game에 유저로 접속하기
  • game 시작

game 진행중

유저는 마지막으로 살아남는 플레이어가 되기 위해서 다른이들을 잡아야 한다. 각각의 유저는 얼마나 많은 유저들을 킬했는지 기록해야하고, 현재 몇명의 플레이어가 살아있는지 확인해야한다. 마지막 플레이어가 되면 보상을 얻게 된다.

  • game의 user 정보 업데이트
  • game 정보 업데이트

Design Primary Key

디자인 테이블
Entity Partition Key Sort Key
User USER#{USERNAME} #METADATA#{USERNAME}
Game GAME#{GAME ID} #METADATA#{GAME ID}
UserGameMapping GAME#{GAME ID} USER#{USERNAME}

User Entity의 Partition Key는 USER#{USERNAME} 와 같이 정의했다. 각 entity에 대한 정보 값 같은 경우에는 #METADATA# 와 같은 Prefix를 붙인 Sort Key를 사용했다. 이와 같이 설정하면 특정 USERNAME or GAME ID에 대한 정보만 알고 있다면 GetItem, PutItem, DeleteItem 를 쉽게 사용할 수 있게 된다.

여기서 User Entity의 Partition Key를 만들 때 굳이 USERNAME을 붙이지 않아도 Sort Key 만으로 검색이 가능한데 Partition Key에 USERNAME을 붙이는 이유는 even partitioning을 통한 load 분산이 목적이다. 만일 하나의 partition key에 너무 많은 rcu or wcu가 몰리게 됐을 때 각 partition key 별로는 limit 이 존재하기 때문에 문제가 생길 수 있다.

partition key 별 limit 은 wcu 1000, rcu 3000으로 table 에 대한 wcu, rcu를 아무리 높여도 이 특정 partition key 에 대한 한계는 정해져 있다. 이렇게 높은 처리량을 받게 되는 partition key를 hot partition key 라고 부른다.

Game Entity도 User와 비슷한 방식으로 이해하면 된다. 단지 GAME ID로 바뀐 것.

UserGameMapping Entity의 경우에는 Game Entity와 동일한 Partition Key를 쓰지만 Sort Key를 USER#{USERNAME}과 같은 방식으로 사용한다. 같은 Partition Key를 공유하여 single query로 game의 정보와 유저의 정보 모두를 가져올 수 있게 된다.

결론

위와 같이 기존의 RDBMS와는 다른 방식의 테이블 설계가 필요한 것이 DDB이다. 높은 처리량과 아무리 많은 request가 들어와도 일정한 응답속도를 제공하는 등 높은 성능을 보여주지만 이와 같은 성능을 올바르게 내기 위해서는 이러한 반복적인 디자인 피드백을 통해서 최적의 테이블을 설계해야한다.

Reference

https://amazon-dynamodb-labs.workshop.aws/game-player-data/core-usage/step1.html
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-design.html

profile
Developer

1개의 댓글

comment-user-thumbnail
2023년 7월 29일

정보 감사합니다.

답글 달기