들어가며
- Grab은 스마트폰 기반의 동남아시아 승차 공유 서비스이다(- 위키 백과 -).
- 지금은 그랩페이, 익스프래스, 프레쉬... 등 다양한 사업 분야 진출해 있는 소위 말하는 "슈퍼앱"이다.
- 지금은 처분했지만 한동안 그랩의 "주주"였다.
- 그래서, 조금은 뜬금없지만 언젠가 재진입할 날을 고대하며, 그랩의 테크 블로그 글 들에 대해 번역해보려 한다.
Grab Food
- 오늘 번역하려고 하는 글을 Grab의 여러 서비스들 중 Grab Food를 대상으로 한다.
https://www.grab.com/global/ko/food/
- 즉시배달, 포장.. 흡사 동남아의 "배달의 민족" 같다. 그런데, 특이한 점이 두 가지 정도 있다.
- 하나는 예약 배달이 된다는 것이고, 다른 하나는 "믹스 앤 매치" = 배달 한 건에 여러 음식점의 음식을 맛 볼 수 있는 서비스.
- 그래서 배달료는? - 안알랴즘
원문
https://engineering.grab.com/how-we-store-millions-orders
- Grab에서 OLTP와 OLAP를 어떤 식으로 구성했는지에 관련된 글
- DynamoDB를 OLTP로 MySQL RDS를 OLAP로 운영하고 있다고 함.
글 소개
- Grab App에서 음식 주문 -> 음식점에서 주문을 준비 -> 드라이버가 음식을 찾아서 고객에게 배송
- 이런 일반적인 음식 배달 서비스의 백엔드 시스템이 어떻게 구성되어 있는지 생각해본 적이 있는가?
- Grab의 주문 시스템 데이터 베이스는 효율적으로 나뉘어져 있다. 오늘은 이 시스템을 만들기 전까지 있었던 여정에 대해 다루어 보려 한다.
- 원문에는 "database solution that powers the order platform"이라는 말이 있었는데, database solution을 컴퓨터 전원과 같은 power로 설명한게 인상깊었음...
배경 지식 설명
Query Pattern
Write(쓰기) | Read(읽기) |
1. 주문 생성하기! | 1. 주문 id 조회! |
2. 주문 업데이트 하기! | 2. 고객id로 현재 진행중인 주문 알기! |
3. 과거 주문 조회하기! | |
4. 주문 통계 조회하기! |
- 여기서 Write + R1 + R2를 Transactional Query라고 하고 R3 + R4를 Analytical Query라고 하는데...
- 가장 큰 차이점은? Analytic Query는 당장 서비스를 "운영"하는데 있어서는 필요하지 않을 수도 있다!(지금 피자를 주문하는데, 옛날에 치킨을 먹었던 사실이 무슨 상관인가!)
- 반면에, Transactional Query는 당장 운영하는데 필요하다.. 예를 들어 R2.(고객id로 현재 주문 알기) 주문에 문제가 생기면.. 현재 진행중인 주문을 먼저 살펴봐야 한다.
- Transactional과 Analytic이 좀 더 궁금하신 분들은.. 아래 글을 읽어봐도 좋다..!(ㅎ헷)
Traffic Pattern
- 여기서 Traffric이란 위에서 말한 쓰기/읽기 쿼리 패턴 중 뭐가 더 많이 들어오냐!이다.
- 일단, 바쁠 때는 새로운 주문이 겁내 많이 들어 온다. 쓰기 쿼리가 읽기 쿼리보다 3배 가량 많다.
- 여기서 QPS라는 용어가 나오는데, Qquery Per Second의 약자로. 말 그대로 초당 쿼리 횟수이다.
- 즉, 위에서 새로운 주문이 겁내 많이 들어온다 = QPS가 3배다 라는 의미로, 초당 읽기 쿼리보다 쓰기 쿼리가 3배 가량 더 들어온다. 이런 의미이다.
https://jjingho.tistory.com/154
- 위 블로그에서 QPS 추정에 대한 대략적인 감을 잡을 수 있다. QPS는 사용자와 사용자의 액션에 대한 함수로, 사용자가 많고 사용자가 하는 행동이 많아질수록 QPS 값이 커진다.
- 그리고 범위 쿼리(Range Query)가 겁내 많아진다는 얘기가 같이 나오는데, 고객 단위로 현재 진행중인 주문이 몇 건인지를 살펴보는 것을 말하는 것 같다. (고객이 여러 건을 주문할 수 있으니, 고객 자체를 범위로 볼 수 있음)
- 여튼, 바쁠 때는 새로운 주문이 겁내 많이 들어와서 Write쿼리량이 > Read 쿼리량 보다 압도적이다.
그래서, DB 디자인은 어떻게?
<Stability>
- 요약하자면.. Peak시간에 무수히 쏟아져 들어오는 Write/Read쿼리도 견딜 수 잇어야 한다!
- 그리고, 일부 시스템이 죽더라도.. 고객이 주문만큼은 꼭 완료할 수 있게 해야 함.
<Scalability and cost>
- 비즈니스가 성장하는 만큼 database solution도 성장해야 한다. 여기서 성장이란 = 그만큼 더 많은 쿼리를 처리하라는 뜻!
- 근데, 이게 끝이 아님. 그러면서도 사장님 가라사대 cost effective = 돈은 덜써라!
<Consistency>
- 일단 Transactional 쿼리 = 운영계(=주문관련) 먼저 잘 처리하고! Analytic 쿼리 = 분석계(=분석해서 의사결정에 활용) 이것도 흔들림 없이 잘 처리해라!
DB솔루션!
- 일단 OLTP와 OLAP에 서로 다른 DB를 쓰자!
- OLTP DB는 주문 처리 할 때 필수!(=주문 생성, 진행중인 주문 조회 등) 그렇지만 완료된 주문까지 다 저장할 필요는 없기 때문에 상대적으로 보유 기간이 짧음!(= 금방 삭제 한다는 뜻)
- 반면, OLAP DB는 과거 데이터와! 통계치를 저장하고 있어야 하기 때문에 상대적으로 보유 기간이 길다!(=금방 삭제 하지 않는 다는 뜻)
- 왜 서로 다른 DB를 쓰는 걸까? = 독립적인 운영
- 예를 들어, 엄청 좁은 주차 공간을 두고 두 차량이 경쟁을 하는 상황을 생각해보자.
- 똑같이 일이 Database내에서도 일어 난다. 좁은 공간을 두고 두 차 모두 이러지도 저러지도 못하는 상황이 연출되는데 이를 DeadLock이라고 한다. DeadLock이 생겨버리면 저 두 차가 나가지 못하는 것처럼.. 어떤 작업도 끝낼 수 없다.
- 10분 테코톡 케빈님의 영상을 보면 "소심한"이라는 단어가 붙어 있다. 즉, 서로 쓰고 싶은 운동 기구를 상대편이 사용하고 있는데, 둘 다 소심해서.. 다 썼냐는 말을 못하면 하루 종일 똑같은 기구만 쓰다가.. 집에 간다는 그런 얘기
https://www.youtube.com/watch?v=Ry_gB34cvwc&ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC
- 왜 이런 일이 일어 날까? 한정된 주차장 공간처럼 본질적으로 서버의 자원은 유한하기 때문이다.
- 그래서, OLAP보고는 저쪽가서 혼자 놀라고 독립적인 공간을 떼어 주는 것이다.
- 그럼, OLAP가 어떤 난리를 피워도 OLTP에는 영향을 주지 않을테니까.
- 왜 이렇게 OLAP만 미워 하냐고 할 수 있지만.. 근본적으로 소를 키우는건 OLTP이다.(주문을 받아야 돈을 벌지 않겠음?)
- 그리고, OLTP는 그때 그때 들어오는 주문을 잘 처리해야 하니까 상대적으로 가볍게 유지하고 옛날 데이터는 OLAP에 저장해둔다.
- 대충 아래와 같은 방식으로 처리한다고 한다.
- 주문 플랫폼에서 고객이 액션을 하면 OLTP로 들어가서 주문을 처리하고 동시에, 일정 과정(=Data Ingestion)을 거쳐서 OLAP도 저장한다는 의미
상세 구조 - OLTP database
- 크게 두 가지 종류의 쿼리를 날린다.
- 하나는 주문 번호로 주문을 생성하는 것이고 key(주문 번호)-value(주문 내용) query
- 다른 하나는 배치 쿼리로 고객 아이디로 진행중인 주문 목록을 보여 주는 것.( 고객 아이디가 직접 노출되는건 아니지만 패스트푸드점에 대기중인 주문 번호 리스트 전광판 정도로 생각할 수 있을 것 같다.)
- 그리고 OLTP용 DB로 DynamoDB를 사용한다.
DynamoDB란?
- AWS의 NoSQL 서비스이다. 여기서 NoSQL이란? 수평적 확장이 가능한 구조의 DB를 얘기한다.
- RDB는 수직적 확장을 지원한다. 즉, 주문 번호 / 주문 내용 / 주문 고객 등과 같은 고정된 컬럼 값에 대하여 행을 지속적으로 추가하는 것이다.
- 반면, 수평적 확장이란 고정된 컬럼 값 외에 다른 값도 들어올 수 있다는 것을 의미한다.
- 위 그림의 Attributes를 보면 행 별로 OrgName + SubscriptionLevel과 UserName + Role이라는 서로 다른 컬럼 값을 가진다. 이게 만약 RDB였다면..
Primary Key | Sort Key | OrgName | SubscriptionLevel | UserName | Role |
... | ... | ... | ... | NULL | NULL |
... | ... | NULL | NULL | ... | .... |
- 위와 같이 출력할 수 있었을 것이다. 하지만, 이것도 어디까지나 고정된 컬럼값을 사전에 정의해두어야 볼 수 있는 부분이다.
- NoSQL은 속성값을 사전에 정의 하지 않고도 다 때려 박을 수 있다.(좀 더 궁금하면 아래 영상을 참조)
https://www.youtube.com/watch?v=1nE609C6-SI&ab_channel=AmazonWebServicesKorea
- 여하튼 이런 DynamoDB를 사용하는 이유는, 확장 가능성이 좋고 사용성도 좋다고 한다.
사용 이유1. three-way replication이란?
- 우선, two-way를 먼저 알아보자.
- Master DB를 본 떠서 Slave DB를 만들어두고 CRUD에서 CRD인 Insert, Update, Delete만 Master DB에서 하고
- Read(=Select)는 Slave에서 진행하여 DB 부하를 줄이는 방식.
- 반면, three-way replication은 각각의 마스터를 세 개를 만드는 것과 유사하다.
- 여하튼, 이걸 three-way나 한다는 건 replication을 기본적으로 세 번 한다는 의미. 정확히는 모르겠지만 아래 글에 보면 서로 다른 Zone으로 copy를 세 번 한다는 얘기가 나온다.
https://www.amazon.science/blog/lessons-learned-from-10-years-of-dynamodb
- 당연히, 카피 할 때마다 돈도 많이 나올텐데 왜 세 번이나..? replication을 할까?
- 킹오브 파이터를 하면 세 명의 캐릭터를 고를 수 있는데.. 두 명이 먼저 죽어도 나머지 한명으로 게임을 진행할 수 있다.
- DB도 마찬가지 아닐까? 두명의 OLTP 마스터가 쓰러지면.. 나머지 OLTP 마스터라도 주문을 완료하자는 강력한 의지가 반영된게 아닐까 싶다. 그만큼 안정적이라는 의미.
https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy
https://www.quora.com/Why-is-replication-factor-always-3-for-building-distributed-systems
- 위 글들에서도 비슷한 의미를 찾을 수 있다.
사용 이유2. strong consistency란?
- 정보의 일관성으로 생각할 수 있다. Dynamo DB에서 strong consistency를 모드를 사용하면
- 항상, 최신 데이터를 읽을 수 있다는 의미이다.
- 이게 무슨말이냐면, 데이터를 업데이트 하고 있는 중에 데이터를 조회하면 변경 전 데이터 버젼을 볼 수도 있고, 이로 인해 정보의 일관성이 떨어질 수 있다.
- 반면, strong consistency모드를 사용하면 데이터 업데이트를 먼저하고 읽기를 진행(= 즉 읽기가 후순위)하기 때문에 항상 최신 정보를 일관성있게 확인할 수 있다.
사용 이유3. adaptive capacity
- 우선, partition key가 뭔지 정렬 key가 뭔지 대략 알고 있어야 한다.
- partition key는 고객, sort key는 주문 일자이다. 그리고, 속성으로 orderid가 있는데 nosql이니까 상품명 주문량등 다양한 정보가 들어올 수 있다.
- 여튼, partition key를 고객이라 두었으니 예를 들어 customername을 삼성전자라고 둬보자. 23년 1월 국민연금 가입 기준 임직원 수가 11만 8천명 정도이다.
- 삼성전자에서 grab food로 대량 주문을 했다고 가정하자. 그러면, 삼성전자 partition으로 traffic(=쿼리 요청)이 어마어마하게 증가할 것이다.
- 이때, DynamoDB를 사용하면 유연하게 Partition의 처리 용량을 조절할 수 있다고 한다.
- 위 그림에 잘 설명되어 있듯이, 삼성전자(=partition4)의 쿼리 요청량이 급증하면 그만큼 유연하게 partition4의 읽기/쓰기 쿼리 처리 용량을 늘려준다.
GSI사용법
- GSI는 Globaly Second Index의 약자이다. 일단, 왜 이런 구조가 나왔는지 먼저 이해를 해야 하는데
- 앞서 얘기 했듯 Attribute는 고정된 값이 아니기 때문에 기본적으로 쿼리는 Key단위로 날릴 수 있다.
- 그런데, Attribute값을 기준으로 쿼리를 날려야 하는 경우가 있다.
- 예를 들어서, 위와 같은 데이터가 있다고 가정하면, Award 속성의 Champ를 먹은 경우만 출력한다고 해보자.
- 이를 위해서 Award속성을 partition Key로 바꿔 주는 것이다.
- 그럼 짜잔 이렇게 champ를 먹은 경기만 알 수 있다.
- 여하튼 GSI를 쓰면 이렇게 Attribute를 partition key로 가지는 테이블을 복제해서 좀 더 편리하게 쿼리를 날릴 수 있다.
Sparse Index를 유지하는 전략
- 위 예시에서 sort key는 game_id이고 gsi로 award를 사용했는데, 자세히 살펴보면 모든 game_id마다 award가 있지는 않다. 이러한 구조를 sparse index라고 한다. award가 듬성 듬성 있다는 의미
- 여튼, sparse index면 조회 범위가 적으니 당연히 더 빨리 쿼리를 실행할 수 있다.
- grab food에서 특정 고객의 현재 진행중인 주문을 살펴보는 쿼리를 생각해보자.
- 여기서 gsi를 쓴다는건 pax_id를 gsi -> pk로 바꾼다는 의미인데 pax_id를 그대로 pk로 사용하지 않고 pax_id_gsi를 pk로 사용한다고 한다.
- 그 이유는 completed된 경우 pax_id_gsi에서 삭제하기 때문이다.(completed 됐다고 해서 바로 pax_id를 삭제할 수는 없다.)
- 이렇게 하면 앞서 말한 것 처럼 ongoing 주문이 듬성 듬성 있게 되고 -> sparse index 구조의 장점을 살려 더 효율적으로 쿼리를 날릴 수 있게 된다.
TTL 적용 전략
- TTL이란? Time to Live의 약자로 data 만료 기간을 자동으로 설정해주는 기능이다.
- 그런데, big table에 TTL 기능을 진행하면 그 자체로도 큰 비용이 들 수 있다.
- 그렇기 때문에 big table 같은 경우에는 TTL 적용 전 수동으로 오래된 기간 데이터를 삭제하고 나면
- 더 줄어든 데이터에 TTL을 적용해서 비용을 아낄 수 있음.
상세 구조 - OLAP database
- Aurora 대신에 MySQL RDS를 사용함.
- Aurora에 큰 돈을 쓸만큼 비즈니스 요구량이 크지 않음(Aurora가 MySQL보다 비쌈)
- RDS를 이용하여 저장소 비용만 내고 조회 비용은 최소화
- Partitioning은 Monthly 단위로 하고 6개월 지난 데이터는 삭제
Data ingestion pipeline
- kafka를 사용함. 99.95% SLA 때문에(대충 10000분 중에 9995분 간은 서비스 작동을 보장한다는 의미)
- 작동 안하는 5분은? stream 데이터를 받지 못함. 이 때는 Amazon Simple Queue Service(SQS)를 이용해서 한번 더 시도해보고
- 그래도 안되면 DLQ에 저장해 두었다가 추후 재요청
- 중복 메시지랑 잘못된 메시지를 처리하는 것도 중요
- 중복 메시지는 database에서 order id + createdate와 같은 unique key 조합으로 처리
- 잘못된 메시지의 경우 두 과정으로 처리
- 1. microseconds 단위로 데이터를 최신 상태로 유지
- 2. Upsert를 사용 = 중복되는 값이 있으면 Update를 하고 중복되는 값이 없으면 Insert를 함.
결론 및 향후 계획
- Aurora에서 RDS + table size조절 전략을 통해 비용을 많이 줄임
- DynamoDB를 쓰니까 확실히 OLTP의 안정성과 용이성을 보장해줌.
- OLAP로 RDS를 사용하면서 늘어나는 비즈니스 요구사항에 효과적으로(=비용 절감) 대응함
- data 유지 기간을 설정하면서 비용을 절감함
- OLTP / OLAP DB는 분리되어 있지만, Source는 하나로 사용하며 일관성을 유지함
- MySQL RDS를 OLAP로 쓰고 있는데, Text 서치가 어려움. ElasticSearch같은 NoSQL 데이터베이스로 넘어갈까 고민.
제 블로그에 와주셔서 감사합니다! 다들 오늘 하루도 좋은 일 있으시길~~
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!