Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions marshmallowing/Week03/chapter03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# 6장: 광고 클릭 이벤트 집계

> 디지털 광고의 핵심 프로세스는 RTB(Real-Time-Bidding), 즉 실시간 경매라고 부른다. 이 경매 절차를 통해 광고가 나갈 지면(inventory)을 거래한다. 온라인 광고에 사용되는 핵심 지표로는 CTR(클릭률), CVR(전환률) 등이 있으며, 집계된 광고 클릭 데이터에 기반하여 계산한다.

### 질의 API 설계

> 클라이언트가 대시보드를 이용하는 순간 집계 서비스에 질의가 발생
>
- 기능 요구사항
- 지난 M분 동안 각 ad_id에 발생한 클릭 수 집계
- 지난 M분 동안 가장 많은 클릭이 발생한 상위 N개 ad_id 목록 반환
- 다양한 속성을 기준으로 집계 결과를 필터링하는 기능 지원
1. 지난 M분간 각 ad_id에 발생한 클릭 수 집계

: `GET /v1/ads/{:ad_id}/aggregated_count`

2. 지난 M분간 가장 많은 클릭이 발생한 상위 N개 ad_id 목록

: `GET /v1/ads/popular_ads`


### 데이터 모델

- 원시 데이터
- 장점: 원본 데이터를 손실 없이 보관, 데이터 필터링 및 재계산 지원
- 단점: 막대한 데이터 용량, 낮은 질의 성능
- 집계 결과 데이터
- 장점: 데이터 용량 절감, 빠른 질의 성능
- 단점: 데이터 손실- 원본 데이터가 아닌 계산/유도된 데이터를 저장하는 데서 오는 결과

> 두 데이터 모두 저장하는 방식을 추천한다. 원시 데이터는 양이 많으므로 질의를 할때는 집계 결과 데이터를 이용하며 원시 데이터의 경우 디버깅용이나 백업 데이터로 활용할 수 있다. 집계 결과 데이터는 활성 데이터 역할로, 질의 성능을 높이기 위해 튜닝한다.
>

### 올바른 데이터베이스

- 원시 데이터
- 평균 쓰기 QPS는 10000, 최대 QPS는 50000
- 백업과 재계산 용도로, 쓰기 중심 시스템
- 쓰기 및 범위 질의에 최적화된 카산드라, InfluxDB
- 집계 데이터
- 시계열 데이터로, 읽기 연산과 쓰기 연산 둘 다 많이 사용
- 각 광고에 대해 매 분마다 데이터베이스에 질의를 던져 고객(광고주)에세 최신 집계 결과를 제시
- 총 200만개의 데이터로, 읽기 연산 매우 빈번
- 집계 서비스가 데이터를 매 분 집계하고 그 결과를 기록하므로 쓰기 작업도 매우 빈번

---

## 개략적 설계안

집계 서비스에서 실시간으로 빅데이터를 처리할 때, 입력은 원시 데이터(무제한 데이터 스트림)이고, 출력은 집계 결과이다.

### 비동기 처리

동기식 시스템의 경우, 특정 컴포넌트의 장애는 전체 시스템 장애로 이어진다. 트래픽이 갑자기 증가하여 발생하는 이벤트 수가 소비자의 처리 용량을 훨씬 넘어서는 경우, 소비자는 예기치 않은 문제를 겪게 된다.

<aside>
💡

이 문제를 해결하는 방안은 카프카 같은 **메시지 큐를 도입하여 생산자와 소비자의 결합을 끊는 것**이다.

</aside>

> 로그 감시자, 집계 서비스, 데이터베이스는 두 개의 메시지 큐로 분리되어 있다. 데이터베이스 기록 프로세스는 메시지 큐에서 데이터를 꺼내 데이터베이스가 지원하는 형식으로 변환한 다음 기록하는 역할을 수행한다.
>

![원통 모양이 메시지 큐](attachment:9cb14994-8d86-40ba-aa41-30c08a7c5ae4:image.png)

원통 모양이 메시지 큐

- 첫번째 메시지 큐
- 광고 클릭 이벤트 데이터
- 두번째 메시지 큐
- 분 단위로 집계된 광고 클릭 수
- 분 단위로 집계한, 가장 많이 클릭한 상위 N개 광고

<aside>
💡

집계 결과를 데이터베이스에 바로 기록하지 않는 이유는, 정확하게 한번(exactly-once) 데이터를 처리하기 위해 카프카 같은 시스템을 두 번째 메시지 큐로 도입했다. (atomic-commit, 원자적 커밋 방식)

</aside>

