SlideShare a Scribd company logo
김효준 AB180
Luft:


10초만에 10억 데이터를
쿼리하는 데이터스토어 개발기
발표자 소개
김효준


Lead of Data R&D Team, AB180





다양한 기술을 사용자 경험에 녹여내,


뛰어난 제품을 만드는 데 관심이 많습니다.
백엔드
안드로이드
딥러닝 블록체인
데이터 엔지니어링
CONTENTS
1. 배경
2. Luft 소개
3. 개발기
3.0. 프로젝트 시작과, 우리의 철학
3.1. TrailDB
3.2. LLVM으로 쿼리 엔진 만들기
3.3. 연산 레이어 직접 만들기
3.4. 샤딩 구현하기
4. What’s next?
이런 로고들이 어느정도 익숙하신 분들이 들으시면 좋습니다.
1. 배경
1.1 Airbridge에 대해
웹과 앱에서의 유저 행동을 분석해 마케팅 성과를 측정하는 모바일 애트리뷰션 애널리틱스
누적 디바이스 수
5,000만+
월 평균 이벤트 수
100억+
1.2 Airbridge의 새로운 기능 개발
코호트 분석 기능을 도입해, 폭넓고 유용한 유저 행동 분석 기능을 제공하자!
Retention
Funnel
TrendUsers who
Purchased
morethan
3
times
타겟 유저군을 자유자재로 잡아서, 다양한 분석 리포트 제공
타겟 유저군은 자유자재로 설정될 수 있어야 함
예시: 6월 한달동안 매 주마다 10000원 이상 소비했다가,
7월 이후로 한번도 앱을 열지 않은 “우수회원” 등급의 유저


기술적 니즈:
- 유저 ID에 대한 GROUP BY 쿼리가 자주, 빠르게 일어날 수 있어야 함
- 시계열 분석을 용이하게 할 수 있어야 함
1.4 기술적인 니즈 분석하기
설정하자마자 리포트가 즉시 보여져야 함
기술적 니즈: 실시간! 빠르게!
e.g. 나이키를 5월에 3번 이상 구매한 고객의 6월 일별 아디다스 소비 금액
1.5 기존 솔루션의 한계
Druid : 너무 느림 (16-20초 소요)
- 심지어 Pre-Aggregation 방식의 한계로 복잡한 행동분석 쿼리가 불가능
데이터 웨어하우스 : 비용 비효율적
- SQL로 행동분석 쿼리는 가능, 하지만 너무 범용적이라 여전히 느리다
- 빠르게 만들려면? 💸 🤭 💸
1.6 자체 개발의 필요성
기존 솔루션을 테스트해보면서, 자체 개발의 필요성에 도달


비용 비효율성 해소
- 대규모 스케일의 데이터 웨어하우스로 원하는 성능을 낼 수 있지만 비용 비효율적
- 반면 자체 개발시 OLAP 쿼리 특수성으로 인해 최적화 여지가 무궁무진 (후술)
쿼리 다양성
- 퍼널 분석 등의 고도화된 행동 분석은 SQL로 표현하기에 한계가 존재함
- 따라서 높은 커스텀이 필요하지만, Spark 등을 쓰기엔 느림
그래서 직접 만들었습니다.
2. Luft 소개
2.1. Luft
유저 행동 분석에 최적화된 실시간 OLAP 데이터스토어 by AB180
- Fast: 5~10초 내로 수억 이벤트를 스캔해 쿼리 제공
- Real-Time: 람다 아키텍쳐로 실시간 데이터 처리
- High Availability: 데이터는 샤딩되고 S3에 저장됨
- Cloud Native: 클라우드에 직접 연동되어 유연한 스케일링 및 확장
2.2. Performance
- 코호트, 리텐션, 트렌드 분석 등을 10-15초 이내로 제공
- Druid와 비슷하거나 작은 규모 클러스터로 이를 제공
​
0초 2.5초 5초 7.5초 10초
9.31초
4.06초
0.66초
2020-05-27일부터 4주동안 구매 금액이 5000원 이상인 사람의 모수 추출 쿼리를 동일 클러스터 (4core * 4, 32GB) 스펙에서 캐시 없이 진행
2.3. Key Points
- Immutability: Luft 내 데이터는 불변하다.
- 이벤트 데이터는 로그성 데이터처럼 한번 쌓이면 변하지 않기 때문
- 그래도 데이터 보정이 필요하다면 덮어쓰는 방식으로 수정 가능


- TrailDB: 이벤트 데이터 저장에 최적화된 데이터 저장 포맷
- 일반적인 DBMS는 데이터 수정을 위해 B+Tree, Skip-List 등을 사용
- 하지만 불변성을 전제한다면? 성능을 위한 특수 자료구조 적용 가능
2.4. Architecture
Go로 개발되었으며, gRPC로 통신함
- 마스터 노드: API 서버 제공 및 쿼리 스케줄링 & 결과 취합
- 히스토리컬 노드: 샤딩된 데이터 저장 및 쿼리 연산
- 리얼타임 노드: 실시간 스트림 구독 및 쿼리 연산
- Lambda Architecture
Realtime Node
Kafka Consumer
Historical Node
Partition some-table/
2020-09-01_2020-09-02/


2020-09-05T18:43:01Z/1
Partition some-table/
2020-09-02_2020-09-03/


2020-09-06T18:43:01Z/1
Master
이벤트 데이터는 유저별로 파티셔닝된 채로 인덱싱됨
Raw Data 유저 ID별 파티셔닝
installuser1
addToCartuser2
installuser3
purchaseuser2
installuser1
user2
user3
user4
purchase
addCart addCart purch
launch
install
like
addCart
Partition


2
0
2
0
-
0
9
-
0
1
Partition


2
0
2
0
-
0
9
-
0
2
Partition


2
0
2
0
-
0
9
-
0
3
Partition


2
0
2
0
-
0
9
-
0
4
이벤트 발생 시각 기준으로 한번 더 묶어, 파티션 단위로 만든다
Partition some-table/
2020-09-01_2020-09-02/


2020-09-05T18:43:01Z/1
Partition some-table/
2020-09-02_2020-09-03/


2020-09-06T18:43:01Z/1
Partition some-table/
2020-09-03_2020-09-04/


2020-09-07T11:11:11Z/1
Partition some-table/
2020-09-03_2020-09-04/


2020-09-07T11:11:11Z/2
Partition some-table/
2020-09-04_2020-09-05/


2020-09-08T22:22:22Z/1
만들어진 파티션은 S3에 업로드되고, 히스토리컬 노드에 분배됨
Historical Node
1
Partition some-table/
2020-09-01_2020-09-02/


