LabHub
배우기 러닝패스 코스

데이터 파이프라인 · 이벤트 시간 워터마크와 늦게 온 자료 · 이론

워터마크는 사실이 아니라 약속이다

LabHub 에서 이어서 보기

한 줄 요약

워터마크는 "이 시각까지는 다 왔다" 는 관측이 아니라 약속이고, 그 약속을 어긴 자료를 만났을 때 무엇을 할지 미리 정해 두지 않으면 어제 낸 숫자를 설명할 수 없게 된다.

왜 이게 필요했나

이 코스의 앞 실습에서 워터마크를 이미 한 번 다뤘다. 적재한 자료의 최대 시각을 표에 적어 두고 다음 실행이 그 뒤만 읽게 하는 것이었다. 그 워터마크가 답하는 질문은 하나다 — 어디까지 읽었는가.

그런데 그 질문만으로는 풀리지 않는 일이 있다. 모바일 앱은 지하철에서 신호를 잃었다가 몇 분 뒤에, 비행기 모드였던 단말은 몇 시간 뒤에 이벤트를 보낸다. 그 이벤트의 일어난 시각은 이미 지나간 시각이다. 시간 기준 워터마크는 그 자료를 영영 못 본다. 그리고 우리는 이미 그 시간대의 집계를 내보냈다.

그래서 아침 회의에서 이런 말이 나온다. "어제 대시보드에서 본 숫자랑 오늘 숫자가 달라요." 이 질문에 대답하지 못하면, 그 대시보드는 그날부터 아무도 믿지 않는다.

어떻게 동작하나

먼저 시간이 세 가지라는 것부터 갈라야 한다. 이벤트 시간은 실제로 일어난 때이고, 수집 시간은 우리 시스템에 들어온 때이고, 처리 시간은 우리가 계산한 때다. 분석의 기준은 언제나 이벤트 시간이다. 처리 시간을 기준으로 삼으면 재처리할 때마다 답이 달라진다.

이벤트 시간으로 창(window)을 나누면 곧바로 문제가 하나 생긴다. 이 창을 언제 닫을 것인가. 영원히 열어 두면 결과가 나오지 않고, 너무 일찍 닫으면 아직 오지 않은 자료를 빠뜨린다.

워터마크가 그 판단을 대신한다. [Flink 문서](https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/datastream/event-time/generating_watermarks/)는 워터마크를 "이벤트 시간에서의 진행 상황을 시스템에 알려 주는 것" 이라고 설명한다. 흔한 계산식은 이렇다.

워터마크 = 지금까지 본 최대 이벤트 시간 − 허용 지연창이 닫힌다 = 워터마크가 그 창의 끝을 지났다

여기서 두 가지를 놓치기 쉽다.

첫째, 워터마크는 뒤로 가지 않는다. 최대 이벤트 시간은 단조증가하므로 워터마크도 단조증가한다. 늦게 온 이벤트가 워터마크를 되돌리지 않는다. 되돌릴 수 있게 만들면 이미 닫은 창이 다시 열렸다 닫히기를 반복하고, 어떤 값도 확정되지 않는다.

둘째, 늦음은 자료의 성질이 아니라 도착 차례의 성질이다. 같은 이벤트라도 언제 도착했느냐에 따라 늦은 것이 되기도 하고 아니기도 하다. 그래서 판정은 그 이벤트보다 먼저 도착한 것들로 계산한 워터마크로 한다. 여기서 흔히 나는 사고가 있다 — 자료를 다루기 편하라고 이벤트 시간으로 한 번 정렬해 버리는 것이다. 그 순간 도착 차례가 사라지고, 늦은 자료가 0건으로 나온다. 실측해 보면 앞뒤가 뒤바뀐 도착이 스무 건 넘게 있던 자료에서도 정확히 0이 나온다. 문제가 없어서가 아니라 볼 수 있는 눈을 없앤 것이다.

허용 지연은 정확도와 지연을 맞바꾸는 손잡이다. 크게 잡으면 늦은 자료를 더 많이 받아들이지만 창이 그만큼 늦게 닫히고, 작게 잡으면 빨리 내지만 더 많이 놓친다. 이 값은 감으로 정하는 것이 아니라 실제 지연 분포에서 읽어 낸다. 지연의 중앙값과 95분위와 최댓값을 재 보면, 대개 95분위 근처에서 꺾이고 그 위로는 꼬리가 길게 늘어진다. 최댓값에 맞추면 그 하나 때문에 모두가 기다린다.

늦게 온 자료를 어떻게 할 것인가

워터마크를 넘겨 온 자료에 대한 선택지는 둘이다.

버린다. 확정된 숫자가 절대 바뀌지 않는다. 대신 그만큼이 조용히 사라지므로, 버린 건수와 금액을 반드시 따로 센다. 세지 않고 버리면 나중에 "왜 우리 합계가 안 맞죠" 를 설명할 근거가 없다.

늦은 갱신으로 반영한다. 같은 Flink 문서의 창 설명은 허용 지연을 둔 뒤 늦게 도착한 원소가 창을 다시 발화시킬 수 있고, 그때 나오는 값은 앞 계산의 갱신된 결과로 다뤄야 한다고 적어 둔다. 하류가 그것을 갱신으로 받아들이지 못하고 덧붙이기만 하면 중복이 생긴다. 그래서 이 선택은 우리 쪽만의 결정이 아니다.

어느 쪽이든 닫는 순간의 값과 그 뒤의 정정을 따로 남기는 것이 핵심이다. 하나로 합쳐 두면 "어제 숫자가 오늘 바뀌었다" 에 대답할 수 없지만, 따로 남기면 바뀐 양이 곧 대답이 된다.

현장에서 만나는 모습

첫째, 창이 안 닫힌다. 자료가 끊기면 최대 이벤트 시간이 멈추고, 워터마크도 멈추고, 창은 영원히 열려 있다. 같은 Flink 문서가 다루는 유휴(idleness) 문제가 이것이다. 조용한 파티션 하나가 전체를 붙잡는다.

둘째, 허용 지연을 늘려 놓고 잊는다. 사고가 나면 "일단 넉넉히" 로 늘어나고, 그대로 굳는다. 여섯 시간 뒤에 나오는 대시보드는 실시간이 아니다. 늘린 값은 되돌릴 날짜와 함께 적어 둔다.

셋째, 재처리와 늦은 자료가 섞인다. 과거 구간을 다시 돌리는 일과 늦게 온 자료를 반영하는 일은 둘 다 "옛 숫자가 바뀐다" 로 보인다. 원인이 다르므로 기록도 따로 남겨야 한다.

넷째, 늦은 자료가 한쪽에만 몰린다. 지역이나 단말 기종으로 갈라 보면 특정 집단만 크게 늦다. 전체 평균으로만 보면 그 집단의 자료는 늘 버려지고, 그 사실을 아무도 모른다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

지하철과 비행기 모드를 지나온 주문 이벤트를 만들고, 도구 wm.py 를 한 단계씩 키운다. 지연을 분포로 재고, 도착 순서대로 워터마크를 움직이고, 이벤트 시간으로 창을 나누고, 창이 닫힌 뒤 온 자료를 가른다. 그다음 같은 자료를 버리는 정책과 반영하는 정책으로 각각 집계해 어느 창이 얼마나 달라지는지 보고, 닫는 순간의 값과 정정을 따로 낸다. 마지막으로 그 숫자로 보고서를 쓴다. 채점기는 매번 다른 창 크기와 허용 지연으로 여러분의 도구를 실제로 돌려 답을 대조한다.