### 집계 서비스

> 광고 클릭 이벤트를 집계하는 방안으로 **맵리듀스 프레임워크**를 활용할 수 있다. 맵리듀스 프레임워크에 좋은 모델은 유향 비순환 그래프(**DAG**)다. DAG 모델의 핵심은 시스템을 맵/집계/리듀스/ 노드 등의 작은 컴퓨팅 단위로 세분화하는 것이다. 각 노드는 한 가지 작업만 처리하며, 처리 결과를 다음 노드에 인계한다.
>
- **맵 노드**
- 데이터 출처에서 읽은 데이터를 필터링하고 변환하는 역할을 담당
- 입력 데이터를 정리하거나 정규화해야 하는 경우에 필요
- 데이터가 생성되는 방식에 대한 제어권이 없는 경우에 동일한 ad_id를 갖는 이벤트가 서로 다른 카프카 파티션에 입력될 수도 있어서 맵 노드가 필요
- **집계 노드**
- ad_id별 광고 클릭 이벤트 수를 매 분 메모리에서 집계한다
- 맵리듀스 패러다임에서 집계노드는 사실 리듀스 프로세스의 일부
- **리듀스 노드**
- 모든 집계 노드가 산출한 결과를 최종 결과로 축약
- 집계 노드 각각은 자기 관점에서 가장 많은 클릭이 발생한 광고 3개를 추려 리듀스 노드로 보내고, 리듀스 노드는 그 결과를 모아 최종적으로 3개의 광고만 남긴다

> **DAG**는 맵리듀스 패러다임을 표현하기 위한 모델로, 빅데이터를 입력으로 받아 병렬 분산 컴퓨팅 자원을 활용하여 빅데이터를 작고 일반적 크기 데이터로 변환할 수 있게 설계된 모델이다
>
- 클릭 이벤트 수 집계
- 멥 노드는 시스템에 입력되는 이벤트를 (ex: ad_id%3을 기준으로) 분배하며, 분배한 결과는 각 집계 노드가 집계
- 가장 많이 클릭된 상위 N개 광고 반환
- 입력 이벤트는 ad_id 기준으로 분배되고 각 집계 노드는 힙을 내부적으로 사용해 상위 3개 광고를 식별한다. 마지막 단계의 리듀스 노드는 전달 받은 9개 가운데 가장 많이 클릭된 광고 3개를 골라낸다.
- 데이터 필터링
- 필터링 기준을 사전에 정의한 후 기준에 따라 집계
- 해당 기법을 스타 스키마라 부르며, 데이터웨어하우스에서 주로 쓰이는 기법으로 필터링에 사용되는 필드는 차원(id-mension)이라 부른다

---

## 상세 설계

### 스트리밍 VS 일괄 처리

> 스트림 처리는 데이터를 오는 대로 처리하고 서의 실시간으로 집계된 결과를 생성하는 데 사용한다. 일괄 처리는 이력 데이터를 백업하기 위해 활용한다.
>
- 람다 아키텍처
- 일괄 및 스트리밍 처리 경로를 동시에 지원하는 시스템 아키텍처
- 두 가지 처리 경로를 지원하므로 유지 관리해야하는 코드가 두배이다
- 카파 아키텍처
- 일괄 처리와 스트리밍 처리 경로를 하나로 결합하여 위 문제를 해결
- **단일 스트림 처리 엔진**을 사용하여 실시간 데이터 처리 및 끊임없는 데이터 재처리 문제를 모두 해결

### 데이터 재계산

> 이미 집계한 데이터를 다시 계산해야 하는 경우, 이를 이력(history) 데이터 재처리라고 부른다. 집계 서비스에 버그가 발생 시, 버그 발생 시점부터 원시 데이터를 다시 읽어 집계 데이터를 재계산하고 고쳐야 할 것이다.
>
1. 재계산 서비스는 원시 데이터 저장소에서 데이터를 검색한다. 일괄 처리 프로세스를 따른다.
2. 추출된 데이터는 전용 집계 서비스로 전송된다. 전용 집계 서비스를 두는 것은 실시간 데이터 처리 과정이 과거 데이터 재처리 프로세스와 간섭하는 일을 막기 위해서다.
3. 집계 결과는 두 번째 메시지 큐로 전송되어 집계 결과 데이터베이스에 반영된다.

재계산 프로세스는 데이터 집계 서비스를 재사용하기는 하지만 처리 대상 데이터는 다른 곳에서 읽는다. (원시 데이터를 직접 읽는다)

### 시간

