이 강연에서는 NoSQL 데이터베이스인 DynamoDB를 활용하기 위해 개발 실무자가 알아야 할 실용적 지식을 소개해 드립니다. 테이블을 설계하고 모범사례를 도입해 최적화된 성능과 비용으로 고가용성 데이터베이스 아키텍처를 구축하는 방법에 대해 말씀드린 뒤 다양한 용도로 DynamoDB를 활용하고 계시는 한국 고객들의 사례에 대해 소개하도록 하겠습니다.
연사: 김일호, 아마존 웹서비스 솔루션즈 아키텍트
LG 이노텍 - Amazon Redshift Serverless를 활용한 데이터 분석 플랫폼 혁신 과정 - 발표자: 유재상 선임, LG이노...
2017 AWS DB Day | 개발자가 알아야 할 Amazon DynamoDB 활용법
1. DynamoDB for developers
AWS 김일호, Solutions Architect
SM Entertainment, 이상욱, IT Service Development
Buzzvil, 서주은, Manager
2. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
3. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
4. Local secondary index (LSI)
Alternate range key attribute
Index is local to a hash key (or partition)
A1
(hash)
A3
(range)
A2
(table key)
A1
(hash)
A2
(range)
A3 A4 A5
LSIs A1
(hash)
A4
(range)
A2
(table key)
A3
(projected)
Table
KEYS_ONLY
INCLUDE A3
A1
(hash)
A5
(range)
A2
(table key)
A3
(projected)
A4
(projected)
ALL
10 GB max per hash key,
i.e. LSIs limit the # of ran
ge keys!
5. Global secondary index (GSI)
Alternate hash (+range) key
Index is across all table hash keys (partitions)
A1
(hash)
A2 A3 A4 A5
GSIs A5
(hash)
A4
(range)
A1
(table key)
A3
(projected)
Table
INCLUDE A3
A4
(hash)
A5
(range)
A1
(table key)
A2
(projected)
A3
(projected) ALL
A2
(hash)
A1
(table key)
KEYS_ONLY
RCUs/WCUs provisioned
separately for GSIs
Online indexing
6. How do GSI updates work?
Table
Primary ta
ble
Primary ta
ble
Primary ta
ble
Primary ta
ble
Global Seco
ndary Index
Client
2. Asynchronous
update (in progress)
If GSIs don’t have enough write capacity, table writes will be throttled!
7. LSI or GSI?
LSI can be modeled as a GSI
If data size in an item collection > 10 GB, use GSI
If eventual consistency is okay for your scenario, use
GSI!
8. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
9. Scaling
Throughput
Provision any amount of throughput to a table
Size
Add any number of items to a table
Max item size is 400 KB
LSIs limit the number of range keys due to 10 GB limit
Scaling is achieved through partitioning
10. Throughput
Provisioned at the table level
Write capacity units (WCUs) are measured in 1 KB per second
Read capacity units (RCUs) are measured in 4 KB per second
RCUs measure strictly consistent reads
Eventually consistent reads cost 1/2 of consistent reads
Read and write throughput limits are independent
WCURCU
11. Partitioning math
Number of Partitions
By Capacity (Total RCU / 3000) + (Total WCU / 1000)
By Size Total Size / 10 GB
Total Partitions CEILING(MAX (Capacity, Size))
12. Partitioning example
Table size = 8 GB, RCUs = 5000, WCUs = 500
RCUs per partition = 5000/3 = 1666.67
WCUs per partition = 500/3 = 166.67
Data/partition = 10/3 = 3.33 GB
RCUs and WCUs are uniformly sprea
d across partitions
Number of Partitions
By Capacity (5000 / 3000) + (500 / 1000) = 2.17
By Size 8 / 10 = 0.8
Total Partitions CEILING(MAX (2.17, 0.8)) = 3
13. Allocation of partitions
A partition split occurs when
Increased provisioned throughput settings
Increased storage requirements
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html
16. Getting the most out of DynamoDB throughput
“To get the most out of
DynamoDB throughput, create
tables where the hash key
element has a large number of
distinct values, and values are
requested fairly uniformly, as
randomly as possible.”
—DynamoDB Developer Guide
Space: access is evenly
spread over the key-
space
Time: requests arrive
evenly spaced in time
17. What causes throttling?
If sustained throughput goes beyond provisioned throughput per partition
Non-uniform workloads
Hot keys/hot partitions
Very large bursts
Mixing hot data with cold data
Use a table per time period
From the example before:
Table created with 5000 RCUs, 500 WCUs
RCUs per partition = 1666.67
WCUs per partition = 166.67
If sustained throughput > (1666 RCUs or 166 WCUs) per key or partition, DynamoDB may
throttle requests
Solution: Increase provisioned throughput
18. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
19. 1:1 relationships or key-values
Use a table or GSI with a hash key
Use GetItem or BatchGetItem API
Example: Given an SSN or license number, get
attributes
Users Table
Hash key Attributes
SSN = 123-45-6789 Email = johndoe@nowhere.com, License = TDL25478134
SSN = 987-65-4321 Email = maryfowler@somewhere.com, License = TDL78309234
Users-Email-GSI
Hash key Attributes
License = TDL78309234 Email = maryfowler@somewhere.com, SSN = 987-65-4321
License = TDL25478134 Email = johndoe@nowhere.com, SSN = 123-45-6789
20. 1:N relationships or parent-children
Use a table or GSI with hash and range key
Use Query API
Example:
Given a device, find all readings between epoch X, Y
Device-measurements
Hash Key Range key Attributes
DeviceId = 1 epoch = 5513A97C Temperature = 30, pressure = 90
DeviceId = 1 epoch = 5513A9DB Temperature = 30, pressure = 90
21. N:M relationships
Use a table and GSI with hash and range key elements
switched
Use Query API
Example: Given a user, find all games. Or given a game,
find all users.
User-Games-Table
Hash Key Range key
UserId = bob GameId = Game1
UserId = fred GameId = Game2
UserId = bob GameId = Game3
Game-Users-GSI
Hash Key Range key
GameId = Game1 UserId = bob
GameId = Game2 UserId = fred
GameId = Game3 UserId = bob
22. Documents (JSON)
New data types (M, L, BOOL,
NULL) introduced to support
JSON
Document SDKs
Simple programming model
Conversion to/from JSON
Java, JavaScript, Ruby, .NET
Cannot index (S,N) elements
of a JSON object stored in M
Only top-level table attributes
can be used in LSIs and
GSIs without
Streams/Lambda
JavaScript DynamoDB
string S
number N
boolean BOOL
null NULL
array L
object M
24. Rich expressions
Projection expression to get just some of the attributes
Query/Get/Scan: ProductReviews.FiveStar[0]
ProductReviews: {
FiveStar: [
"Excellent! Can't recommend it highly enough! Buy it!",
"Do yourself a favor and buy this." ],
OneStar: [
"Terrible product! Do not buy this." ] }
]
}
26. Rich expressions
Conditional expression
Put/Update/DeleteItem
attribute_not_exists (#pr.FiveStar)
attribute_exists(Pictures.RearView)
1. First looks for an item whose primary key matches that of the
item to be written.
2. Only if the search returns nothing is there no partition key
present in the result.
3. Otherwise, the attribute_not_exists function above fails and
the write will be prevented.
27. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
36. DynamoDB Accelerator (DAX)
DynamoDB
Cache
Your Applications
Traditional side cache
DynamoDB
DynamoDB Accelerator(DAX)
Your Applications
Caching made simple
New!
Microseconds response times at millions of reads/sec from single DAX cluster
38. GameId Date Host Opponent Status
d9bl3 2014-10-02 David Alice DONE
72f49 2014-09-30 Alice Bob PENDING
o2pnb 2014-10-08 Bob Carol IN_PROGRESS
b932s 2014-10-03 Carol Bob PENDING
ef9ca 2014-10-03 David Bob IN_PROGRESS
Games Table
Multiplayer online game data
Hash key
39. Query for incoming game requests
DynamoDB indexes provide hash and range
What about queries for two equalities and a range?
SELECT * FROM Game
WHERE Opponent='Bob‘
AND Status=‘PENDING'
ORDER BY Date DESC
(hash)
(range)
(?)
40. Secondary Index
Opponent Date GameId Status Host
Alice 2014-10-02 d9bl3 DONE David
Carol 2014-10-08 o2pnb IN_PROGRESS Bob
Bob 2014-09-30 72f49 PENDING Alice
Bob 2014-10-03 b932s PENDING Carol
Bob 2014-10-03 ef9ca IN_PROGRESS David
Approach 1: Query filter
BobHash key Range key
41. Secondary Index
Approach 1: Query filter
Bob
Opponent Date GameId Status Host
Alice 2014-10-02 d9bl3 DONE David
Carol 2014-10-08 o2pnb IN_PROGRESS Bob
Bob 2014-09-30 72f49 PENDING Alice
Bob 2014-10-03 b932s PENDING Carol
Bob 2014-10-03 ef9ca IN_PROGRESS David
SELECT * FROM Game
WHERE Opponent='Bob'
ORDER BY Date DESC
FILTER ON Status='PENDING'
(filtered out)
43. Important when:
Use query filter
Send back less data “on the wire”
Simplify application code
Simple SQL-like expressions
AND, OR, NOT, ()
Your index isn’t entirely selective
45. Secondary Index
Approach 2: composite key
Opponent StatusDate GameId Host
Alice DONE_2014-10-02 d9bl3 David
Carol IN_PROGRESS_2014-10-08 o2pnb Bob
Bob IN_PROGRESS_2014-10-03 ef9ca David
Bob PENDING_2014-09-30 72f49 Alice
Bob PENDING_2014-10-03 b932s Carol
Hash key Range key
46. Opponent StatusDate GameId Host
Alice DONE_2014-10-02 d9bl3 David
Carol IN_PROGRESS_2014-10-08 o2pnb Bob
Bob IN_PROGRESS_2014-10-03 ef9ca David
Bob PENDING_2014-09-30 72f49 Alice
Bob PENDING_2014-10-03 b932s Carol
Secondary Index
Approach 2: composite key
Bob
SELECT * FROM Game
WHERE Opponent='Bob'
AND StatusDate BEGINS_WITH 'PENDING'
48. Sparse indexes
Id
(Hash)
User Game Score Date Award
1 Bob G1 1300 2012-12-23
2 Bob G1 1450 2012-12-23
3 Jay G1 1600 2012-12-24
4 Mary G1 2000 2012-10-24 Champ
5 Ryan G2 123 2012-03-10
6 Jones G2 345 2012-03-20
Game-scores-table
Award
(Hash)
Id User Score
Champ 4 Mary 2000
Award-GSI
Scan sparse hash GSIs
49. Important when:
Replace filter with indexes
Concatenate attributes to form useful
secondary index keys
Take advantage of sparse indexes
You want to optimize a query as much
as possible
Status + Date
50. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
51. Stream of updates to a
table
Asynchronous
Exactly once
Strictly ordered
Per item
Highly durable
Scale with table
24-hour lifetime
Sub-second latency
DynamoDB Streams
52. View type Destination
Old image—before update Name = John, Destination = Mars
New image—after update Name = John, Destination = Pluto
Old and new images Name = John, Destination = Mars
Name = John, Destination = Pluto
Keys only Name = John
View types
UpdateItem (Name = John, Destination = Pluto)
54. DynamoDB Streams
Open Source Cross-Region
Replication Library
Asia Pacific (Sydney) EU (Ireland) Replica
US East (N. Virginia)
Cross-region replication
57. Analytics with DynamoDB Streams
Collect and de-dupe data in DynamoDB
Aggregate data in-memory and flush periodically
Performing real-time aggregation and analytics
EMR
Redshift
DynamoDB
59. Agenda
Tip 1. DynamoDB Index(LSI, GSI)
Tip 2. DynamoDB Scaling
Tip 3. DynamoDB Data Modeling
Scenario based Best Practice
DynamoDB Streams
Use-case 1: SM Entertainment
Use-case 2: Buzzvil
60. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Sangwook Lee
Dept. IT Service Development, SM Entertainment
61. DynamoDB 기반 Serverless 개발 사례
FanBook.me
노래방의 Culture Technology로 도약
에브리싱 소개
누적 다운로드 : 700만
누적 가창 : 3억 회 돌파
수상경력 : 15’ Google Play 올해의 앱
지원언어 : 한,중,일,영,태국어
디바이스 : 웹, 모바일, 크롬캐스트
서비스국가 : 한국,일본,태국,영국,필리핀
대표 제휴 : SBS 판타스틱 듀오 공식 앱
62. DynamoDB 기반 Serverless 개발 사례
FanBook.me
팬북 소개
Fan & Art Social Platform
팬의 아트워크 기반 소셜 플랫폼
오픈 시기 : 16년 하반기 런칭
디바이스 : 웹, 앱(Google Play, AppStore)
작가 : 수 천명의 다양한 국적의 작가
작품 : 약 4만개의 작품 등록
주요 서비스 : 중국, 일본 및 동남아시아
63. DynamoDB 기반 Serverless 개발 사례
FanBook.me
팬북 AWS 적용 주요 서비스
AWS Services
컴퓨팅 : EC2, Lambda
데이타베이스 : RDS, DynamoDB, ElasticCache
네트워크 : VPC, CloudFront, Route53, API Gateway
분석 : Kinesis, CloudSearch, Elasticsearch, EMR
Data Pipeline, Athena
메시지 : SQS, SES, SNS
보안 및 인증 : IAM
리전 : Tokyo
언어 : Spring Java, Node.js
Amazon
RDS
Amazon
ElastiCache
Amazon
DynamoDB
Amazon
CloudFront
Amazon
S3
Amazon
CloudWatch
Amazon
Elasticsearch Service
Amazon
EMR
Amazon
Kinesis
AWS Data
Pipeline
Amazon
VPC
Amazon
Route 53
Amazon
EC2
AWS
Lambda
Elastic
Load Balancing
AWS IAM Amazon
API Gateway
Amazon
SNS
Amazon
SES
Amazon
SQS
64. DynamoDB 기반 Serverless 개발 사례
FanBook.me
DynamoDB 기반 Serverless 개발 사례
목차
- DynamoDB와 EMR을 통한 통계 분석
- DynamoDB Stream과 Lamdba 이용한 집계 처리
- Athena와 함께 적용하여 분석 효율성 증가
- Push Notification과 DynamoDB 활용
65. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Activity Logs 분석 활용
DynamoDB, EMR과 Athena로 분석
- 방문, 좋아요, 댓글, 해시 태그 등 User의 Activity Logs 수집
- 비 정기적 분석은 DynamoDB에서 EMR 분석
- 정기적인 분석은CloudWatch Events를
통해 Lambda에서 Athena 분석
- Athena는 S3 분석 기반이며 Presto Query제공
현재 US-East, US-West2 Region 제공
Amazon
Athena
AWS
Lambda
Amazon
DynamoDB
Amazon
EC2
분
석
로그 수집
User
Amazon
RDS
Amazon
S3
Amazon
CloudWatch
Events
Amazon
EMR
로그 수집
(Presto)
DynamoDB
Stream
트리거 함수
스케쥴 이벤트 발생
보고서 데이터 생
성
콘텐트 노출
66. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Serverless Microservice
- Timeout : 100ms ~ 5분
- Memory : 128~1536MB
- VPC 구성 가능
- 외부 환경 변수 저장 가능
- Java, Nodejs, Python, C#
Amazon
Athena
Amazon
Kinesis
AWS
Lambda
Amazon
DynamoDB Stream
주요 Serverless AWS 서비스
Real-time Stream for DynamoDB
- Data Retention : 24 hours
- Manage Stream으로 활성화
- Region 간 복제 가능
- 데이터 변경 감지
Real-time Stream Data
- Data Retention : Max 7 Day
- Read 2mb/s, Write 1mb/s
- KCL Library Client 개발
- Shard 개수 무제한
Query data in S3 using SQL
- S3 파일기반 직접 쿼리
- Presto Query 제공
- 병렬방식 지원
- ETL 불필요
67. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Activity Logs 분석 – DynamoDB EMR 분석
1. 비 정기적인 Bigdata 분석 EMR 구성 2. 마스터 노드 연결
2. Hive, Presto Query 및 분석 결과 저장
Select ~~
68. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Activity Logs 분석 – DynamoDB Stream 집계 Lambda 설정
1. Lambda EventName “INSERT”, “DELETE” 집계처리
View type – New and old Image 설정
69. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Activity Logs 분석 – DynamoDB Stream 집계 Lambda 설정
2. 로그 테이블에 Trigger 설정 3. 집계 테이블에 업데이트 완료
70. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Activity Logs 분석 – Athena SDK Lambda 설정
2. CloudWatch Events Rule 생성 3. Lambda Triggers 생성1. Athena Lambda Code 등록
71. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Activity Logs 분석 – Athena SDK Lambda 설정
3. Athena Query State 확인
4. 모니터링 정상 확인 – Error 발생 시 alarm 연동
- Presto Query 기반
72. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Serverless 기반으로 운영
- 여러 작가들의 작품 업로드 시 Follower 및 기타 이벤트 Push 관리
- Kinesis를 통한 이벤트 감지하고 Client 프로세스 처리
- Lambda를 통한 다양한 조건의 Push 필터의 반영
및 DynamoDB 로그 입력
- 최소한의 시스템 운영으로 Cost 낮추기
Parse 서버를 제외한 나머지는 Serverless 개발
Push Notification과 DynamoDB 활용
Amazon
Kinesis
AWS
Lambda
Amazon
DynamoDB
Amazon
RDS
Amazon
EC2
Parse
작가
Notification Setting 관
리
Notification Logs 관리
Push 필
터
Event HandlerEvent 감지데이터 등록
serverles
s
73. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Push Notification과 DynamoDB 활용
1. 데이터 등록 이벤트 감시를 위한 Kinesis 등록하고
Lambda에서 Kinesis를 Trigger로 연동한다
2. Kinesis Event Lambda Client 프로세스 코드 개발
Push 발송
74. DynamoDB 기반 Serverless 개발 사례
FanBook.me
Push Notification과 DynamoDB 활용
3. Push Notification 필터 및 Parse 발송 4. DynamoDB Push Notification 로그 입력 확인
75. DynamoDB 기반 Serverless 개발 사례
FanBook.me
DynamoDB 다시 보기
적용 전 고려사항
- WCU, RCU 및 Provisioned Throughput 계산
(Capacity 1일 4회 Scale-down 가능)
- 인덱스 구성의 수 (LSI 5개, GSI 5개)
- LSI 인덱스도 Provisioned Throughput을 사용
- 최대 400kb의 문자열 길이
- Partition Key 구성 제약은 복합키로 해결하자
- Hot Key 주의 및 Cold Table과 Hot Table을 섞지 마라
- Scan 작업 피하기
- 순간 트래픽으로 높은 Read에 대한 대응
DynamoDB DAX 검토. 현재 Preview
( readCapacityUnits / 3,000 ) + ( writeCapacityUnits / 1,000 )
= initialPartitions
참고
http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguid
e
1 RCU = 4KB Read/sec, 1 WCU = 1KB/sec 계산
증가된 Partition 할당 계산하기
78. 포인트 시스템의 요구사항01
78
● 필수
○ 포인트 적립
○ 포인트 차감
○ 남은 포인트 확인
○ 포인트 적립 히스토리 확인
● 옵션
○ 총 쌓은 포인트 확인
○ n개월 이상 미사용 포인트 확인
○ 특정 시점에 남아있는 포인트 확인
Copyright ⓒ All Right Reserved by Buzzvil
79. 포인트 시스템의 요구사항01
79
● 다양한 포인트 타입
○ 잠금해제/랜딩 적립금
○ 액션형 광고 참여 적립금
○ 추천인 적립금
○ 스토어 구매 차감
○ 스토어 구매 후 공유 적립금
○ 오퍼레이터가 적립해주는 수동 적립금
○ ...
Copyright ⓒ All Right Reserved by Buzzvil
80. 허니스크린 초기 포인트 테이블02
80Copyright ⓒ All Right Reserved by Buzzvil
Impression point table
user_id amount type title campaign_id date
1 2 landing xxx 123 2017-12-12
2 4 unlock xxx 333 2017-12-13
user_id amount type title related_user_id date
3 300 inviter xxx 2 2017-12-12
2 300 invitee xxx 3 2017-12-13
Invite point table
user_id amount title date
3 300 xxx 2017-12-12
2 300 xxx 2017-12-13
Manual point table
● n개의 테이블
● 유저의 적립 히스토리를 가져오기 위해
서는 n개의 테이블 모두에 쿼리
81. 허니스크린 초기 포인트 테이블02
81Copyright ⓒ All Right Reserved by Buzzvil
point_sum table
user_id current_point_sum
1 5000
2 20000
● 유저별로 현재 남아있는 포인트 총 합을 저장하는
point_sum 테이블 관리
● user_id는 unique key
● 적립시 current_point_sum에 더하고 차감시
current_point_sum에서 뺀다
82. 허니스크린 초기 포인트 테이블02
82Copyright ⓒ All Right Reserved by Buzzvil
point_sum table
user_id deposit_sum withdraw_sum
1 10000 5000
2 20000 0
● current_point_sum 컬럼을 두 컬럼으로 분리
○ 총 쌓았던 포인트 = deposit_sum
○ 총 사용한 포인트 = withdraw_sum
○ 현재 남은 포인트 = deposit_sum - withdraw_sum
● 현재 남은 적립금 뿐만 아니라 지금까지 총 쌓은 포인
트와 지금까지 사용한 총 포인트도 알 수 있다
83. 허니스크린 초기 포인트 테이블02
83
● 22번 유저에게 10 포인트 적립
○ begin transaction
○ insert into imp_point (user_id, amount) values (22, 10)
○ update point_sum set deposit_sum = deposit_sum + 10 where
user_id = 22
○ commit
● 22번 유저로부터 300 포인트 차감
○ raise exception if (deposit_sum - withdraw_sum) < 300
○ begin transaction
○ insert into withdraw_point (user_id, amount) values (22, 300)
○ update point_sum set withdraw_sum = withdraw_sum + 300
where user_id = 22
○ commit
Copyright ⓒ All Right Reserved by Buzzvil
84. 허니스크린 초기 포인트 테이블02
84
● 문제점
○ 트랜잭션을 쓰긴 했지만 철저하게 관리하지 못하다 보니 각 point
table과 point sum table에 일관성이 깨지기 시작
○ 포인트 타입별 테이블이 너무 많아져서 관리하기 힘들고 타입 추가
할 때 마다 테이블을 생성해야하는 불편함
○ 포인트 적립 히스토리 조회시 n개의 테이블에 조회를 해야해서 성능
저하 발생
○ 특정 시점에 남아있던 포인트 현황 파악이 힘듦
○ 포인트 차감 요청이 동시에 들어오는 경우 남은 포인트가 음수가 되
는 경우가 발생
Copyright ⓒ All Right Reserved by Buzzvil
86. 포인트 시스템 리팩토링03
86
● 타입 별 여러개의 포인트 테이블
○ 스키마가 명확하고 각 포인트 타입에 따른 인덱스를 다르게 설정 가능
○ 인덱스 추가 없이 특정 타입에 대한 포인트 검색이 빠름
○ 적립내역 조회시 n개의 테이블에 쿼리를 보내야함
○ 포인트 타입이 많아짐에 따라 관리가 어려움
● 하나의 포인트 테이블
○ 컬럼만 보고 용도를 알기가 힘듦
○ 타입에 따라 컬럼의 내용이 달라지기 때문에 foreign key를 걸기 힘듦
○ 특정 포인트 타입에 필요한 컬럼이 여러개인 경우 전체 테이블 변경 필요
or JSON field 활용
○ 포인트 적립내역 조회가 빠름
○ 하나의 테이블만 관리하면 됨
○ user_id, amount, title, type, created_at을 제외한 나머지 컬럼은 부가정
보라고 생각하면 스키마를 정규화하지 않았다는 죄책감에서 조금은 벗어
날 수 있다
Copyright ⓒ All Right Reserved by Buzzvil
87. 포인트 시스템 리팩토링03
87Copyright ⓒ All Right Reserved by Buzzvil
General point table
user_id amount title type sub_type refer_key date extra
2 1000 xxx imp l 123 2017-12-12 {"abc:"def"}
2 200 xxx action xxx 333 2017-12-13
2 -300 xxx withdraw store1 2 2017-12-13 {"ddd":"fff"}
● 스키마
○ amount: 포인트를 쌓는 경우 +, 차감하는 경우 -
○ type: 포인트 타입
○ title: 유저에게 보여줄 포인트 이름
○ sub_type: 같은 포인트 타입 안에서 추가로 타입을 지정하기 위해
사용
○ refer_key: 적립과 연관된 캠페인 아이디, 추천인 아이디, 주문 아이
디 등 대표적인 참조키로 쓸 만한 정보 저장
○ date: 포인트 적립 시간
○ extra: 그 외 필요한 추가 필드를 json 형태로 저장
88. 포인트 시스템 리팩토링03
88Copyright ⓒ All Right Reserved by Buzzvil
General point table
user_id amount title type sub_type refer_key date extra deposit_sum withdraw_sum
2 1000 xxx imp l 123 2017-12-12 {"abc": "def"} 1000 0
2 200 xxx action xxx 333 2017-12-13 1200 0
2 -300 xxx withdraw store1 2 2017-12-13 {"pid": 123} 1200 -300
2 100 xxx action 22 2 2017-12-14 1300 -300
2 100 xxx invite invitee 23 2017-12-14 1400 -300
● point_sum 테이블도 합치자
● 통장처럼 각각의 레코드에서
deposit_sum/withdraw_sum를 중복 기입
● 별도의 point_sum 테이블이 필요 없어짐
● 특정 시점에 쌓인 적립금 확인 가능
● 혼란을 방지하기 위해 withdraw_sum은 음수로 관리
89. 포인트 시스템 리팩토링03
89Copyright ⓒ All Right Reserved by Buzzvil
General point table
● 총 적립 포인트 = last_point.deposit_sum
● 총 사용 포인트 = -last_point.withdraw_sum
● 현재 남은 포인트 = last_point.deposit_sum +
last_point.withdraw_sum
user_id amount title type sub_type refer_key date extra deposit_sum withdraw_sum
2 1000 xxx imp l 123 2017-12-12 {"abc": "def"} 1000 0
2 200 xxx action xxx 333 2017-12-13 1200 0
2 -300 xxx withdraw store1 2 2017-12-13 {"pid": 123} 1200 -300
2 100 xxx action 22 2 2017-12-14 1300 -300
2 100 xxx invite invitee 23 2017-12-14 1400 -300
90. 포인트 시스템 리팩토링03
90Copyright ⓒ All Right Reserved by Buzzvil
General point table
● 4 포인트 적립
○ new_point.amount = 4
○ new_point.deposit_sum = last_point.deposit_sum + new_point.amount
○ new_point.withdraw_sum = last_point.withdraw_sum
○ new_point.save()
● 10 포인트 차감
○ new_point.amount = -10
○ new_point.deposit_sum = last_point.deposit_sum
○ new_point.withdraw_sum = last_point.withdraw_sum + new_point.amount
○ if new_point.deposit_sum + last_point.withdraw_sum < 0 then raise exception
○ new_point.save()
user_id amount title type sub_type refer_key date extra deposit_sum withdraw_sum
2 1000 xxx imp l 123 2017-12-12 {"abc": "def"} 1000 0
91. 포인트 시스템 리팩토링03
91Copyright ⓒ All Right Reserved by Buzzvil
General point table
● Atomicity
○ 한 유저에 대해 동시에 포인트 적립 요청이 온다면?
○ last_point.deposit_sum = 10 인경우
○ new_point_1.deposit_sum = last_point.deposit_sum + 4
○ new_point_2.deposit_sum = last_point.deposit_sum + 2
○ 최종적으로 deposit_sum이 16이 되어야 하나 new_point_1,
new_point_2가 저장된 순서에 따라 14또는 12가 된다
user_id amount title type sub_type refer_key date extra deposit_sum withdraw_sum
2 1000 xxx imp l 123 2017-12-12 {"abc": "def"} 1000 0
92. 포인트 시스템 리팩토링03
92Copyright ⓒ All Right Reserved by Buzzvil
● 동시 접근을 막기위한 방어로직
○ user_id를 lock key로 사용하여 동시접근 제한
○ Optimistic locking
93. 포인트 시스템 리팩토링03
93Copyright ⓒ All Right Reserved by Buzzvil
● Optimistic locking
○ 동시접근을 방지하기위한 하나의 방법
○ 매 변경마다 값이 1씩 증가하는 version 컬럼을 추가
○ 수정시 마지막에 읽은 버전에 1을 더하고 conditional update를 통
해 쓰려고 하는 버전과 같은 버전이 존재하는 경우 업데이트 하지 않
음
○ lock/unlock을 안쓰기 때문에 contention이 적은경우 매우 효율적
○ 대표적으로 UI에서 같은 객체를 여러 사람이 수정하는 경우에 한쪽
변경사항이 반영 안되는 문제를 방지 할 수 있다
94. 포인트 시스템 리팩토링03
94Copyright ⓒ All Right Reserved by Buzzvil
● 포인트 시스템에는 어떻게 적용할까?
○ user_id/version에 unique constraint를 건다 => 같은 유저에 대해
같은 버전으로 write 시도하는 경우 integrity error 발생
○ 마지막에 쌓인 포인트에서 version을 1 증가 시킨 값으로 새 포인트
를 저장
● 수도 코드
○ last_point = Point.objects.filter(user_id=123).last()
○ new_point.user_id = 123
○ new_point.amount = 10
○ new_point.deposit_sum = last_point.deposit_sum + 10
○ new_point.withdraw_sum = last_point.withdraw_sum
○ new_point.version = last_point.version + 1
○ new_point.save()
○ save()에서 integrity exception 발생시 처음부터 다시 수행
95. 포인트 시스템 리팩토링03
95Copyright ⓒ All Right Reserved by Buzzvil
General point table (Final)
user_id amount title type sub_type refer_key date extra deposit_sum withdraw_sum version
2 1000 xxx imp l 123 2017-12-12 {"abc": "def"} 1000 0 1
2 200 xxx action xxx 333 2017-12-13 1200 0 2
2 -300 xxx withdraw store1 2 2017-12-13 {"pid": 123} 1200 -300 3
2 100 xxx action 22 2 2017-12-14 1300 -300 4
2 100 xxx invite invitee 23 2017-12-14 1400 -300 5
● 내가 생성한 new_point가 항상 마지막에 생성되어있
던 last_point의 다음 버전으로 생성되는 것을 보장
96. DynamoDB 활용04
96Copyright ⓒ All Right Reserved by Buzzvil
● DynamoDB 활용
○ MySQL로도 구현이 가능하나 안정적이고 쉽게 확장 가능
한 시스템을 만들기 위해 AWS DynamoDB를 사용하기
로 결정
○ DynamoDB는 일종의 NoSQL로 Linear한 확장이 가능
○ 인덱스 개수에 제한이 있고 인덱스가 늘어날 때마다 비용
이 증가한다
○ hash key/range key를 이용한 conditional update를 통
해 optimistic locking을 구현 할 수 있다
97. 97Copyright ⓒ All Right Reserved by Buzzvil
● 통계
○ DynamoDB에서 직접 통계 쿼리를 돌릴수는 없음
○ 날짜기준으로 정렬된 global secondary index를
추가했고 이를 이용해 데이터를 주기적으로 다시
읽어서 redshift로 이전 후 통계 처리
○ S3에 데이터 저장 후 redshift load 명령 사용
○ 직접 구현 또는 firehose 사용
DynamoDB 활용04
98. 98Copyright ⓒ All Right Reserved by Buzzvil
● DynamoDB 장점
○ Fully managed service
○ 안정적으로 수평 확장 가능
○ 평균 응답시간 5ms 이내로 속도가 매우 빠르다
○ 예약용량 구매시 모든 테이블에 적용 가능해 관리
가 용이
○ 예약용량 할인 폭이 큼(최대 76%까지)
DynamoDB 활용04
99. 99Copyright ⓒ All Right Reserved by Buzzvil
● DynamoDB 사용시 고려해야할 점
○ 사용 패턴에 따라 비용 차이가 많이 난다 -> 공부를
많이 해야한다 -> row 크기 / 인덱스에 따른
capacity 사용량 계산 공식이 복잡함
○ 파티셔닝 동작을 이해하지 못하고 사용하면 구매한
capacity를 완전히 활용하지 못할 수 있다
○ 테이블이 많아짐에 따라 capacity 예측 및 관리가
어려움
○ Daily snapshot이 안되는 것이 아쉽다
○ 아주 가끔 Internal server error가 발생하는 경우
가 있다
DynamoDB 활용04
100. 100Copyright ⓒ All Right Reserved by Buzzvil
● 마무리하며
○ 버즈빌에서는 DynamoDB를 적극 활용 중
○ MySQL을 완전히 대체하는 것이 아닌 대량의 트래
픽이 예상되고 실시간 처리가 필요한 부분에 대해
서만 적용
DynamoDB 활용04