2020-09-05T18:43:01Z/1
Partition some-table/
2020-09-02_2020-09-03/


2020-09-06T18:43:01Z/1
Partition some-table/
2020-09-03_2020-09-04/


2020-09-07T11:11:11Z/2
Historical Node
2
Partition some-table/
2020-09-03_2020-09-04/


2020-09-07T11:11:11Z/1
Partition some-table/
2020-09-04_2020-09-05/


2020-09-08T22:22:22Z/1
Historical Node
3
분배된 파티션은 리얼타임 노드의 데이터와 합쳐져
나중에 마스터 노드에 의해 쿼리됨
Historical Node
1
Partition some-table/
2020-09-01_2020-09-02/


2020-09-05T18:43:01Z/1
Partition some-table/
2020-09-02_2020-09-03/


2020-09-06T18:43:01Z/1
Partition some-table/
2020-09-03_2020-09-04/


2020-09-07T11:11:11Z/2
Historical Node
2
Partition some-table/
2020-09-03_2020-09-04/


2020-09-07T11:11:11Z/1
Partition some-table/
2020-09-04_2020-09-05/


2020-09-08T22:22:22Z/1
Historical Node
3
Realtime Node
Master
Kafka Consumer
더 궁금하다면? abit.ly/luft 🔗
3. 개발기
3.0. 프로젝트 시작과, 우리의 철학
우리에겐 오버엔지니어링할 여유가 없다.


하늘 아래 새로운 건 없기 때문에,
- 이미 솔루션이 있으면 최대한 차용
- 없다면, 기존에 왜 문제를 못 풀었을 지 생각하고
- 이제 무엇이 바뀌어 문제를 풀 수 있게 되었을지 분석
R&D를 통한 초기 방향성 잡기
기존에 왜 문제를 못 풀었는지 생각하자.
- Druid, Spark, Kafka, CockroachDB등의 오픈소스 분석 및 인사이트 추출
지금 무엇이 바뀌었기 때문에 문제를 풀 수 있을지 분석하자.
- 주요 변화: CPU 코어 수, SIMD, 메모리 용량, I/O 속도, 클라우드
그러나 Pre-Aggregation 방식은 우리에게 맞지 않다!
“정해진 Metric을 시간 단위별로 사전에 미리 계산해놓고,
쿼리 시엔 필요한 값만 선택해 합산 (Roll-up)해서 결과를 빠르게 제공한다”
= High-Cardinal Shuffle에 취약
= 정해진 종류의 한정된 분석만 가능
따라서 Pre-Aggregation을 쓰지 않고, 그때그때 계산하는 게 베스트라는 결론
그때그때 계산도 문제는 여전하다
유저 행동 분석 쿼리는 필연적으로 계산 결과를 유저별로 Group By하게 됨


한편 MapReduce에서의 가장 큰 Bottleneck은 Shuffling이다.
데이터가 흩어져 저장되어 있을수록 Shuffle 비용이 기하급수적으로 증가함
-> 파티션 키의 Cardinality가 높을수록 Shuffle 비용이 기하급수적으로 증가!
유저 ID는 대표적인 High-Cardinality 칼럼
= 따라서 Group By User를 수행할 시 Shuffle 비용이 매우 커진다.
Shuffle을 최적화하는 방법: 파티셔닝을 잘 해서 Shuffle을 국소적으로 만들자.
즉, 유저 ID로 파티셔닝될 수 있는 스토리지를 찾으면 되겠다!
3.1. TrailDB의 발견
3.2. LLVM으로 쿼리 엔진 개발
3.3. 연산 레이어를 직접 만들기
3.4. 샤딩 구현하기
Query Engine
LRMR
Luft
Shard Controller
STORAGE LAYER
COMPUTATION LAYER
APPLICATION LAYER
We’re here! TrailDB
3.1. TrailDB
· Adroll에서 2015년에 공개한 타임시리즈 이벤트 저장 Rowstore
· 유저 ID 기반 파티셔닝에 최적화된 유일한 스토리지 포맷


· 인덱스가 없다! 무조건 풀스캔해야 한다.
· 뭐 이런게 다 있ㅇ…😡
· 맥북 15” (Core i7, 2.6Ghz @ 6core) 기준 0.51초
· Snowflake에선 8.1초, Druid에선 1.1초
근데 풀스캔 성능이… 너무 좋네? 🤨
충격적으로 높은 압축률이 비결
­ CSV로 13GB짜리 데이터가 TrailDB론 ~300mb
­ 이런 압축률에도 여전히 Time Complexity는 O(N)이다.
­ 어차피 Time Complexity가 O(N)일거, Space Complexity를 극한으로 줄여버리자.
­ 사이즈가 작으면 메모리에도 잘 들어가요.
­ SSD고 뭐고, RAM Sequential Read가 최고


TrailDB, 왜 이렇게 빠를까?
98%
유저 이벤트 특성을 반영해 설계한 데이터 구조
­ 델타 인코딩: 유저의 이벤트를 시간순으로 정렬해,


이전 이벤트 대비 늘어난 시간 값만 저장


­ 딕셔너리 인코딩: 값을 사전화시켜 그 ID만 저장
Event #
1
Event Timestamp: Delta-Encoded
Data: Dictionary-Encoded
…
Event #
2
Event Timestamp: Delta-Encoded
…
User ID
…
…
TrailDB, 왜 이렇게 빠를까?
??? : 아무리 그래도 Rowstore 주제에 칼럼스토어보다 빠를 수가 있어?
Time view 100 TRUE
John


Doe
foo 72
like 150 FALSE bar
buy
Raw Data
Time view 100 TRUE
John


Doe
foo 72
Time like 150 FALSE
John


Doe
bar 72
Time buy 150 FALSE
John


Doe
bar 72
Edge-Encoding: 이전 이벤트 대비 바뀐 칼럼만 넣자.
어차피 대부분의 사용자 속성은 변하지 않으니까
TrailDB는 Columnstore같은 Rowstore
Event #1
Event #2
Event #3
Raw Data
Time view 100 TRUE
John


Doe
foo 72
Time like 150 FALSE
John


Doe
bar 72
Time buy 150 FALSE
John


Doe
bar 72
Δt
Event #1 Event #2 Event #3
Edge-Encoding: 이전 이벤트 대비 바뀐 칼럼만 넣자.
어차피 대부분의 사용자 속성은 변하지 않으니까
TrailDB는 Columnstore같은 Rowstore
Event #1
Event #2
Event #3
BuyΔt like 150 FALSE barTime view 100 TRUE
John