- 이벤트 발생 시각: 광고 클릭이 발생한 시각
- 광고 클릭 시점을 정확히 아는 것은 클라이언트이므로 집계 결과가 보다 정확
- 클라이언트가 생성한 타임스탬프에 의존하는 방식이므로 위험성 존재
- 처리 시각: 집계 서버가 클릭 이벤트를 처리한 시스템 시각
- 서버 타임스탬프이므로 안정적
- 이벤트가 시스템에 도착한 시각이 한참 뒤인 경우 (네트워크 지연, 비동기 환경..) 부정확

> 데이터의 정확도를 위해 책에서는 이벤트 발생 시각을 이용
>
- **워터마크**
- 시스템에 늦게 도착한 이벤트를 올바르게 처리하기 위한 기술
- 집계 윈도우의 확장으로 집계 결과의 정확도 향상
- 집계 윈도우
- 광고 클릭 이벤트를 1분 단위로 끊음
- 이벤트가 집계 윈도우가 끝나는 시점보다 살짝 늦을 경우 누락되는 이벤트 발생
- 워터마크의 구간이 길면 늦게 도착하는 이벤트 포착 가능하지만 시스템의 이벤트 처리 시간은 늘어남
- 짧을 시 데이터 정확도는 떨어지지만 응답 지연 낮아짐

### 집계 윈도우

- 텀블링 윈도우
- 시간을 같은 크기의 겹치지 않는 구간으로 분할한 것
- 슬라이딩 윈도우
- 데이터 스트림을 미끄러져 나아가면서 같은 시간 구간 안에 있는 이벤트를 집계
- 서로 겹칠 수 있음

### 전달 보장

> 약간의 중복이 괜찮다면 ‘최소 한 번’이 적절하지만 이 설계안에서는 데이터의 정확성과 무결성이 중요하기에 (데이터의 작은 차이로 큰 과금 이어질 수 있다) ‘정확히 한 번’ 방식을 권장한다.
>
- 데이터 중복 제거
- 중복된 데이터가 발생하는 경우
- 클아이언트: 한 클라이언트가 같은 이벤트를 여러번 전송
- 악의적 전송의 중복 이벤트 처리하기 위해 광고사기/위험제어 컴포넌트 존재
- 서버 장애: 집계 도중에 집계 서비스 노드에서 장애가 발생하였고 업스트림 서비스가 이벤트 메시지에 대해 응답을 받지 못하였다면, 같은 이벤트가 다시 전송되어 재 집계 될 가능성 존재

### 시스템 규모 확장

- **메시지 큐의 규모 확장**
- 생산자: 생산자 인스턴스 수에는 제한을 두지 않으므로 확장성 쉽게 달성 가능
- 소비자: 소비자 그룹 내의 재조정 매커니즘은 노드 추가/삭제를 통해 규모를 쉽게 조정 가능
- **브로커**
- 해시키
- ad_id를 해시키로 사용하면 집계 서비스는 같은 ad_id를 갖는 이벤트를 전부 같은 파티션에서 구독 가능하다
- 파티션의 수
- 사전에 충분한 파티션 확보 필요 (동적 증가 피하기)
- 토픽의 물리적 샤딩
- 데이터를 여러 토픽으로 나누면 시스템의 처리 대역폭 높일 수 있음
- 복잡성과 유지 관리 비용 증가하는 단점
- **집계 서비스의 규모 확장**
- ad_id마다 별도의 처리 스레드 구성
- 집계 서비스 노드를 아파치 하둡 YARN 같은 자원 공급자에 배포해 다중 프로세싱을 활용
- **데이터베이스의 규모 확장**

카산드라는 안정 해시와 유사한 방식으로 수평적 규모 확장 지원

- 데이터와 사본은 각 노드에 균등 분산
- 각 노드는 해시 링 위의 특정 해시 값 구간의 데이터 보관을 담당하며 다른 가상 노드의 데이터 사본도 보관
- 클러스터에 새 노드 추가 시 가상 노드 간 균형 자동 조정

- **핫스팟 문제**

다른 서비스나 샤드보다 더 많은 데이터를 수신하는 서비스나 샤드

- 서버 과부하 문제 발생 가능
- 더 많은 집계 서비스 노드를 할당하여 완화 가능
- **결함 내성**

집계는 메모리에서 이루어지므로 집계 노드에 장애가 생기면 집계 결과도 소실

- 업스트림 카프카 브로커에서 이벤트를 다시 받아오면 숫자 재생 가능
- 업스트림 오프셋같은 시스템 상태를 스냅숏으로 저장하고 마지막으로 저장된 상태부터 복구
Loading