Kafka Streams 윈도우 도입기
Kafka Streams 윈도우와 timestamp extractor로 스파이크 정산을 안정화했다.
정산 프로세스를 배치로 처리할지 Kafka 기반 이벤트 처리로 갈지 검토한 끝에, 준실시간 처리와 확장성을 위해 이벤트 기반 구조를 선택했다. 다만 주문 마감과 새벽 도착 보장이라는 특성 때문에 특정 시간대에 데이터가 몰리는 스파이크성 트래픽을 안정적으로 묶어 처리할 필요가 있었다.
핵심 요구사항은 세 가지였다.
- 5분 단위로 수집한 원천 데이터를 Kafka로 발행할 것
- 스파이크성 데이터는 상품 코드 + 일정 시간 간격 기준으로 집계할 것
- 스파이크성 데이터가 아닌 항목은 집계 없이 정산 테이블에 즉시 반영할 것
이를 위해 Kafka Streams의 tumbling window를 선택했다. 이벤트가 겹치지 않아야 하고, 집계 결과도 한 번만 발행되어야 했기 때문이다. 예시로는 5분 윈도우, 1분 grace period를 두고 groupByKey → windowedBy(TimeWindows.ofSizeAndGrace(...)) → reduce() → suppress() 흐름으로 최종 결과만 내보내도록 구성했다.
여기서 중요한 개념은 grace period와 stream time이다. grace period는 늦게 도착한 이벤트를 어디까지 허용할지 정하는 기준이고, 실제 윈도우 종료와 발행 시점은 단순한 이벤트 시간이 아니라 Kafka Streams가 내부적으로 관리하는 스트림 시간에 의해 결정된다. 즉, 윈도우 시간만 끝났다고 바로 결과가 나가는 것이 아니라, 이후 이벤트가 들어와 스트림 시간이 충분히 전진해야 닫힘이 인지된다.
시행착오도 있었다. 첫 번째 문제는 데이터 생성 시간과 Kafka 발행 시간이 달라 자정 경계에서 정산 날짜가 어긋나는 일이었다. 이를 해결하기 위해 Kafka 레코드의 발행 시각 대신 TimestampExtractor로 생성 시간을 이벤트 시간으로 재정의했다.
두 번째 문제는 윈도우 종료 시점 이후에 새로운 이벤트가 아예 들어오지 않는 경우였다. Processor 내부에서 forward()로 더미 이벤트를 흘려보내도 스트림 시간은 바뀌지 않았고, WindowStore를 직접 스캔해 강제 발행하면 내부 상태가 닫힌 것으로 인식되지 않아 중복 발행이 발생했다.
최종적으로는 외부에서 더미 이벤트를 토픽에 주입해 스트림 시간을 강제로 전진시키는 방식으로 해결했다. 이때 Kafka Streams의 스트림 시간은 파티션별로 독립적이므로, 모든 더미 이벤트가 한 파티션에 몰리지 않도록 파티션별 키를 다르게 하거나 파티션 번호를 지정해야 했다. Processor에서는 WALL_CLOCK_TIME 기준으로 3분마다 펑크추에이션을 돌려 더미 이벤트를 발행했고, 이를 통해 각 파티션의 윈도우가 정상적으로 닫히게 만들었다.
결론적으로 이번 작업은 단순히 시간을 자르는 문제가 아니라, 시간과 이벤트의 관계를 정확히 정의하는 설계였다. 윈도우 기반 정산은 경계 시점 처리와 재계산 정책까지 함께 고려해야 하며, Kafka Streams는 결국 입력 이벤트를 통해서만 시간이 흐른다는 점을 다시 확인하게 됐다.
이 요약은 원문 이해를 돕기 위한 큐레이션입니다. 저작권은 원저작자에게 있으며, 정확한 내용과 맥락은 원문을 확인하세요.
요약 오류, 출처 표기 문제, 삭제 요청은 문의 · 건의로 알려주세요.