Doe
foo 72
Immutability: 수정 불가능함
­ 물론 Mutability를 포기하고 다른걸 얻자는 아이디어는 이미 대세 (e.g. HDFS, RDD..)


Simplicity: Full-featured DB가 아닌 LevelDB같은 스토리지 포맷에 가깝다.
­ 쿼리 엔진이나 샤딩, 복제, 클러스터링 같은 기능이 없음


그래도 이걸 스토리지 엔진으로 삼아 데이터스토어를 만들어볼 만 하다!
물론, 공짜 점심은 없다 🍔
· Less Space Complexity leads to Less Time Complexity
· Edge Encoding, Dict Encoding을 통한 중복 제거
· Rowstore에서 칼럼 순서는 상관없기 때문에,


최빈순으로 정렬해 Data Entropy를 줄임
· Immutability를 통해서 할 수 있는게 많아진다
· 기존의 RDBMS에서 할 수 없던 극한의 압축률
· 시간축으로 정렬해 저장해 Sequential I/O 속도 장점 살림
· OLAP 데이터스토어기 때문에, 수정 용이성을 과감히 버릴 수 있었기 때문
Lessons Learned from TrailDB
3.1. TrailDB의 발견
3.2. LLVM으로 쿼리 엔진 개발
3.3. 연산 레이어를 직접 만들기
3.4. 샤딩 구현하기
TrailDB
Query Engine
LRMR
Luft
Shard Controller
STORAGE LAYER
COMPUTATION LAYER
APPLICATION LAYER
We’re here!
3.2. LLVM으로 쿼리 엔진 개발
· TrailDB라는 스토리지가 생겼으니


쿼리 기능을 짜야 할 때


· Predicate Pushdown:

스토리지 레벨에 필터를 적용해

필요한 데이터만 읽는 기법


· Luft의 쿼리는 Go에서 파싱되므로,


C/C++단 TrailDB 쿼리 엔진에 넘겨야 함
· TrailDB는 OR-AND 형식의 Equals (=) 쿼리만을 지원한다!
· 난데없이 생겨버린 요구사항 2가지
1. TrailDB의 쿼리 엔진을 확장해 다양한 연산자 지원
2. Go로 파싱한 쿼리를 C언어로 가져오기


· 레이어화를 시킬 땐 Separation-of-Concern이 확실해야 한다.
· 따라서 이런 식으로 중복 코드를 절대 만들고 싶지 않았음
TrailDB 쿼리 기능의 한계
추후 기능 확장에 제약사항!
관리 포인트가 많이 늘어남.
· PostgreSQL에서 쿼리를 LLVM JiT으로 컴파일한다는 걸 리서치 중 발견
· LLVM JiT은 IR 코드를 주면 즉시 최적화 및 컴파일해 실행할 수 있음.
· 잠깐, 우리의 모든 문제점을 해결해줄 기술인데?
· Go 레벨에서 LLVM IR 코드만 생성해 넘기면
· C/C++에서 JiT 컴파일해서 실행해버리면 된다.


상상도 못한 돌파구, LLVM JiT
쿼리는 기능 확장이 빈번하기 때문에 C/C++로 짜면 추후 개발 비용 .
근데 JiT 쿼리엔진은 한번만 만들어두면 나머진 Go로 짤 수 있음 .
용을 잡아볼만 하다!
Query
Compile Scan Data
Parse Query


Generate Code
Go C/C++ C/C++
새로운 JiT 쿼리 엔진의 구조
· 일반적으론 성능 최적화 때문에 JiT을 도입하지만,


개발 비용때문에 JiT을 도입했다는 특수한 시나리오
· 상식: 복잡한 기술 스택 도입은 오버엔지니어링이다.
· 교훈: 복잡한 기술 스택 도입을 적시에 한다면 오히려 개발 비용 절감 가능
· 어차피 유지보수는 힘드니, 기능 확장성을 고려


Lessons Learned from JiT
3.1. TrailDB
3.2. LLVM으로 쿼리 엔진 개발
3.3. 연산 레이어를 직접 만들기
3.4. 샤딩 구현하기
TrailDB
Query Engine
LRMR
Luft
Shard Controller
STORAGE LAYER
COMPUTATION LAYER
APPLICATION LAYER
We’re here!
· 연산 레이어에서 흩어진 데이터를 하나로 모음


· 이런 연산 레이어로 MapReduce가 많이 쓰여요.
· 근데 우리는 Go라서 못 써요.


· Spark를 겨우 붙여서 벤치마크해봤지만, 성능이 그닥
· 애초에 Spark / Hadoop은 Long-running Job에 최적화된 프레임워크
3.3. 연산 레이어를 직접 만들기
그래서 직접 만들었습니다.
Open-Sourced on github.com/ab180/lrmr 🔗
예시: LRMR로 분산화한 리텐션 분석 쿼리
빠른 개발을 위해
· gRPC + Protobuf + etcd라는 검증된 조합 선택
· 익숙한 Spark의 디자인을 많이 차용


쿼리와 같은 Short-running Job에 최적화하기 위해
- Idea: 과감히 Resiliency를 포기하자.
- 저널링이나 체크포인팅을 포기하는 대신, 성능 극한으로 높이고 개발 비용 절감
- 만약 장애가 발생하면?
- 처음부터 다시 하자! 어차피 10초 미만
Design Goals of LRMR
구조적 문제 발생 : Stream Backpressure
프로덕션에서 대규모 데이터를 쿼리하다 보니, LRMR 단에서 버퍼 오버플로가 자주 발생
- Push-based Stream에서 흔히 볼 수 있는 Backpressure 문제
- 프로듀서 속도를 컨슈머가 따라오지 못해 버퍼에만 쌓이다 터진다.
🐰


Fast


Producer
🐢


Slow


Consumer
Push
Laaaaaaaaaag
해결 : Pull-based Event Stream!
· Idea: 컨슈머가 처리할 수 있는 양만 그때그때 요청하면 어떨까?
· ✅ 속도에 맞게 요청할 수 있어 Backpressure 없음
· ✅ 스트림 오프셋을 컨슈머가 관리하므로, 추후 체크포인트나 저널링 도입도 용이
· Kafka, Reactive Streams, Armeria 등이 채택한 방식
🐰


Fast


Producer
Pull N tasks
🦄


Slow


