스트리밍 시스템 1부
광고 과금 시스템으로 이해하는 Streaming Systems 1부
《Streaming Systems》 1부를 읽으면서 이벤트 시간(Event Time), 처리 시간(Processing Time), 윈도(Window), 워터마크(Watermark), 트리거(Trigger), 누적(Accumulation), Exactly Once 같은 개념을 접했습니다.
개념 하나하나는 크게 어렵지 않았지만, 책을 따라가다 보면 비슷해 보이는 개념이 계속 등장합니다. 특히 Window, Watermark, Trigger가 실제 시스템에서 각각 어떤 문제를 해결하기 위해 필요한지 한 번에 잡히지는 않았습니다.
그래서 이번 글에서는 광고 과금 시스템을 하나 만들어본다고 가정하고 1부의 내용을 다시 따라가보려고 합니다.
광고 시스템은 다음처럼 동작합니다.
사용자에게 광고가 노출되고, 사용자가 광고를 클릭합니다. 광고주는 클릭 한 번당 비용을 지불하는 CPC(Cost Per Click) 방식으로 과금된다고 하겠습니다.
1
2
3
4
5
6
7
광고 노출
↓
광고 클릭
↓
클릭 수 집계
↓
광고 비용 계산
클릭당 비용이 100원이라면 계산 자체는 어렵지 않습니다.
1
광고 비용 = 클릭 수 × 100원
처음에는 클릭 이벤트를 받아 개수만 세면 될 것처럼 보입니다.
그런데 실제 과금 시스템이라고 생각하고 하나씩 조건을 붙여보면 이야기가 달라집니다.
1. 끝나지 않는 데이터를 어떻게 계산할까
광고 서비스가 운영되는 동안 다음과 같은 이벤트가 계속 들어옵니다.
1
2
3
4
5
6
10:00:01 impression campaign=A
10:00:05 click campaign=A
10:00:08 impression campaign=B
10:00:12 click campaign=A
10:00:17 click campaign=B
...
광고 서비스에는 데이터의 끝이 없습니다.
오늘 발생한 이벤트 처리가 끝나더라도 내일 다시 이벤트가 들어옵니다. 서비스가 운영되는 동안 계속 데이터가 만들어집니다.
이처럼 끝을 미리 알 수 없는 데이터를 Unbounded Data라고 합니다.
배치 처리라면 하루치 데이터를 모아 한 번에 계산할 수 있습니다.
1
2
3
4
5
6
7
하루치 데이터
↓
데이터 로딩
↓
클릭 수 집계
↓
과금 계산
반면 스트리밍에서는 “모든 데이터를 받은 뒤 계산한다”는 기준을 잡기 어렵습니다. 애초에 모든 데이터가 언제 도착했는지 정의할 수 없기 때문입니다.
여기서 첫 번째 질문이 생깁니다.
데이터가 끝나지 않는다면 언제 계산해야 할까?
예를 들어 Campaign A의 클릭 수를 구한다고 해보겠습니다.
1
Campaign A click count = ?
서비스가 운영되는 동안 클릭 수는 계속 늘어날 수 있습니다. count라는 연산만 정한다고 해서 계산 기준이 완성되지는 않습니다.
《Streaming Systems》에서는 이 문제를 네 가지 질문으로 나눕니다.
- What: 무엇을 계산할 것인가?
- Where: 어떤 범위의 데이터를 함께 계산할 것인가?
- When: 언제 결과를 내보낼 것인가?
- How: 여러 번 나온 결과를 어떻게 다룰 것인가?
이 네 질문을 광고 과금 문제에 하나씩 적용해보겠습니다.
2. What — 무엇을 계산할 것인가
먼저 계산할 값을 정합니다.
현재 요구사항은 다음과 같습니다.
캠페인별 클릭 수를 집계하고, 클릭 수에 CPC 단가를 곱해 광고 비용을 계산합니다.
CPC가 100원이고 Campaign A에서 클릭이 100번 발생했다면 다음과 같습니다.
1
2
3
4
5
6
Campaign A
click count = 100
CPC = 100원
광고 비용 = 10,000원
연산 흐름을 풀어 쓰면 이렇습니다.
1
2
3
4
5
6
7
Click Event
↓
campaignId 기준 그룹화
↓
클릭 수 집계
↓
클릭 수 × CPC
이 부분이 What입니다.
어떤 입력으로 어떤 값을 계산할지를 정하는 단계입니다.
여기까지는 어렵지 않습니다.
문제는 Campaign A의 클릭 수가 계속 증가한다는 데 있습니다. 어느 시점까지의 클릭을 묶어서 계산할지 정해야 합니다.
3. Where — 어디까지를 하나의 계산으로 볼 것인가
광고주가 실시간 대시보드에서 10분 단위로 클릭 수와 예상 광고 비용을 보고 싶다고 해보겠습니다.
그러면 이벤트를 다음과 같이 나눌 수 있습니다.
1
2
3
4
10:00 ~ 10:10
10:10 ~ 10:20
10:20 ~ 10:30
...
이때 사용하는 것이 Window입니다.
다음과 같은 클릭이 발생했다고 하겠습니다.
1
2
3
4
10:01 click A
10:03 click A
10:07 click A
10:12 click A
10분 Fixed Window를 적용하면 다음처럼 묶입니다.
1
2
3
4
5
6
7
8
[10:00 ~ 10:10)
A → 3 clicks
[10:10 ~ 10:20)
A → 1 click
처음 질문은 이랬습니다.
1
Campaign A의 클릭 수는 몇 개인가?
Window를 적용하고 나면 질문을 이렇게 바꿀 수 있습니다.
1
2
10:00부터 10:10까지
Campaign A의 클릭 수는 몇 개인가?
끝이 없던 데이터에 계산 범위가 생겼습니다.
그래서 Window는 시간을 일정한 크기로 자르는 기능이라고만 보기보다, 끝없는 데이터에 계산 가능한 경계를 만드는 방법으로 이해하는 편이 자연스럽습니다.
4. 이벤트가 발생한 시간과 처리한 시간은 다르다
여기까지 생각하면 10:00부터 10:10 사이에 발생한 클릭을 모아서 10:10에 계산하면 될 것 같습니다.
하지만 실제 시스템에서는 이벤트가 발생한 순서대로 서버에 도착하지 않습니다.
사용자가 10시 7분에 광고를 클릭했다고 해보겠습니다.
1
event_time = 10:07
사용자의 네트워크가 좋지 않아 서버에는 10시 12분에 이벤트가 도착할 수도 있습니다.
1
processing_time = 10:12
여기서 두 종류의 시간을 구분해야 합니다.
Event Time
이벤트가 실제로 발생한 시간입니다.
1
2
사용자가 광고를 클릭한 시각
10:07
Processing Time
스트리밍 시스템이 해당 이벤트를 관찰하거나 처리한 시간입니다.
1
2
서버가 클릭 이벤트를 받은 시각
10:12
두 시간은 얼마든지 달라질 수 있습니다.
실제 클릭 순서가 아래와 같더라도,
1
2
3
4
10:01 click
10:03 click
10:07 click
10:09 click
서버가 관찰한 순서는 이렇게 될 수 있습니다.
1
2
3
4
10:01 click
10:09 click
10:03 click
10:07 click
모바일 네트워크 지연, 오프라인 상태, 메시지 브로커 지연, 장애 후 재시도 등 원인은 다양합니다.
스트리밍 시스템은 이런 순서 뒤바뀜을 기본 조건으로 받아들여야 합니다.
5. 광고 과금에서는 어느 시간이 기준이 되어야 할까
10분 단위로 광고 비용을 집계한다고 해보겠습니다.
1
2
3
10:00 ~ 10:10
클릭 수 × 100원
사용자가 실제로 클릭한 시간은 10:07입니다.
서버에 도착한 시간은 10:12입니다.
Processing Time을 기준으로 보면 이 클릭은 다음 구간에 들어갑니다.
1
10:10 ~ 10:20
하지만 사용자가 클릭한 시점은 10:07이므로 비즈니스 관점에서는 다음 구간에 들어가는 편이 자연스럽습니다.
1
10:00 ~ 10:10
10분짜리 대시보드만 생각하면 몇 분의 차이가 별일 아닌 것처럼 느껴질 수도 있습니다.
그런데 날짜 경계로 가면 조금 더 현실적인 문제가 됩니다.
1
2
Event Time
23:59:58
서버에는 자정이 지난 뒤 도착합니다.
1
2
Processing Time
00:00:03
Processing Time을 기준으로 처리하면 이 클릭은 다음 날 발생한 클릭으로 집계될 수 있습니다.
여기서 정책 문제가 생깁니다.
이 클릭을 어느 날짜의 정산에 귀속해야 할까요?
광고 과금처럼 실제 행위가 발생한 시각에 의미가 있는 도메인에서는 Event Time을 기준으로 집계하는 편이 자연스럽습니다.
다만 Event Time을 사용한다고 해서 문제가 모두 해결되지는 않습니다.
오히려 새로운 질문이 생깁니다.
6. 현재 시간이 10시 10분이라고 해서 10시 10분 이전 이벤트가 모두 온 것은 아니다
10:00~10:10 Window가 있다고 해보겠습니다.
Processing Time이 10:10이 됐습니다.
이제 10:00~10:10 구간의 과금을 확정해도 될까요?
아직은 어렵습니다.
다음과 같은 클릭이 도착하지 않았을 수 있습니다.
1
2
event_time = 10:08
processing_time = 10:13
현재 시간이 10:10이라는 사실과,
1
2
10:10 이전에 발생한 이벤트가
모두 도착했다
는 별개의 문제입니다.
Processing Time만으로는 Event Time 기준 데이터가 어디까지 도착했는지 알 수 없습니다.
이 문제를 다루기 위해 Watermark가 등장합니다.
7. Watermark — Event Time이 어디까지 진행됐다고 볼 것인가
Watermark는 시스템이 현재까지 관찰한 데이터와 입력 특성을 바탕으로 Event Time의 진행 정도를 표현하는 값입니다.
예를 들어 다음과 같은 Watermark가 있다고 해보겠습니다.
1
Watermark = 10:10
이 값을 직관적으로 읽으면 다음과 비슷합니다.
1
2
10:10 이전에 발생한 이벤트는
대부분 도착했다고 본다.
여기서 조심해야 할 점이 있습니다.
Watermark가 10:10이라는 것이
1
2
10:10 이전의 모든 이벤트가
100% 도착했다.
라는 뜻은 아닙니다.
미래에 어떤 이벤트가 얼마나 늦게 들어올지는 완벽하게 알 수 없습니다.
Watermark가 이미 10:15까지 진행된 상황에서도 다음과 같은 이벤트가 들어올 수 있습니다.
1
2
click
event_time = 10:07
이처럼 Watermark가 지난 Event Time을 가진 이벤트를 지연 데이터(Late Data)라고 부릅니다.
8. 늦게 들어온 클릭이 과금 금액을 바꾼다
10:00~10:10 Window에서 클릭이 100번 발생했다고 계산했다고 해보겠습니다.
CPC가 100원이므로 현재 과금액은 10,000원입니다.
1
2
100 clicks × 100원
= 10,000원
그 뒤 10:00~10:10에 속하는 클릭 5건이 늦게 들어왔습니다.
1
2
105 clicks × 100원
= 10,500원
그럼 정책을 정해야 합니다.
1
2
3
4
5
늦게 들어온 5건을 버릴 것인가?
기존 결과를 다시 계산할 것인가?
얼마나 늦게 들어온 이벤트까지 받아줄 것인가?
스트리밍에서 결과를 한 번 만들어 고정된 값으로 생각하기 어려운 이유가 여기에 있습니다.
현재까지 도착한 데이터를 기준으로 결과를 만들고, 이후 들어온 데이터에 따라 그 결과가 달라질 수 있습니다.
9. When — 결과를 언제 내보낼 것인가
Window가 10분이라고 해서 결과를 10분마다 한 번만 보여줄 필요는 없습니다.
광고주가 실시간 대시보드를 보고 있다고 해보겠습니다.
10분 동안 아무 변화가 없다가 마지막에 다음 값을 보여주는 방법도 있습니다.
1
2
3
4
10:10
클릭 100건
예상 비용 10,000원
하지만 광고 시스템에서는 중간 값에도 의미가 있습니다.
1
2
3
4
5
10:02 → 20 clicks → 2,000원
10:04 → 41 clicks → 4,100원
10:06 → 63 clicks → 6,300원
10:08 → 82 clicks → 8,200원
10:10 → 100 clicks → 10,000원
대시보드뿐 아니라 광고 송출 쪽에서도 현재까지 소진한 예산을 알아야 광고를 계속 내보낼지 판단할 수 있습니다.
이때 Trigger가 필요합니다.
Trigger는 현재 계산된 값을 언제 내보낼지 정합니다.
Window와 역할이 다릅니다.
1
2
3
4
5
Window
────────────────────────
10:00 10:10
어떤 이벤트를 같은 계산에 포함할 것인가?
Trigger는 다음 질문을 다룹니다.
1
2
3
4
5
↓ ↓ ↓
────────────────────────
10:03 10:06 10:10
언제 계산 결과를 내보낼 것인가?
Window가 데이터의 범위를 정한다면, Trigger는 결과를 내보내는 시점을 정합니다.
10. Early, On-Time, Late
Watermark와 Trigger를 같이 놓고 보면 하나의 Window에서도 결과가 여러 번 나올 수 있습니다.
최종 클릭 수가 105였다고 해보겠습니다.
1
2
3
4
5
10:03 → 30 clicks
10:06 → 61 clicks
10:09 → 92 clicks
Watermark 통과 → 100 clicks
지연 데이터 도착 → 105 clicks
10:03, 10:06, 10:09에 나온 값은 Early Result입니다.
1
2
3
30
61
92
아직 해당 Window의 데이터가 충분히 도착했다고 보기는 이르지만, 현재 상황을 빨리 보여주기 위해 내보낸 값입니다.
Watermark가 Window의 끝을 통과했을 때 나온 값은 On-Time Result입니다.
1
100
그 뒤 지연 데이터를 반영해 나온 값은 Late Result입니다.
1
105
이 부분을 이해하고 나니 스트리밍과 배치를 보는 관점이 조금 달라졌습니다.
배치에서는 보통 이런 흐름을 떠올립니다.
1
2
3
4
5
데이터가 준비된다
↓
계산한다
↓
결과를 만든다
스트리밍에서는 모든 데이터가 언제 준비되는지를 알기 어렵습니다.
그래서 다음과 같은 흐름이 더 자연스럽습니다.
1
2
3
4
5
6
7
현재까지 받은 데이터로 결과를 만든다
↓
추가 데이터가 들어온다
↓
기존 결과를 수정한다
↓
더 완전한 결과에 가까워진다
스트리밍의 결과는 한 번 계산되고 끝나는 값이 아니라, 시간이 지나면서 수정될 수 있는 값입니다.
11. 빠른 결과와 완전한 결과 사이에서
광고 시스템에서는 이 차이가 쉽게 드러납니다.
실시간 대시보드라면 어느 정도의 오차를 허용할 수 있습니다.
1
2
3
현재 예상 광고 비용
10,000원
몇 분 뒤 지연 데이터가 들어와 값이 바뀔 수도 있습니다.
1
2
3
10,000원
→
10,500원
대시보드의 역할은 현재 상황을 빠르게 보여주는 데 있기 때문에 이 정도 수정은 받아들일 수 있습니다.
하지만 실제 청구 금액은 다릅니다.
1
2
3
이번 달 광고 청구액
32,481,200원
이 값은 실시간 대시보드보다 훨씬 보수적으로 계산해야 합니다.
같은 클릭 스트림을 사용해도 목적에 따라 원하는 결과의 성격이 달라집니다.
1
2
3
4
실시간 대시보드
결과를 빨리 보여주는 것이 중요
나중에 일부 수정될 수 있음
1
2
3
4
실제 정산
늦게 들어온 데이터까지 충분히 반영
빠른 결과보다 정확한 정산이 중요
스트리밍 시스템을 설계할 때는 빠른 결과와 완전한 결과 가운데 무엇을 더 우선할지 정해야 합니다.
12. 늦은 이벤트를 언제까지 기다릴 것인가
그렇다고 늦은 이벤트를 계속 기다릴 수는 없습니다.
언젠가는 광고주에게 비용을 청구해야 하고, 시스템 입장에서도 오래된 Window 상태를 영원히 들고 있을 수 없습니다.
예를 들어 10:00~10:10 Window를 몇 달 뒤까지 유지한다면 시간이 지날수록 관리해야 하는 상태가 늘어납니다.
그래서 어느 시점에는 이 Window에 대한 계산을 닫고 상태를 정리해야 합니다.
30분까지 늦은 이벤트를 허용한다고 해보겠습니다.
1
2
3
4
5
Window
10:00 ~ 10:10
지연 데이터 허용
추가 30분
이 기간 안에 이벤트가 들어오면 기존 결과를 수정할 수 있습니다.
1
2
3
4
5
100 clicks
↓
Late Click + 5
↓
105 clicks
정해둔 시간이 지나면 해당 Window의 상태를 정리할 수 있습니다.
여기서 지연 허용 시간은 데이터 수용 정책인 동시에 과거 계산 상태를 언제까지 유지할 것인가에 대한 정책이기도 합니다.
끝없이 실행되는 시스템에서는 오래된 상태를 언제 정리할지 정하지 않으면 상태 또한 계속 쌓이게 됩니다.
13. How — 여러 번 나온 결과를 어떻게 해석할 것인가
하나의 Window에서 결과를 여러 번 내보내기로 했다면 또 하나를 정해야 합니다.
다음 두 값이 나왔다고 해보겠습니다.
1
2
첫 번째 결과 = 30
두 번째 결과 = 60
60이 “새롭게 발생한 클릭 60건”인지, “현재까지 누적 클릭 60건”인지에 따라 의미가 달라집니다.
Discarding
Trigger 사이에 새롭게 들어온 데이터만 출력합니다.
1
2
3
30
30
40
소비자가 전체 값을 알고 싶다면 직접 더해야 합니다.
1
2
30 + 30 + 40
= 100
Accumulating
현재까지 누적된 값을 매번 출력합니다.
1
2
3
30
60
100
각 값은 그 시점의 전체 클릭 수를 뜻합니다.
1
2
3
현재까지 클릭 수 = 30
현재까지 클릭 수 = 60
현재까지 클릭 수 = 100
광고 대시보드라면 이쪽이 더 읽기 쉽습니다.
1
2
현재 클릭 수 = 100
현재 예상 비용 = 10,000원
이후 지연 데이터가 5건 들어오면 다음처럼 갱신할 수 있습니다.
1
2
현재 클릭 수 = 105
현재 예상 비용 = 10,500원
How는 이렇게 여러 번 내보낸 결과 사이의 관계를 정하는 문제입니다.
14. What, Where, When, How를 한 번에 정리해보기
지금까지 만든 광고 과금 시스템을 네 가지 질문으로 다시 묶어보겠습니다.
What
무엇을 계산할 것인가?
1
2
3
4
캠페인별 클릭 수
광고 비용
= 클릭 수 × CPC
Where
어떤 데이터를 함께 계산할 것인가?
1
2
Event Time 기준
10분 Fixed Window
When
언제 결과를 내보낼 것인가?
1
2
3
4
5
6
7
8
Processing Time 기준으로
주기적으로 Early Result 출력
Watermark가 Window 종료 시점을 지나면
On-Time Result 출력
지연 데이터가 들어오면
필요한 경우 Late Result 출력
How
여러 결과를 어떤 형태로 내보낼 것인가?
1
2
3
4
5
6
Accumulating
30
60
100
105
전체 흐름은 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Click Event
│
▼
campaignId 기준
Group
│
▼
Event Time Window
10:00 ~ 10:10
│
┌─────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Trigger Trigger Watermark
10:03 10:06 통과
│ │ │
▼ ▼ ▼
30 clicks 60 clicks 100 clicks
│
Late Data
│
▼
105 clicks
15. 클릭 이벤트가 두 번 들어오면 어떻게 될까
여기까지는 주로 시간과 지연을 다뤘습니다.
광고 과금 시스템에서는 중복도 무시하기 어렵습니다.
사용자가 광고를 한 번 클릭했다고 해보겠습니다.
1
click_id = 123
이 이벤트를 처리하던 중 Worker에 장애가 발생합니다.
메시지 브로커 입장에서는 처리가 완료됐는지 확인하지 못했기 때문에 같은 이벤트를 다시 전달할 수 있습니다.
1
2
click_id = 123
click_id = 123
실제 클릭은 한 번입니다.
이벤트 개수만 세면 두 번으로 계산됩니다.
1
2 clicks
CPC가 100원이라면 원래 비용은 100원입니다.
1
2
1 click × 100원
= 100원
중복 이벤트까지 계산하면 200원이 됩니다.
1
2
2 clicks × 100원
= 200원
광고 과금에서는 처리 중복이 그대로 금액 차이로 이어집니다.
이 문제에서 Exactly Once가 필요해집니다.
16. Exactly Once를 어떻게 이해해야 할까
Exactly Once라는 표현만 보면 모든 이벤트가 물리적으로 딱 한 번만 실행된다고 생각하기 쉽습니다.
하지만 장애와 재시도가 있는 환경에서는 같은 이벤트가 여러 번 전달되거나 처리될 수 있습니다.
그래서 저는 Exactly Once를 다음처럼 이해하는 편이 더 자연스러웠습니다.
최종적으로 관찰되는 결과가 이벤트를 한 번 처리한 결과와 같아야 합니다.
내부적으로 아래 클릭이 두 번 전달되더라도,
1
2
click_id = 123
click_id = 123
외부에서 본 결과는 이렇게 나와야 합니다.
1
2
click count = 1
charge = 100원
이벤트 ID를 이용한 중복 제거, 상태 관리, 체크포인트, 재처리 같은 방법이 여기에 사용될 수 있습니다.
관심을 둬야 하는 지점은 “이 이벤트가 내부적으로 몇 번 실행됐는가”보다 최종 결과가 한 번 처리한 것과 같은가입니다.
17. 스트리밍 엔진이 정확하다고 외부 시스템까지 안전한 것은 아니다
광고 비용을 계산한 뒤 광고주의 예산에서 실제로 돈을 차감한다고 해보겠습니다.
1
2
3
4
5
6
클릭 발생
↓
100원 과금 계산
↓
광고주 예산에서
100원 차감
스트리밍 엔진 내부의 계산이 정확하더라도 외부 시스템과 연결되는 순간 다른 문제가 생길 수 있습니다.
예를 들어 다음과 같은 상황입니다.
1
2
3
4
5
6
7
8
9
10
11
1. 클릭 이벤트 처리
2. 광고주 잔액에서
100원 차감
3. 처리 완료 상태를 기록하기 전에
Worker 장애
4. 이벤트 재처리
5. 다시 100원 차감
결과는 이렇습니다.
1
2
3
4
5
실제 클릭
1회
실제 차감
200원
그래서 Exactly Once를 생각할 때는 스트리밍 엔진 내부의 연산만 봐서는 부족합니다.
1
2
3
4
5
6
7
Source
↓
Processing
↓
State
↓
Sink
어디까지를 하나의 처리 단위로 보고 어떤 보장을 제공할지 함께 살펴봐야 합니다.
광고 과금이나 결제처럼 외부 상태를 바꾸는 시스템에서는 Side Effect를 어떻게 처리하느냐도 정확성의 일부가 됩니다.
18. Accuracy와 Completeness는 서로 다른 문제다
광고 시스템을 생각하다 보면 “정확하다”는 말에도 두 가지 문제가 섞여 있다는 것을 알 수 있습니다.
실제로 클릭이 105번 발생했다고 해보겠습니다.
현재 시스템에는 100건만 도착했습니다.
1
2
실제 클릭
105
1
2
현재 시스템에 도착
100
1
2
아직 도착하지 않음
5
현재 도착한 100건을 중복 없이 처리했다면 지금 가지고 있는 데이터에 대한 계산은 정확합니다.
1
2
도착한 100개
→ 100번 계산
하지만 실제 발생한 클릭 전체를 기준으로 보면 아직 결과가 완전하지 않습니다.
두 질문을 나눠보면 이해하기 쉽습니다.
1
2
3
4
Accuracy
도착한 데이터를
정확하게 처리했는가?
1
2
3
4
Completeness
필요한 데이터가
충분히 도착했는가?
Exactly Once는 주로 앞의 문제와 관련되어 있습니다.
Watermark는 뒤의 문제를 판단하기 위한 단서를 제공합니다.
이 둘을 나눠 생각하면 Watermark와 Exactly Once가 왜 서로 다른 문제를 풀고 있는지도 선명해집니다.
19. 실시간 대시보드와 실제 정산은 같은 방식으로 만들 필요가 없다
하나의 클릭 스트림을 사용하더라도 결과를 어디에 쓰느냐에 따라 정책을 다르게 가져갈 수 있습니다.
실시간 광고 대시보드라면 이런 식입니다.
1
2
3
4
5
6
7
8
9
10
11
Window
10분
Trigger
1분마다
Accumulation
Accumulating
Late Data
일정 기간 반영
값은 계속 수정될 수 있습니다.
1
2
3
4
5
6
7
예상 광고 비용
10,000원
→
10,300원
→
10,500원
대시보드는 지금까지의 상황을 빨리 보여주는 것이 목적이기 때문에 이런 변화가 자연스럽습니다.
반면 실제 정산에는 더 보수적인 기준을 둘 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
Window
1일
Event Time 기준 집계
Late Data
충분한 시간 허용
중복 처리 방지
필수
최종 결과
높은 정확성 요구
같은 이벤트 스트림이어도 어떤 결과를 얼마나 빨리 제공해야 하는지, 결과가 어느 정도까지 완전해야 하는지에 따라 처리 방식이 달라집니다.
20. 스트리밍 시스템에서 어려운 것은 ‘실시간’보다 시간의 의미다
처음 스트리밍 시스템을 생각하면 이런 구조가 먼저 떠오릅니다.
1
2
3
4
5
Kafka
↓
Flink
↓
Database
저도 처음에는 메시지가 들어오는 즉시 처리하면 그게 스트리밍이라고 생각했습니다.
하지만 《Streaming Systems》 1부에서 반복해서 다루는 문제는 처리 속도보다 시간을 어떤 기준으로 바라볼 것인가에 가깝습니다.
끝이 없는 데이터가 들어오는 상황에서 다음 질문을 정해야 합니다.
1
2
3
What
무엇을 계산할 것인가?
1
2
3
4
Where
어떤 데이터끼리
계산할 것인가?
1
2
3
4
When
언제 결과를
내보낼 것인가?
1
2
3
4
How
여러 번 나온 결과를
어떻게 표현할 것인가?
그리고 실제 이벤트는 순서대로 도착하지 않습니다.
이 문제를 다루기 위해 다음 개념들이 등장합니다.
1
2
3
4
Event Time
Processing Time
Watermark
Late Data
장애와 재시도까지 고려하면 또 다른 문제가 따라옵니다.
1
2
3
4
Duplicate
Retry
Exactly Once
Side Effect
광고 과금 시스템에 적용해보면 전체 흐름은 다음처럼 정리할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
클릭 이벤트
│
▼
Event Time
실제 클릭 시각
│
▼
Window
어느 과금 구간에 속하는가?
│
▼
Watermark
Event Time이 어디까지 왔다고 볼 것인가?
│
▼
Trigger
언제 결과를 내보낼 것인가?
│
▼
Accumulation
기존 결과와 어떤 관계로 내보낼 것인가?
│
▼
Exactly Once
재처리가 있어도 결과는 정확한가?
처음에는 Window, Watermark, Trigger가 비슷한 개념처럼 느껴졌습니다.
광고 과금 문제에 하나씩 대입해보니 역할이 꽤 다르게 보였습니다.
Window는 어떤 이벤트를 같은 계산에 묶을지 정합니다.
Watermark는 Event Time이 어디까지 진행됐다고 판단할지 표현합니다.
Trigger는 현재까지 계산한 결과를 언제 내보낼지 정합니다.
Accumulation은 여러 번 나온 결과 사이의 관계를 정합니다.
Exactly Once는 장애와 재시도가 있어도 결과의 정확성을 유지하는 문제입니다.
그래서 지금은 스트리밍 시스템을 “데이터를 실시간으로 빠르게 처리하는 시스템”보다는 다음과 같이 이해하고 있습니다.
끝을 미리 알 수 없고, 순서가 뒤바뀔 수 있으며, 늦게 도착하거나 중복될 수도 있는 데이터를 어떤 시간 기준으로 묶고 언제 결과로 만들 것인지 결정하는 시스템
광고 시스템에서는 클릭 하나가 몇 초 늦게 도착하는 일이 별것 아닌 것처럼 보일 수 있습니다.
하지만 그 클릭이 광고주에게 청구할 금액을 바꾼다면 그 몇 초를 어떻게 해석할지 정책으로 정해야 합니다.
스트리밍에서 시간과 정확성을 다루는 문제는 기술만으로 결정되지 않습니다.
우리 서비스에서 어떤 이벤트를 어느 시점의 사실로 보고, 언제부터 결과를 확정된 값으로 받아들일 것인가라는 비즈니스 규칙과 맞닿아 있습니다.
전체 흐름을 수도코드로 표현해보기
마지막으로 지금까지 만든 광고 과금 시스템을 하나의 파이프라인처럼 적어보겠습니다.
특정 프레임워크 문법을 따르기보다는 앞에서 설명한 개념을 한 번에 보기 위한 수도코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
clickEvents
.filter(event.type == CLICK)
// What
// 캠페인별 클릭 수와 비용을 계산한다.
.keyBy(event.campaignId)
// Where
// 실제 클릭이 발생한 Event Time을 기준으로
// 10분 단위 Window를 만든다.
.window(
FixedWindow(
size = 10 minutes,
time = EVENT_TIME
)
)
// When
.trigger(
// 실시간 화면을 위해 1분마다 중간 결과를 낸다.
early = every 1 minute,
// Watermark가 Window의 끝을 지나면
// On-Time 결과를 낸다.
onTime = when watermark passes window end,
// 이후 지연 데이터가 들어오면 다시 갱신한다.
late = when late event arrives
)
// 지연 데이터는 30분까지만 반영한다.
.allowedLateness(30 minutes)
// How
// 각 시점의 누적 결과를 출력한다.
.accumulationMode(ACCUMULATING)
.aggregate {
clickCount += 1
charge = clickCount * 100
}
// 장애나 재처리가 있어도
// 같은 클릭이 중복 과금되지 않도록 한다.
.ensureExactlyOnce()
.writeTo(
realtimeDashboard,
billingSystem
)
이 흐름을 더 줄이면 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Click Event
↓
Event Time
↓
Window
↓
Watermark
↓
Trigger
↓
Accumulation
↓
Exactly Once
↓
광고 비용
이렇게 놓고 보니 1부에서 등장하는 개념들이 각각 떨어진 기능이라기보다, 하나의 질문을 차례로 해결하기 위해 등장한 도구처럼 보였습니다.
끝없이 들어오는 이벤트를 어떤 시간 기준으로 묶고, 언제까지 기다리고, 언제 결과를 보여주며, 그 결과를 어떻게 믿을 수 있게 만들 것인가.
1부는 이 질문을 다루고 있습니다.
2부에서는 여기에서 한 단계 더 나아갑니다.
이번 글에서는 각각의 클릭이 어느 시간대에 속하는지, 언제 결과를 내보낼지, 재처리가 발생했을 때 과금을 어떻게 정확하게 유지할지에 집중했습니다.
하지만 실제 광고 시스템에서는 이벤트 하나만 보는 것으로 해결되지 않는 문제가 있습니다.
1
2
3
4
5
광고 노출
↓
광고 클릭
↓
구매 전환
구매가 발생했을 때는 이런 질문이 생깁니다.
이 구매는 어떤 광고 노출과 클릭에서 시작됐을까?
이 질문에 답하려면 서로 다른 시점에 들어온 이벤트의 관계를 기억하고 연결해야 합니다.
여기서부터는 Window 집계만으로는 부족합니다. 상태(State)를 어떻게 유지할 것인지, 그리고 서로 다른 이벤트 스트림을 어떻게 연결할 것인지가 중요해집니다.
이 문제는 2부의 Stream과 Table, Persistent State, Streaming Join을 정리하면서 이어서 살펴보려고 합니다.