<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Late Events on jyukki's Blog</title><link>https://jyukki.com/tags/late-events/</link><description>Recent content in Late Events on jyukki's Blog</description><generator>Hugo -- 0.147.0</generator><language>ko-kr</language><lastBuildDate>Tue, 31 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jyukki.com/tags/late-events/index.xml" rel="self" type="application/rss+xml"/><item><title>백엔드 커리큘럼 심화: 워터마크와 지연 도착 이벤트를 전제로 한 스트림 처리 운영 플레이북</title><link>https://jyukki.com/learning/deep-dive/deep-dive-stream-processing-watermark-late-events-playbook/</link><pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate><guid>https://jyukki.com/learning/deep-dive/deep-dive-stream-processing-watermark-late-events-playbook/</guid><description>실시간 집계가 배치 결과와 다르게 나오는 문제를 줄이기 위해, 워터마크·허용 지연·재처리 규칙을 숫자 기준으로 설계하는 방법을 정리합니다.</description><content:encoded><![CDATA[<h2 id="이-글에서-얻는-것">이 글에서 얻는 것</h2>
<ul>
<li>&ldquo;실시간 대시보드 숫자가 다음 날 정산 결과와 왜 다르지?&rdquo; 같은 문제를 <strong>이벤트 시간(Event Time) 관점</strong>에서 구조적으로 분해할 수 있습니다.</li>
<li>워터마크, 허용 지연(allowed lateness), 재처리(backfill) 정책을 분리해 <strong>정확도·지연·운영비</strong>를 동시에 관리하는 기준을 얻습니다.</li>
<li>팀에서 바로 적용 가능한 **의사결정 기준(임계치·조건·우선순위)**과 운영 체크리스트를 가져갈 수 있습니다.</li>
</ul>
<h2 id="핵심-개념이슈">핵심 개념/이슈</h2>
<h3 id="1-스트림-처리-실패의-본질은-늦게-온-이벤트가-아니라-시간-의미론-부재다">1) 스트림 처리 실패의 본질은 &ldquo;늦게 온 이벤트&quot;가 아니라 &ldquo;시간 의미론 부재&quot;다</h3>
<p>많은 팀이 지연 도착 이벤트를 &ldquo;어쩔 수 없는 데이터 노이즈&quot;로 취급합니다. 그런데 실제 장애를 보면 공통 원인은 늦게 온 이벤트 자체보다, 시간 규칙을 명시하지 않은 설계입니다.</p>
<p>대표 증상은 아래 3가지입니다.</p>
<ol>
<li>실시간 집계(분 단위)가 다음 날 배치 결과와 3~10% 다름</li>
<li>특정 시간대(배포 직후, 네트워크 이슈 직후)에만 수치가 비정상적으로 튐</li>
<li>운영자가 수동 정정 SQL을 반복 실행함</li>
</ol>
<p>이 문제는 보통 &ldquo;수집 지연&quot;과 &ldquo;처리 지연&quot;을 같은 것으로 다뤄서 생깁니다. 이벤트에는 최소한 <code>event_time</code>(실제 발생 시각), <code>ingest_time</code>(수집 시각), <code>process_time</code>(처리 시각)을 분리해야 합니다. 이 기본기는 <a href="/learning/deep-dive/deep-dive-clock-skew-time-semantics-playbook/">Clock Skew 시간 의미론 플레이북</a>과 <a href="/learning/deep-dive/deep-dive-consistency-models/">정합성 모델</a>을 함께 보면 더 선명해집니다.</p>
<h3 id="2-워터마크는-현재까지-믿을-수-있는-이벤트-시간의-경계다">2) 워터마크는 &ldquo;현재까지 믿을 수 있는 이벤트 시간의 경계&quot;다</h3>
<p>워터마크를 단순히 &ldquo;지연 허용 시간&quot;으로 이해하면 설계가 흔들립니다. 워터마크의 핵심은 다음 문장입니다.</p>
<blockquote>
<p>이 시각보다 과거의 이벤트는 대부분 도착했다고 간주하고, 집계를 닫아도 된다.</p></blockquote>
<p>예를 들어 워터마크를 <code>max_event_time - 5분</code>으로 잡으면, 이벤트 시간이 10:00~10:01인 윈도우는 워터마크가 10:01을 넘는 시점에 종료됩니다. 이후 10:00:30 이벤트가 도착하면 &ldquo;late event&quot;로 별도 처리해야 합니다.</p>
<p>운영에서 중요한 포인트는 <strong>워터마크 지연을 고정값으로 박지 않는 것</strong>입니다. 트래픽 패턴, 리전 간 지연, 모바일 오프라인 비율에 따라 2분이 맞을 수도 있고 20분이 맞을 수도 있습니다.</p>
<h3 id="3-allowed-lateness와-재처리-정책을-분리하지-않으면-비용이-폭발한다">3) allowed lateness와 재처리 정책을 분리하지 않으면 비용이 폭발한다</h3>
<p>실무에서 자주 보는 안티패턴은 &ldquo;늦게 와도 다 반영&quot;입니다. 정확도는 올라가지만 상태 저장소(state store)와 체크포인트 크기가 급격히 커집니다.</p>
<p>권장 분리는 다음과 같습니다.</p>
<ul>
<li><strong>경로 A: 온라인 보정 구간</strong>
<ul>
<li>allowed lateness 안에서 도착한 이벤트는 즉시 재집계</li>
</ul>
</li>
<li><strong>경로 B: 오프라인 보정 구간</strong>
<ul>
<li>허용 구간 밖 이벤트는 DLQ/보정 토픽으로 보내고 배치 재처리</li>
</ul>
</li>
</ul>
<p>즉, &ldquo;모든 늦은 이벤트를 같은 비용으로 처리&quot;하지 말고, 지연 정도에 따라 처리 경로를 다르게 둬야 합니다. 이 구조는 <a href="/learning/deep-dive/deep-dive-batch-idempotency-reprocessing/">Batch Idempotency/재처리 전략</a>과 <a href="/learning/deep-dive/deep-dive-reconciliation-ledger-pipeline/">Reconciliation Ledger 파이프라인</a>에서 쓰는 운영 철학과 같습니다.</p>
<h3 id="4-집계-정확도는-알고리즘보다-관측-지표가-먼저다">4) 집계 정확도는 알고리즘보다 관측 지표가 먼저다</h3>
<p>워터마크를 도입해도 지표가 없으면 결국 감으로 튜닝하게 됩니다. 최소 아래 지표는 기본으로 가져가야 합니다.</p>
<ul>
<li><code>late_event_ratio</code>: 전체 이벤트 중 late 비율</li>
<li><code>watermark_lag_sec</code>: 처리 시각 대비 워터마크 지연</li>
<li><code>window_reopen_count</code>: 닫힌 윈도우 재오픈 횟수</li>
<li><code>correction_delta_ratio</code>: 실시간 집계 대비 보정치 비율</li>
<li><code>backfill_job_duration</code>: 재처리 작업 소요 시간</li>
</ul>
<p>초기 기준 예시:</p>
<ul>
<li><code>late_event_ratio</code> 5분 이동평균 <strong>2% 초과</strong> 시 경보</li>
<li><code>watermark_lag_sec</code> p95 <strong>300초 초과</strong> 시 지연 정책 재검토</li>
<li>일별 <code>correction_delta_ratio</code> <strong>1.5% 초과</strong>가 3일 연속이면 윈도우/워터마크 설계 결함으로 분류</li>
</ul>
<h2 id="실무-적용">실무 적용</h2>
<h3 id="1-의사결정-기준숫자조건우선순위">1) 의사결정 기준(숫자·조건·우선순위)</h3>
<p>우선순위는 보통 <strong>정산 정확도 &gt; 사용자 실시간성 &gt; 인프라 비용</strong> 순으로 두는 편이 안전합니다.</p>
<p>실무에서 바로 쓰기 좋은 기준:</p>
<ol>
<li><strong>워터마크 지연 기본값</strong>: 최근 7일 <code>event_time → ingest_time</code> 지연 p99 + 20% 마진
<ul>
<li>예: p99가 180초면 시작값 220초</li>
</ul>
</li>
<li><strong>allowed lateness</strong>: 워터마크 지연의 1.5~2배에서 시작
<ul>
<li>예: 워터마크 220초면 allowed lateness 6~8분</li>
</ul>
</li>
<li><strong>윈도우 재오픈 상한</strong>: 윈도우당 최대 2회
<ul>
<li>2회 초과 이벤트는 오프라인 보정 경로로 전환</li>
</ul>
</li>
<li><strong>재처리 SLA</strong>: 보정 배치는 D+1 06:00 이전 완료</li>
<li><strong>롤백 트리거</strong>: 집계 지연 p95가 2배 이상 증가 + 정확도 개선이 0.3%p 미만이면 정책 롤백</li>
</ol>
<p>핵심은 정확도 목표를 먼저 고정하고, 그 안에서 지연/비용을 최적화하는 순서입니다.</p>
<h3 id="2-추천-아키텍처-실시간-경로와-보정-경로를-분리">2) 추천 아키텍처: 실시간 경로와 보정 경로를 분리</h3>
<p>권장 구성:</p>
<ol>
<li>Kafka ingest (이벤트 스키마 버전 명시)</li>
<li>Stream Processor (event-time window + watermark)</li>
<li>Online Aggregate Store (대시보드/알람용)</li>
<li>Late Event Router (allowed lateness 밖 이벤트 분리)</li>
<li>Backfill Job (배치 재집계)</li>
<li>Reconciliation Report (실시간 vs 정산 차이 리포트)</li>
</ol>
<p>이 구조를 쓰면 &ldquo;실시간 응답성&quot;과 &ldquo;최종 정합성&quot;을 동시에 만족시키기 쉽습니다. 스키마 안정성은 <a href="/learning/deep-dive/deep-dive-event-schema-registry-compatibility-playbook/">Event Schema Registry 호환성 플레이북</a>과 같이 운영해야 장애를 줄일 수 있습니다.</p>
<h3 id="3-운영-파라미터-튜닝-순서4주-플랜">3) 운영 파라미터 튜닝 순서(4주 플랜)</h3>
<p><strong>1주차: 측정 고정</strong></p>
<ul>
<li>이벤트 지연 분포(p50/p90/p99) 수집</li>
<li>실시간 집계와 정산 결과 차이의 기준선 측정</li>
</ul>
<p><strong>2주차: 보수적 워터마크 적용</strong></p>
<ul>
<li>넓은 워터마크(예: p99+30%)로 시작</li>
<li>late_event_ratio와 지연 증가폭을 관찰</li>
</ul>
<p><strong>3주차: allowed lateness 조정</strong></p>
<ul>
<li>온라인 보정 구간을 단계적으로 축소(예: 15분→10분→7분)</li>
<li>상태 저장소 크기·체크포인트 시간 모니터링</li>
</ul>
<p><strong>4주차: 재처리 자동화</strong></p>
<ul>
<li>DLQ→백필 파이프라인 자동 연결</li>
<li>보정 리포트를 운영 회의 지표로 고정</li>
</ul>
<h3 id="4-샘플-의사코드-개념용">4) 샘플 의사코드 (개념용)</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>onEvent(event):
</span></span><span style="display:flex;"><span>  et = event.event_time
</span></span><span style="display:flex;"><span>  updateMaxEventTime(et)
</span></span><span style="display:flex;"><span>  watermark = maxEventTime - watermarkDelay
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  if et &gt;= watermark - allowedLateness:
</span></span><span style="display:flex;"><span>      upsertWindowAggregate(event)
</span></span><span style="display:flex;"><span>  else:
</span></span><span style="display:flex;"><span>      publishToLateEventTopic(event)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>onWindowClose(window):
</span></span><span style="display:flex;"><span>  emitPreliminaryResult(window)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>onBackfillCompleted(window):
</span></span><span style="display:flex;"><span>  emitCorrectedResult(window)
</span></span><span style="display:flex;"><span>  recordCorrectionDelta(window)
</span></span></code></pre></div><p>여기서 중요한 건 <code>preliminary result(잠정값)</code>와 <code>corrected result(보정값)</code>를 모델에서 구분하는 것입니다. 둘을 같은 의미로 취급하면 소비자 시스템에서 중복 정산/중복 알림이 발생합니다.</p>
<h2 id="트레이드오프주의점">트레이드오프/주의점</h2>
<ol>
<li>
<p><strong>워터마크를 짧게 잡으면 실시간성은 좋아지지만 보정 비용이 증가한다</strong><br>
집계를 빨리 닫을수록 late 이벤트가 늘어 배치 보정 비용이 커집니다.</p>
</li>
<li>
<p><strong>allowed lateness를 길게 잡으면 정확도는 좋아지지만 상태 관리 비용이 급증한다</strong><br>
메모리/디스크/체크포인트 부하가 커져 장애 반경이 커질 수 있습니다.</p>
</li>
<li>
<p><strong>지표 없이 튜닝하면 팀마다 다른 숫자를 진실로 믿게 된다</strong><br>
데이터팀, 백엔드팀, 비즈니스팀이 서로 다른 집계를 들고 논쟁하는 상태가 반복됩니다.</p>
</li>
<li>
<p><strong>late 이벤트를 무조건 버리면 단기 성능은 좋아도 장기 신뢰를 잃는다</strong><br>
정산, 과금, SLA 리포트처럼 돈/신뢰가 걸린 영역에서는 반드시 보정 경로를 유지해야 합니다.</p>
</li>
</ol>
<h2 id="체크리스트-또는-연습">체크리스트 또는 연습</h2>
<h3 id="체크리스트">체크리스트</h3>
<ul>
<li><input disabled="" type="checkbox"> 이벤트에 <code>event_time</code>, <code>ingest_time</code>, <code>process_time</code>이 분리 저장된다.</li>
<li><input disabled="" type="checkbox"> 워터마크/allowed lateness 기준이 문서화되어 있다.</li>
<li><input disabled="" type="checkbox"> late 이벤트의 온라인/오프라인 처리 경로가 분리되어 있다.</li>
<li><input disabled="" type="checkbox"> 실시간 집계와 정산 집계 차이를 주간 지표로 추적한다.</li>
<li><input disabled="" type="checkbox"> 보정 결과가 소비자 시스템에서 중복 반영되지 않도록 버전/타입이 구분된다.</li>
</ul>
<h3 id="연습-과제">연습 과제</h3>
<ol>
<li>최근 14일 이벤트를 기준으로 <code>event_time→ingest_time</code> 지연 분포를 구하고, p99 기반 워터마크 시작값을 계산해 보세요.</li>
<li>allowed lateness를 5분/10분/20분으로 바꿔 <code>late_event_ratio</code>, 상태 저장소 크기, 정산 오차를 비교해 보세요.</li>
<li>잠정값/보정값 이중 모델을 적용했을 때, 알림 중복률과 정산 오차가 얼마나 줄어드는지 실험해 보세요.</li>
</ol>
<h2 id="관련-글">관련 글</h2>
<ul>
<li><a href="/learning/deep-dive/deep-dive-clock-skew-time-semantics-playbook/">Clock Skew를 전제로 한 시간 의미론 설계</a></li>
<li><a href="/learning/deep-dive/deep-dive-consistency-models/">정합성 모델(Strong/Eventual) 의사결정</a></li>
<li><a href="/learning/deep-dive/deep-dive-batch-idempotency-reprocessing/">Batch 멱등성과 재처리 설계</a></li>
<li><a href="/learning/deep-dive/deep-dive-reconciliation-ledger-pipeline/">Reconciliation Ledger 파이프라인</a></li>
<li><a href="/learning/deep-dive/deep-dive-event-schema-registry-compatibility-playbook/">Event Schema Registry 호환성 운영</a></li>
</ul>
]]></content:encoded></item></channel></rss>