Consumer
3.1. TrailDB
3.2. LLVM으로 쿼리 엔진 개발
3.3. 연산 레이어를 직접 만들기
3.4. 샤딩 구현하기
TrailDB
Query Engine
LRMR
Luft
Shard Controller
STORAGE LAYER
COMPUTATION LAYER
APPLICATION LAYER
Finally!
3.4. 샤딩 구현하기
Luft에서의 샤드 = “히스토리컬 노드”이다.
즉 샤딩 = 파티션을 여러 히스토리컬 노드에 분배하는 과정
Idea: 파티션의 날짜 범위를 샤딩 키값으로 사용하자
- 모든 쿼리에 반드시 시간 범위가 붙음 → 데이터 필터링 용이
- 같은 시간 범위엔 비슷한 용량의 데이터가 들어 있음 → 데이터 분산 용이
새로운 파티션을 만들 때마다 Round-Robbin으로 각 샤드에 분배


문제: 분산 환경은 아름답지 않다.
- 노드가 다운되거나 새로 추가되면?
- 노드의 저장 공간이 꽉 차면?
- 일시적 장애로 인해 파티션이 한 샤드에 쏠려버리면?
Rebalance, Time Decay, Eviction 등의 고도화된 정책 니즈 제기됨
버전 1 : Vanilla Sharding
· 정책적 의도대로 Cost Function을 세우고, Cost가 제일 낮은 노드에 자원을 배치함
· 일반적으로 스케줄러를 짤 때 많이 사용되는 방법


· 우리의 정책적 의도 : 날짜 범위가 겹치지 않게 파티션을 분배하고 싶다!
· 두 파티션의 날짜 범위가 가깝고 겹칠수록 코스트가 높게 하면 된다.






· 이것까지 직접 만들긴 어렵다. Druid의 Cost Function 공식을 베이스 삼아 커스텀
버전 2 : Cost Function을 사용하자
etcd로 샤드의 장애 상황을 효과적으로 관리
- Liveness Probe 패턴: 샤드 정보에 TTL을 걸고 주기적으로 갱신
- 장애 시엔 TTL 갱신이 안되니까 자연스럽게 샤드 목록에서 삭제됨
- Watch 기능으로 감지해 빠른 파티션 리밸런싱


클라우드 = 최고의 가용성 머신
- S3에 파티션을 저장해두고 필요한 것만 로드
- DynamoDB를 통해 파티션 목록을 관리
샤드의 가용성을 위한 끊임없는 노력
4. What’s Next?
4.1. 현재의 Luft
- Airbridge의 프로덕션에서 운영 중
- 실시간 코호트 모수 추출 및 리텐션 분석만을 현재 수행 중
- 4대의 c5.2xlarge 인스턴스만으로 최대 15초 내에 500GB 규모의 데이터 스캔
4.1. 앞으로 해야 할 일
다양한 종류의 쿼리 지원
- 실시간 퍼널 분석을 10대 이내의 클러스터만으로 수행하는 것이 목표
- 고도화된 GROUP BY와 JOIN 기능
Spark 지원
- 데이터 분석의 확장성과 추후 ML 연동 등을 위해


그리고… Open Source!
4.2. Ziegel : Columnstore by AB180
TrailDB를 대체할 자체 칼럼스토어
- TrailDB의 한계점을 극복하고 핵심 인사이트 계승
Design Goals
- 데이터 기반 데이터스토어 설계

- Bitmap Index 도입으로 사전에 유저 속성 기반 필터링
- SIMD와 멀티코어 최적화
4.3. 다음에 다뤄보고 싶은 이야기
- 새로운 칼럼스토어 설계를 하며 얻은 데이터 엔지니어링적 인사이트
- 더 많은 프로덕션 경험과 장애 대응
- 구조적 SPOF를 줄여나간 이야기
- 극한의 Go 성능 최적화에 대한 경험
...만약 이런 이야기들을 직접 경험해보고 싶으시다면…?
Luft를 함께 만들어나가실 분을 찾습니다.
dev-recruit.ab180.co/database-engineer
감사합니다.
김효준 AB180

More Related Content

What's hot

webservice scaling for newbie
webservice scaling for newbiewebservice scaling for newbie
webservice scaling for newbie
DaeMyung Kang
 
화성에서 온 개발자, 금성에서 온 기획자
화성에서 온 개발자, 금성에서 온 기획자화성에서 온 개발자, 금성에서 온 기획자
화성에서 온 개발자, 금성에서 온 기획자
Yongho Ha
 
BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편
BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편
BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편
Seongyun Byeon
 
SPAのルーティングの話
SPAのルーティングの話SPAのルーティングの話
SPAのルーティングの話
ushiboy
 
코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
Jiyeon Seo
 
