무계 데이터
고친 사람 github-actions[bot]
무계 데이터는 끝나는 때 없이 계속 들어옵니다. 아무리 기다려도 「이제 다 모였다」는 순간이 오지 않습니다. 서비스가 도는 동안 쌓이는 주문 기록과 클릭 기록이 그렇습니다. 그래서 다 모은 뒤에 한 번에 계산하는 방식이 통하지 않습니다.
쉽고 빠른 이해
무계 데이터는 끝없이 들어오는 데이터입니다. 쇼핑몰에 들어오는 주문은 서비스를 닫는 날까지 멈추지 않습니다.
이 구분이 필요한 까닭은 끝이 있느냐에 따라 계산하는 법이 달라지기 때문입니다. 끝이 있으면 다 모은 뒤 한 번에 계산합니다. 끝이 없으면 「다 모은 뒤」가 오지 않아서, 그렇게 짠 계산은 끝나지 않습니다.
어떻게 다루나:
- 한 시간치나 하루치로 잘라 조각마다 따로 계산합니다. 자른 조각에는 끝이 있습니다
- 아니면 들어오는 대로 계산해서 결과를 계속 고쳐 냅니다
- 늦게 도착하는 데이터를 언제까지 기다릴지 정해 둡니다
무엇이 나빠지나 — 결과가 언제나 「지금까지」의 답입니다. 늦게 온 데이터 때문에 이미 낸 답을 고쳐야 할 때도 있습니다. 들어온 것을 다 쌓아 둘 수 없어서 무엇을 기억하고 무엇을 버릴지도 정해야 합니다.
무엇을 고르나 — 하루 한 번 보는 보고서라면 잘라서 계산해도 답이 제때 나옵니다. 몇 초 안에 답이 필요하면 들어오는 대로 계산합니다.
상세
양동이에 담긴 물은 다 부은 뒤에 무게를 잴 수 있습니다. 강물은 그렇게 잴 수 없습니다. 아무리 기다려도 강물이 다 흘러오는 때는 없습니다. 강물의 양을 알고 싶으면 「한 시간 동안 흐른 양」처럼 구간을 정해서 잽니다.
무계 데이터는 끝이 정해지지 않아 계속 늘어나는 데이터입니다. 이름의 무(無)는 「없다」, 계(界)는 「경계」라는 뜻입니다. 영어로는 unbounded data 라고 부릅니다. 끝이 정해진 쪽은 유계 데이터(bounded data)입니다.
이런 데이터는 대개 메시지 큐를 타고 한 건씩 들어옵니다. 메시지 큐는 주문 같은 기록을 보내는 쪽에서 받는 쪽으로 한 건씩 실어 나르는 통로입니다.
아래에서는 먼저 끝이 있느냐가 무엇을 가르는지 코드로 봅니다. 이어서 끝없는 데이터를 다루는 두 길을 봅니다. 마지막으로 두 길이 함께 부딪히는 늦게 오는 데이터를 봅니다.
끝이 있느냐로 갈리는 계산
유계 데이터는 언젠가 다 모입니다. 어제 하루의 주문은 오늘 아침이 되면 더 늘지 않습니다. 다 모인 데이터는 처음부터 끝까지 훑어서 정렬하거나 합계를 낼 수 있습니다. 이렇게 다 모은 뒤 한 번에 계산하는 방식이 배치 처리입니다.
무계 데이터에는 그런 순간이 오지 않습니다. 쇼핑몰의 주문 기록 전체는 지금 이 순간에도 한 줄씩 늘어납니다. 「전체 주문의 합계」를 내려고 끝까지 읽으면 끝에 닿지 않습니다.
아래 파이썬 코드가 그 차이를 보입니다. orders() 는 다음 주문이 올 때까지 기다렸다가 하나씩 돌려주는 함수입니다. yield 는 값 하나를 내주고 다음에 불릴 때 이어서 도는 파이썬 문법입니다. 오른쪽 주석이 그 줄이 내는 값입니다.
def orders():
while True:
yield next_order()
day = load("10-15") # 어제 주문
sum(o.amount for o in day) # 1820000
sum(o.amount for o in orders()) # 안 끝남
첫 합계는 load 로 읽어 온 어제 주문 목록을 더합니다. 목록에 끝이 있으니 답이 나옵니다. 둘째 합계는 끝없이 이어지는 주문을 다 더하려 해서 영영 답을 내지 않습니다.
두 줄은 생김새가 같습니다. 다른 것은 데이터에 끝이 있느냐 하나입니다.
이 차이를 표로 모으면 아래와 같습니다.
| 유계 데이터 | 무계 데이터 | |
|---|---|---|
| 끝 | 있다 | 없다 |
| 전체 합계·정렬 | 다 모은 뒤 한 번에 낸다 | 전체가 없어 낼 수 없다 |
| 결과 | 한 번 내면 끝난다 | 「지금까지」의 답을 계속 고친다 |
| 처리 방식 | 배치 처리 | 잘라서 배치 처리 · 들어오는 대로 계산 |
같은 데이터가 둘 다 되는 경우
유계와 무계는 데이터가 어디서 왔느냐로 갈리지 않습니다. 그 데이터를 어디까지로 정해서 보느냐로 갈립니다. 같은 주문 기록이라도 「어제 하루치」로 정하면 유계 데이터입니다. 「앞으로 들어올 주문까지 전부」로 보면 무계 데이터입니다.
크기와도 상관이 없습니다. 몇 년 치 기록을 담은 큰 파일도 더 늘지 않으면 유계 데이터입니다. 하루에 열 건만 들어오는 기록도 끝이 정해져 있지 않으면 무계 데이터입니다. 「너무 커서 한 번에 못 본다」와 「끝이 없다」는 서로 다른 문제입니다.
잘라서 유계로 만드는 방법
끝없는 데이터를 다루는 오래된 방법은 시간으로 자르는 것입니다. 한 시간치나 하루치로 자른 조각에는 끝이 있습니다. 조각 하나하나를 유계 데이터로 보고 배치 처리로 계산합니다. 밤마다 어제치 기록을 집계하는 작업이 이 방법입니다.
flowchart TD
S["끝없이 들어오는 주문"] --> A["0시~1시 조각 · 닫힘"]
S --> B["1시~2시 조각 · 닫힘"]
S --> C["2시~3시 조각 · 차는 중"]
A --> R1["배치 작업 · 0시대 집계"]
B --> R2["배치 작업 · 1시대 집계"]
그림에서 앞의 두 조각은 이미 닫혀서 배치 작업이 돌았습니다. 셋째 조각은 아직 주문이 들어오는 중이라 결과가 없습니다. 3시가 되어 이 조각이 닫혀야 다음 배치 작업이 돕니다.
이 방법의 첫째 약점은 결과가 늦다는 것입니다. 한 시간 조각이면 그 한 시간이 끝나야 답이 나옵니다. 하루 조각이면 답이 하루 늦습니다.
둘째 약점은 자르는 선에 걸친 일이 쪼개진다는 것입니다. 자정 직전에 들어와 자정 뒤에 나간 사용자의 방문은 두 조각에 나뉘어 들어갑니다. 조각마다 따로 세면 방문 하나를 둘로 셉니다.
들어오는 대로 처리하는 방법
다른 길은 데이터가 들어오는 대로 계산하는 것입니다. 이 방식을 스트림 처리라고 부릅니다. 주문 하나가 들어올 때마다 합계에 더하고 바뀐 결과를 바로 내놓습니다. 결과는 언제나 「지금까지」의 답입니다. 새 주문이 오면 또 바뀝니다.
들어오는 대로 계산하려면 앞에서 본 것을 기억해 둬야 합니다. 합계라면 지금까지의 합 하나만 들고 있으면 됩니다. 처리하는 쪽이 이렇게 들고 있는 값을 상태라고 부릅니다. 새 주문이 오면 이 값에 더하기만 하면 됩니다.
그런데 끝없는 흐름 전체의 합계는 영영 확정되지 않습니다. 그래서 「한 시간치 합계」처럼 구간을 정해 구간마다 답을 냅니다. 끝없는 흐름 위에 정한 이런 구간을 윈도라고 부릅니다. 구간이 끝나면 그 구간의 결과를 내고 상태는 버립니다.
윈도는 앞 소절의 조각과 같은 자르기를 흐름 위에서 하는 것입니다. 다른 점은 계산하는 때입니다. 조각은 닫히기를 기다렸다가 한꺼번에 계산합니다. 윈도는 주문이 들어올 때마다 그 윈도의 상태에 바로 더합니다.
윈도를 나누는 방식은 몇 가지로 갈립니다. 아래 표는 흔히 쓰는 셋입니다. 각각이 어떻게 도는지는 그 항목이 다룹니다.
| 윈도 | 나누는 법 | 쓰는 예 |
|---|---|---|
| 텀블링 윈도 | 같은 길이로 겹치지 않게 자른다 | 매시 정각부터 한 시간씩 주문 합계 |
| 슬라이딩 윈도 | 같은 길이 구간을 조금씩 밀며 겹치게 잡는다 | 1분마다 최근 5분의 요청 수 |
| 세션 윈도 | 활동이 끊긴 틈을 경계로 자른다 | 30분 동안 클릭이 없으면 방문 하나가 끝난 것으로 본다 |
세션 윈도는 앞 소절의 자정에 걸친 방문 문제를 풉니다. 시계의 정각이 아니라 사용자의 활동이 끊긴 틈을 보고 자르기 때문입니다.
늦게 도착하는 데이터
두 길이 함께 부딪히는 문제가 있습니다. 데이터는 일어난 순서대로 도착하지 않습니다. 지하철에서 12시 58분에 휴대폰으로 넣은 주문은 전파가 끊긴 동안 휴대폰에 머물다가 1시 3분에 서버에 닿습니다.
그래서 한 건의 데이터에 시각이 둘 붙습니다. 일이 일어난 시각을 이벤트 시간이라고 부릅니다. 앞의 주문이라면 휴대폰에서 주문을 넣은 12시 58분입니다.
처리하는 쪽에 도착한 시각은 처리 시간입니다. 같은 주문의 처리 시간은 서버에 닿은 1시 3분입니다.
유계 데이터에서는 이 차이가 크게 문제 되지 않습니다. 다 모인 뒤에 계산하므로 늦게 온 것도 이미 들어와 있습니다.
sequenceDiagram
participant 휴대폰
participant 집계 작업
Note over 휴대폰: 12시 58분 주문 · 전파 끊김
집계 작업->>집계 작업: 1시 정각 · 12시대 윈도 결과를 낸다
휴대폰->>집계 작업: 1시 3분 도착 · 이벤트 시간 12시 58분
Note over 집계 작업: 이미 닫은 12시대 윈도의 주문
그림이 무계 데이터의 고민을 보입니다. 12시대 윈도의 결과를 1시 정각에 내면 1시 3분에 도착한 12시 58분 주문이 빠집니다. 늦게 올 데이터를 다 기다리려 하면 끝이 없는 데이터라서 결과를 영영 못 냅니다.
그래서 처리하는 쪽은 기준을 하나 둡니다. 「이 시각 전에 일어난 일은 거의 다 도착했다」는 추정입니다. 이 추정을 워터마크라고 부릅니다.
워터마크가 어느 윈도의 끝 시각을 지나면 그 윈도를 닫고 결과를 냅니다. 예를 들어 집계 작업이 1시 5분이 되어서야 워터마크를 1시로 올린다고 합시다. 그러면 12시대 윈도는 1시 5분에 닫힙니다. 1시 3분에 온 12시 58분 주문도 결과에 들어갑니다.
워터마크보다 늦게 오는 데이터도 생깁니다. 이런 데이터를 늦은 데이터라고 부릅니다. 처리하는 쪽은 늦은 데이터를 버리거나, 이미 낸 결과를 고쳐 다시 내거나, 따로 모아 둡니다. 셋 중 무엇을 고르느냐는 결과를 얼마나 빨리 내야 하고 얼마나 맞아야 하느냐에 달렸습니다.
언제 무계 데이터로 다루나
데이터에 끝이 없다고 해서 늘 스트림 처리를 고를 일은 아닙니다. 하루에 한 번 보는 매출 보고서라면 하루치로 잘라 배치 처리로 돌려도 답이 제때 나옵니다. 늦은 데이터도 다음 날 아침이면 대부분 들어와 있습니다.
결과를 몇 초 안에 봐야 하는 일이면 사정이 다릅니다. 이상 거래를 잡거나 실시간 순위를 매기는 일은 한 시간 늦은 답이 쓸모가 없습니다. 이런 일에서는 무계 데이터를 들어오는 대로 처리합니다. 윈도와 워터마크도 함께 정합니다.
관련 항목
무계 데이터와 맞세워지는 대립 개념
유계 데이터 · 배치 처리 · 배치 창 · 마이크로 배치 · 스냅샷
무계 데이터를 다루는 처리 방식
스트림 처리 · 상태 저장 처리 · 실시간 처리 · 증분 계산 · 람다 아키텍처 · 카파 아키텍처
끝없는 흐름을 자르는 윈도
윈도 · 텀블링 윈도 · 슬라이딩 윈도 · 세션 윈도
무계 데이터의 늦은 도착을 다루는 시간 개념
이벤트 시간 · 처리 시간 · 워터마크 · 늦은 데이터 · 순서 뒤바뀜
무계 데이터가 실려 오는 통로
스트림 · 이벤트 스트림 · 메시지 큐 · 발행-구독 · 로그 · 변경 데이터 캡처 · Apache Kafka
무계 데이터를 처리하는 스트림 처리 엔진
Apache Flink · Apache Beam · Spark Structured Streaming · Kafka Streams · Google Cloud Dataflow
무계 데이터가 속하는 상위 분류
다른 이름: unbounded data · unbounded dataset · 무한 데이터 · 무한 데이터셋 · 비유계 데이터