[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기
[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기
[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기
CONNECT FOUNDATION
 
Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례
Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례
Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례
Jemin Huh
 
무정지&무점검 서버 개발과 운영 사례
무정지&무점검 서버 개발과 운영 사례무정지&무점검 서버 개발과 운영 사례
무정지&무점검 서버 개발과 운영 사례
Taehyun Kim
 
나의 이직 이야기
나의 이직 이야기나의 이직 이야기
나의 이직 이야기
종립 이
 
강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019
강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019
강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019
devCAT Studio, NEXON
 
[164] pinpoint
[164] pinpoint[164] pinpoint
[164] pinpoint
NAVER D2
 
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
Hyojun Jeon
 
Zend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探るZend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探るYoshio Hanawa
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
NAVER D2
 
역삼역, 이마트 AI_v최종.pdf
역삼역, 이마트 AI_v최종.pdf역삼역, 이마트 AI_v최종.pdf
역삼역, 이마트 AI_v최종.pdf
DeukJin Jeon
 
아라한사의 스프링 시큐리티 정리
아라한사의 스프링 시큐리티 정리아라한사의 스프링 시큐리티 정리
아라한사의 스프링 시큐리티 정리
라한사 아
 
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games ConferenceKGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
Xionglong Jin
 
[236] 카카오의데이터파이프라인 윤도영
[236] 카카오의데이터파이프라인 윤도영[236] 카카오의데이터파이프라인 윤도영
[236] 카카오의데이터파이프라인 윤도영
NAVER D2
 
실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략
YEONG-CHEON YOU
 
[211]대규모 시스템 시각화 현동석김광림
[211]대규모 시스템 시각화 현동석김광림[211]대규모 시스템 시각화 현동석김광림
[211]대규모 시스템 시각화 현동석김광림
NAVER D2
 

What's hot (20)

webservice scaling for newbie
webservice scaling for newbiewebservice scaling for newbie
webservice scaling for newbie
 
화성에서 온 개발자, 금성에서 온 기획자
화성에서 온 개발자, 금성에서 온 기획자화성에서 온 개발자, 금성에서 온 기획자
화성에서 온 개발자, 금성에서 온 기획자
 
BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편
BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편
BigQuery의 모든 것(기획자, 마케터, 신입 데이터 분석가를 위한) 입문편
 
SPAのルーティングの話
SPAのルーティングの話SPAのルーティングの話
SPAのルーティングの話
 
코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
 
[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기
[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기
[부스트캠프 Tech Talk] 진명훈_datasets로 협업하기
 
Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례
Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례
Spring boot 를 적용한 전사모니터링 시스템 backend 개발 사례
 
무정지&무점검 서버 개발과 운영 사례
무정지&무점검 서버 개발과 운영 사례무정지&무점검 서버 개발과 운영 사례
무정지&무점검 서버 개발과 운영 사례
 
나의 이직 이야기
나의 이직 이야기나의 이직 이야기
나의 이직 이야기
 
강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019
강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019
강성훈, 실버바인 대기열 서버 설계 리뷰, NDC2019
 
[164] pinpoint
[164] pinpoint[164] pinpoint
[164] pinpoint
 
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
 
Zend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探るZend OPcacheの速さの秘密を探る
Zend OPcacheの速さの秘密を探る
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
 
역삼역, 이마트 AI_v최종.pdf
역삼역, 이마트 AI_v최종.pdf역삼역, 이마트 AI_v최종.pdf
역삼역, 이마트 AI_v최종.pdf
 
아라한사의 스프링 시큐리티 정리
아라한사의 스프링 시큐리티 정리아라한사의 스프링 시큐리티 정리
아라한사의 스프링 시큐리티 정리
 
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games ConferenceKGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
 
[236] 카카오의데이터파이프라인 윤도영
[236] 카카오의데이터파이프라인 윤도영[236] 카카오의데이터파이프라인 윤도영
[236] 카카오의데이터파이프라인 윤도영
 
실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략
 
[211]대규모 시스템 시각화 현동석김광림
[211]대규모 시스템 시각화 현동석김광림[211]대규모 시스템 시각화 현동석김광림
[211]대규모 시스템 시각화 현동석김광림
 

Luft : 10억 데이터를 10초만에 쿼리하는 DB 개발기

  • 1. 김효준 AB180 Luft: 
 10초만에 10억 데이터를 쿼리하는 데이터스토어 개발기
  • 2. 발표자 소개 김효준 
 Lead of Data R&D Team, AB180 
 

 다양한 기술을 사용자 경험에 녹여내, 
 뛰어난 제품을 만드는 데 관심이 많습니다. 백엔드 안드로이드 딥러닝 블록체인 데이터 엔지니어링
  • 3. CONTENTS 1. 배경 2. Luft 소개 3. 개발기 3.0. 프로젝트 시작과, 우리의 철학 3.1. TrailDB 3.2. LLVM으로 쿼리 엔진 만들기 3.3. 연산 레이어 직접 만들기 3.4. 샤딩 구현하기 4. What’s next?
  • 4. 이런 로고들이 어느정도 익숙하신 분들이 들으시면 좋습니다.
  • 6. 1.1 Airbridge에 대해 웹과 앱에서의 유저 행동을 분석해 마케팅 성과를 측정하는 모바일 애트리뷰션 애널리틱스 누적 디바이스 수 5,000만+ 월 평균 이벤트 수 100억+
  • 7. 1.2 Airbridge의 새로운 기능 개발 코호트 분석 기능을 도입해, 폭넓고 유용한 유저 행동 분석 기능을 제공하자! Retention Funnel TrendUsers who Purchased morethan 3 times 타겟 유저군을 자유자재로 잡아서, 다양한 분석 리포트 제공
  • 8.
  • 9. 타겟 유저군은 자유자재로 설정될 수 있어야 함 예시: 6월 한달동안 매 주마다 10000원 이상 소비했다가, 7월 이후로 한번도 앱을 열지 않은 “우수회원” 등급의 유저 
 기술적 니즈: - 유저 ID에 대한 GROUP BY 쿼리가 자주, 빠르게 일어날 수 있어야 함 - 시계열 분석을 용이하게 할 수 있어야 함 1.4 기술적인 니즈 분석하기 설정하자마자 리포트가 즉시 보여져야 함 기술적 니즈: 실시간! 빠르게!
  • 10. e.g. 나이키를 5월에 3번 이상 구매한 고객의 6월 일별 아디다스 소비 금액
  • 11. 1.5 기존 솔루션의 한계 Druid : 너무 느림 (16-20초 소요) - 심지어 Pre-Aggregation 방식의 한계로 복잡한 행동분석 쿼리가 불가능 데이터 웨어하우스 : 비용 비효율적 - SQL로 행동분석 쿼리는 가능, 하지만 너무 범용적이라 여전히 느리다 - 빠르게 만들려면? 💸 🤭 💸
  • 12. 1.6 자체 개발의 필요성 기존 솔루션을 테스트해보면서, 자체 개발의 필요성에 도달 
 비용 비효율성 해소 - 대규모 스케일의 데이터 웨어하우스로 원하는 성능을 낼 수 있지만 비용 비효율적 - 반면 자체 개발시 OLAP 쿼리 특수성으로 인해 최적화 여지가 무궁무진 (후술) 쿼리 다양성 - 퍼널 분석 등의 고도화된 행동 분석은 SQL로 표현하기에 한계가 존재함 - 따라서 높은 커스텀이 필요하지만, Spark 등을 쓰기엔 느림
  • 15. 2.1. Luft 유저 행동 분석에 최적화된 실시간 OLAP 데이터스토어 by AB180 - Fast: 5~10초 내로 수억 이벤트를 스캔해 쿼리 제공 - Real-Time: 람다 아키텍쳐로 실시간 데이터 처리 - High Availability: 데이터는 샤딩되고 S3에 저장됨 - Cloud Native: 클라우드에 직접 연동되어 유연한 스케일링 및 확장
  • 16. 2.2. Performance - 코호트, 리텐션, 트렌드 분석 등을 10-15초 이내로 제공 - Druid와 비슷하거나 작은 규모 클러스터로 이를 제공 ​ 0초 2.5초 5초 7.5초 10초 9.31초 4.06초 0.66초 2020-05-27일부터 4주동안 구매 금액이 5000원 이상인 사람의 모수 추출 쿼리를 동일 클러스터 (4core * 4, 32GB) 스펙에서 캐시 없이 진행
  • 17. 2.3. Key Points - Immutability: Luft 내 데이터는 불변하다. - 이벤트 데이터는 로그성 데이터처럼 한번 쌓이면 변하지 않기 때문 - 그래도 데이터 보정이 필요하다면 덮어쓰는 방식으로 수정 가능 
 - TrailDB: 이벤트 데이터 저장에 최적화된 데이터 저장 포맷 - 일반적인 DBMS는 데이터 수정을 위해 B+Tree, Skip-List 등을 사용 - 하지만 불변성을 전제한다면? 성능을 위한 특수 자료구조 적용 가능
  • 18. 2.4. Architecture Go로 개발되었으며, gRPC로 통신함 - 마스터 노드: API 서버 제공 및 쿼리 스케줄링 & 결과 취합 - 히스토리컬 노드: 샤딩된 데이터 저장 및 쿼리 연산 - 리얼타임 노드: 실시간 스트림 구독 및 쿼리 연산 - Lambda Architecture Realtime Node Kafka Consumer Historical Node Partition some-table/ 2020-09-01_2020-09-02/ 
 2020-09-05T18:43:01Z/1 Partition some-table/ 2020-09-02_2020-09-03/ 
 2020-09-06T18:43:01Z/1 Master
  • 19. 이벤트 데이터는 유저별로 파티셔닝된 채로 인덱싱됨 Raw Data 유저 ID별 파티셔닝 installuser1 addToCartuser2 installuser3 purchaseuser2 installuser1 user2 user3 user4 purchase addCart addCart purch launch install like addCart
  • 20. Partition 2 0 2 0 - 0 9 - 0 1 Partition 2 0 2 0 - 0 9 - 0 2 Partition 2 0 2 0 - 0 9 - 0 3 Partition 2 0 2 0 - 0 9 - 0 4 이벤트 발생 시각 기준으로 한번 더 묶어, 파티션 단위로 만든다 Partition some-table/ 2020-09-01_2020-09-02/ 
 2020-09-05T18:43:01Z/1 Partition some-table/ 2020-09-02_2020-09-03/ 
 2020-09-06T18:43:01Z/1 Partition some-table/ 2020-09-03_2020-09-04/ 
 2020-09-07T11:11:11Z/1 Partition some-table/ 2020-09-03_2020-09-04/ 
 2020-09-07T11:11:11Z/2 Partition some-table/ 2020-09-04_2020-09-05/ 
 2020-09-08T22:22:22Z/1
  • 21. 만들어진 파티션은 S3에 업로드되고, 히스토리컬 노드에 분배됨 Historical Node 1 Partition some-table/ 2020-09-01_2020-09-02/ 
 2020-09-05T18:43:01Z/1 Partition some-table/ 2020-09-02_2020-09-03/ 
 2020-09-06T18:43:01Z/1 Partition some-table/ 2020-09-03_2020-09-04/ 
 2020-09-07T11:11:11Z/2 Historical Node 2 Partition some-table/ 2020-09-03_2020-09-04/ 
 2020-09-07T11:11:11Z/1 Partition some-table/ 2020-09-04_2020-09-05/ 
 2020-09-08T22:22:22Z/1 Historical Node 3
  • 22. 분배된 파티션은 리얼타임 노드의 데이터와 합쳐져 나중에 마스터 노드에 의해 쿼리됨 Historical Node 1 Partition some-table/ 2020-09-01_2020-09-02/ 
 2020-09-05T18:43:01Z/1 Partition some-table/ 2020-09-02_2020-09-03/ 
 2020-09-06T18:43:01Z/1 Partition some-table/ 2020-09-03_2020-09-04/ 
 2020-09-07T11:11:11Z/2 Historical Node 2 Partition some-table/ 2020-09-03_2020-09-04/ 
 2020-09-07T11:11:11Z/1 Partition some-table/ 2020-09-04_2020-09-05/ 
 2020-09-08T22:22:22Z/1 Historical Node 3 Realtime Node Master Kafka Consumer
  • 25. 3.0. 프로젝트 시작과, 우리의 철학 우리에겐 오버엔지니어링할 여유가 없다. 
 하늘 아래 새로운 건 없기 때문에, - 이미 솔루션이 있으면 최대한 차용 - 없다면, 기존에 왜 문제를 못 풀었을 지 생각하고 - 이제 무엇이 바뀌어 문제를 풀 수 있게 되었을지 분석
  • 26. R&D를 통한 초기 방향성 잡기 기존에 왜 문제를 못 풀었는지 생각하자. - Druid, Spark, Kafka, CockroachDB등의 오픈소스 분석 및 인사이트 추출 지금 무엇이 바뀌었기 때문에 문제를 풀 수 있을지 분석하자. - 주요 변화: CPU 코어 수, SIMD, 메모리 용량, I/O 속도, 클라우드
  • 27. 그러나 Pre-Aggregation 방식은 우리에게 맞지 않다! “정해진 Metric을 시간 단위별로 사전에 미리 계산해놓고, 쿼리 시엔 필요한 값만 선택해 합산 (Roll-up)해서 결과를 빠르게 제공한다” = High-Cardinal Shuffle에 취약 = 정해진 종류의 한정된 분석만 가능 따라서 Pre-Aggregation을 쓰지 않고, 그때그때 계산하는 게 베스트라는 결론
  • 28. 그때그때 계산도 문제는 여전하다 유저 행동 분석 쿼리는 필연적으로 계산 결과를 유저별로 Group By하게 됨 
 한편 MapReduce에서의 가장 큰 Bottleneck은 Shuffling이다. 데이터가 흩어져 저장되어 있을수록 Shuffle 비용이 기하급수적으로 증가함 -> 파티션 키의 Cardinality가 높을수록 Shuffle 비용이 기하급수적으로 증가! 유저 ID는 대표적인 High-Cardinality 칼럼 = 따라서 Group By User를 수행할 시 Shuffle 비용이 매우 커진다.
  • 29. Shuffle을 최적화하는 방법: 파티셔닝을 잘 해서 Shuffle을 국소적으로 만들자. 즉, 유저 ID로 파티셔닝될 수 있는 스토리지를 찾으면 되겠다!
  • 30. 3.1. TrailDB의 발견 3.2. LLVM으로 쿼리 엔진 개발 3.3. 연산 레이어를 직접 만들기 3.4. 샤딩 구현하기 Query Engine LRMR Luft Shard Controller STORAGE LAYER COMPUTATION LAYER APPLICATION LAYER We’re here! TrailDB
  • 31. 3.1. TrailDB · Adroll에서 2015년에 공개한 타임시리즈 이벤트 저장 Rowstore · 유저 ID 기반 파티셔닝에 최적화된 유일한 스토리지 포맷 
 · 인덱스가 없다! 무조건 풀스캔해야 한다. · 뭐 이런게 다 있ㅇ…😡 · 맥북 15” (Core i7, 2.6Ghz @ 6core) 기준 0.51초 · Snowflake에선 8.1초, Druid에선 1.1초 근데 풀스캔 성능이… 너무 좋네? 🤨
  • 32. 충격적으로 높은 압축률이 비결 ­ CSV로 13GB짜리 데이터가 TrailDB론 ~300mb ­ 이런 압축률에도 여전히 Time Complexity는 O(N)이다. ­ 어차피 Time Complexity가 O(N)일거, Space Complexity를 극한으로 줄여버리자. ­ 사이즈가 작으면 메모리에도 잘 들어가요. ­ SSD고 뭐고, RAM Sequential Read가 최고 
 TrailDB, 왜 이렇게 빠를까? 98%
  • 33. 유저 이벤트 특성을 반영해 설계한 데이터 구조 ­ 델타 인코딩: 유저의 이벤트를 시간순으로 정렬해, 
 이전 이벤트 대비 늘어난 시간 값만 저장 
 ­ 딕셔너리 인코딩: 값을 사전화시켜 그 ID만 저장 Event # 1 Event Timestamp: Delta-Encoded Data: Dictionary-Encoded … Event # 2 Event Timestamp: Delta-Encoded … User ID … … TrailDB, 왜 이렇게 빠를까?
  • 34. ??? : 아무리 그래도 Rowstore 주제에 칼럼스토어보다 빠를 수가 있어?
  • 35. Time view 100 TRUE John 
 Doe foo 72 like 150 FALSE bar buy Raw Data Time view 100 TRUE John 
 Doe foo 72 Time like 150 FALSE John 
 Doe bar 72 Time buy 150 FALSE John 
 Doe bar 72 Edge-Encoding: 이전 이벤트 대비 바뀐 칼럼만 넣자. 어차피 대부분의 사용자 속성은 변하지 않으니까 TrailDB는 Columnstore같은 Rowstore Event #1 Event #2 Event #3
  • 36. Raw Data Time view 100 TRUE John 
 Doe foo 72 Time like 150 FALSE John 
 Doe bar 72 Time buy 150 FALSE John 
 Doe bar 72 Δt Event #1 Event #2 Event #3 Edge-Encoding: 이전 이벤트 대비 바뀐 칼럼만 넣자. 어차피 대부분의 사용자 속성은 변하지 않으니까 TrailDB는 Columnstore같은 Rowstore Event #1 Event #2 Event #3 BuyΔt like 150 FALSE barTime view 100 TRUE John 
 Doe foo 72
  • 37. Immutability: 수정 불가능함 ­ 물론 Mutability를 포기하고 다른걸 얻자는 아이디어는 이미 대세 (e.g. HDFS, RDD..) 
 Simplicity: Full-featured DB가 아닌 LevelDB같은 스토리지 포맷에 가깝다. ­ 쿼리 엔진이나 샤딩, 복제, 클러스터링 같은 기능이 없음 
 그래도 이걸 스토리지 엔진으로 삼아 데이터스토어를 만들어볼 만 하다! 물론, 공짜 점심은 없다 🍔
  • 38. · Less Space Complexity leads to Less Time Complexity · Edge Encoding, Dict Encoding을 통한 중복 제거 · Rowstore에서 칼럼 순서는 상관없기 때문에, 
 최빈순으로 정렬해 Data Entropy를 줄임 · Immutability를 통해서 할 수 있는게 많아진다 · 기존의 RDBMS에서 할 수 없던 극한의 압축률 · 시간축으로 정렬해 저장해 Sequential I/O 속도 장점 살림 · OLAP 데이터스토어기 때문에, 수정 용이성을 과감히 버릴 수 있었기 때문 Lessons Learned from TrailDB
  • 39. 3.1. TrailDB의 발견 3.2. LLVM으로 쿼리 엔진 개발 3.3. 연산 레이어를 직접 만들기 3.4. 샤딩 구현하기 TrailDB Query Engine LRMR Luft Shard Controller STORAGE LAYER COMPUTATION LAYER APPLICATION LAYER We’re here!
  • 40. 3.2. LLVM으로 쿼리 엔진 개발 · TrailDB라는 스토리지가 생겼으니 
 쿼리 기능을 짜야 할 때 
 · Predicate Pushdown:
 스토리지 레벨에 필터를 적용해 
필요한 데이터만 읽는 기법 
 · Luft의 쿼리는 Go에서 파싱되므로, 
 C/C++단 TrailDB 쿼리 엔진에 넘겨야 함
  • 41. · TrailDB는 OR-AND 형식의 Equals (=) 쿼리만을 지원한다! · 난데없이 생겨버린 요구사항 2가지 1. TrailDB의 쿼리 엔진을 확장해 다양한 연산자 지원 2. Go로 파싱한 쿼리를 C언어로 가져오기 
 · 레이어화를 시킬 땐 Separation-of-Concern이 확실해야 한다. · 따라서 이런 식으로 중복 코드를 절대 만들고 싶지 않았음 TrailDB 쿼리 기능의 한계 추후 기능 확장에 제약사항! 관리 포인트가 많이 늘어남.
  • 42. · PostgreSQL에서 쿼리를 LLVM JiT으로 컴파일한다는 걸 리서치 중 발견 · LLVM JiT은 IR 코드를 주면 즉시 최적화 및 컴파일해 실행할 수 있음. · 잠깐, 우리의 모든 문제점을 해결해줄 기술인데? · Go 레벨에서 LLVM IR 코드만 생성해 넘기면 · C/C++에서 JiT 컴파일해서 실행해버리면 된다. 
 상상도 못한 돌파구, LLVM JiT
  • 43. 쿼리는 기능 확장이 빈번하기 때문에 C/C++로 짜면 추후 개발 비용 . 근데 JiT 쿼리엔진은 한번만 만들어두면 나머진 Go로 짤 수 있음 . 용을 잡아볼만 하다!
  • 44. Query Compile Scan Data Parse Query Generate Code Go C/C++ C/C++ 새로운 JiT 쿼리 엔진의 구조
  • 45. · 일반적으론 성능 최적화 때문에 JiT을 도입하지만, 
 개발 비용때문에 JiT을 도입했다는 특수한 시나리오 · 상식: 복잡한 기술 스택 도입은 오버엔지니어링이다. · 교훈: 복잡한 기술 스택 도입을 적시에 한다면 오히려 개발 비용 절감 가능 · 어차피 유지보수는 힘드니, 기능 확장성을 고려 
 Lessons Learned from JiT
  • 46. 3.1. TrailDB 3.2. LLVM으로 쿼리 엔진 개발 3.3. 연산 레이어를 직접 만들기 3.4. 샤딩 구현하기 TrailDB Query Engine LRMR Luft Shard Controller STORAGE LAYER COMPUTATION LAYER APPLICATION LAYER We’re here!
  • 47. · 연산 레이어에서 흩어진 데이터를 하나로 모음 
 · 이런 연산 레이어로 MapReduce가 많이 쓰여요. · 근데 우리는 Go라서 못 써요. 
 · Spark를 겨우 붙여서 벤치마크해봤지만, 성능이 그닥 · 애초에 Spark / Hadoop은 Long-running Job에 최적화된 프레임워크 3.3. 연산 레이어를 직접 만들기
  • 48. 그래서 직접 만들었습니다. Open-Sourced on github.com/ab180/lrmr 🔗
  • 49. 예시: LRMR로 분산화한 리텐션 분석 쿼리
  • 50. 빠른 개발을 위해 · gRPC + Protobuf + etcd라는 검증된 조합 선택 · 익숙한 Spark의 디자인을 많이 차용 
 쿼리와 같은 Short-running Job에 최적화하기 위해 - Idea: 과감히 Resiliency를 포기하자. - 저널링이나 체크포인팅을 포기하는 대신, 성능 극한으로 높이고 개발 비용 절감 - 만약 장애가 발생하면? - 처음부터 다시 하자! 어차피 10초 미만 Design Goals of LRMR
  • 51. 구조적 문제 발생 : Stream Backpressure 프로덕션에서 대규모 데이터를 쿼리하다 보니, LRMR 단에서 버퍼 오버플로가 자주 발생 - Push-based Stream에서 흔히 볼 수 있는 Backpressure 문제 - 프로듀서 속도를 컨슈머가 따라오지 못해 버퍼에만 쌓이다 터진다. 🐰 Fast 
 Producer 🐢 Slow Consumer Push Laaaaaaaaaag
  • 52. 해결 : Pull-based Event Stream! · Idea: 컨슈머가 처리할 수 있는 양만 그때그때 요청하면 어떨까? · ✅ 속도에 맞게 요청할 수 있어 Backpressure 없음 · ✅ 스트림 오프셋을 컨슈머가 관리하므로, 추후 체크포인트나 저널링 도입도 용이 · Kafka, Reactive Streams, Armeria 등이 채택한 방식 🐰 Fast 
 Producer Pull N tasks 🦄 Slow 
 Consumer
  • 53. 3.1. TrailDB 3.2. LLVM으로 쿼리 엔진 개발 3.3. 연산 레이어를 직접 만들기 3.4. 샤딩 구현하기 TrailDB Query Engine LRMR Luft Shard Controller STORAGE LAYER COMPUTATION LAYER APPLICATION LAYER Finally!
  • 54. 3.4. 샤딩 구현하기 Luft에서의 샤드 = “히스토리컬 노드”이다. 즉 샤딩 = 파티션을 여러 히스토리컬 노드에 분배하는 과정 Idea: 파티션의 날짜 범위를 샤딩 키값으로 사용하자 - 모든 쿼리에 반드시 시간 범위가 붙음 → 데이터 필터링 용이 - 같은 시간 범위엔 비슷한 용량의 데이터가 들어 있음 → 데이터 분산 용이
  • 55. 새로운 파티션을 만들 때마다 Round-Robbin으로 각 샤드에 분배 
 문제: 분산 환경은 아름답지 않다. - 노드가 다운되거나 새로 추가되면? - 노드의 저장 공간이 꽉 차면? - 일시적 장애로 인해 파티션이 한 샤드에 쏠려버리면? Rebalance, Time Decay, Eviction 등의 고도화된 정책 니즈 제기됨 버전 1 : Vanilla Sharding
  • 56. · 정책적 의도대로 Cost Function을 세우고, Cost가 제일 낮은 노드에 자원을 배치함 · 일반적으로 스케줄러를 짤 때 많이 사용되는 방법 
 · 우리의 정책적 의도 : 날짜 범위가 겹치지 않게 파티션을 분배하고 싶다! · 두 파티션의 날짜 범위가 가깝고 겹칠수록 코스트가 높게 하면 된다. 
 


 · 이것까지 직접 만들긴 어렵다. Druid의 Cost Function 공식을 베이스 삼아 커스텀 버전 2 : Cost Function을 사용하자
  • 57.
  • 58. etcd로 샤드의 장애 상황을 효과적으로 관리 - Liveness Probe 패턴: 샤드 정보에 TTL을 걸고 주기적으로 갱신 - 장애 시엔 TTL 갱신이 안되니까 자연스럽게 샤드 목록에서 삭제됨 - Watch 기능으로 감지해 빠른 파티션 리밸런싱 
 클라우드 = 최고의 가용성 머신 - S3에 파티션을 저장해두고 필요한 것만 로드 - DynamoDB를 통해 파티션 목록을 관리 샤드의 가용성을 위한 끊임없는 노력
  • 60. 4.1. 현재의 Luft - Airbridge의 프로덕션에서 운영 중 - 실시간 코호트 모수 추출 및 리텐션 분석만을 현재 수행 중 - 4대의 c5.2xlarge 인스턴스만으로 최대 15초 내에 500GB 규모의 데이터 스캔
  • 61. 4.1. 앞으로 해야 할 일 다양한 종류의 쿼리 지원 - 실시간 퍼널 분석을 10대 이내의 클러스터만으로 수행하는 것이 목표 - 고도화된 GROUP BY와 JOIN 기능 Spark 지원 - 데이터 분석의 확장성과 추후 ML 연동 등을 위해 
 그리고… Open Source!
  • 62. 4.2. Ziegel : Columnstore by AB180 TrailDB를 대체할 자체 칼럼스토어 - TrailDB의 한계점을 극복하고 핵심 인사이트 계승 Design Goals - 데이터 기반 데이터스토어 설계
 - Bitmap Index 도입으로 사전에 유저 속성 기반 필터링 - SIMD와 멀티코어 최적화
  • 63. 4.3. 다음에 다뤄보고 싶은 이야기 - 새로운 칼럼스토어 설계를 하며 얻은 데이터 엔지니어링적 인사이트 - 더 많은 프로덕션 경험과 장애 대응 - 구조적 SPOF를 줄여나간 이야기 - 극한의 Go 성능 최적화에 대한 경험 ...만약 이런 이야기들을 직접 경험해보고 싶으시다면…?
  • 64. Luft를 함께 만들어나가실 분을 찾습니다. dev-recruit.ab180.co/database-engineer