Kafka

[Kafka] 9. Kafka Streams

noahkim_ 2025. 5. 12. 12:14

3. Developer Guide

항목명 간단 설명
Writing a Streams Application 스트림 처리 로직을 작성하고 실행하는 기본 구조 제공 (토폴로지 정의)
Configuring a Streams Application 설정 지정 (브로커 주소, 앱 ID, 직렬화 방식 등)
Streams DSL 고수준 연산을 위한 간편 API (map, filter 등)
Processor API 사용자 정의 처리 로직을 위한 저수준 API
Naming Operators DSL 연산자에 이름 붙이기 (디버깅/모니터링 용이)
Data Types & Serialization 메시지 직렬화/역직렬화 방식 정의
Testing TopologyTestDriver로 로컬 테스트 가능
Interactive Queries 상태 저장소 데이터를 외부에서 조회 가능
Memory Management 메모리 관련 설정 (RocksDB, 캐시 등)
Running Applications 다양한 환경에서 실행 가능
Managing Topics 입력/출력/체인지로그 토픽 자동 생성 및 관리
Streams Security 보안 설정 적용 가능 (TLS, SASL 등)
Application Reset Tool CLI로 상태 초기화 가능

 

4. Core Concepts

  • Kafka Streams는 Kafka에 저장된 데이터를 처리하고 분석하기 위한 Java 기반 클라이언트 라이브러리

 

주요 특징

  • Java 애플리케이션에 쉽게 통합 가능
  • Kafka 외의 외부 시스템 의존 없음
  • 상태 저장 기능을 통한 빠른 집계 및 조인 가능
  • exactly-once processing 보장
  • 낮은 지연 시간 (밀리초 단위의)
  • Streams DSL / Processor API 제공

 

Stream Processing Topology

Kafka Streams 애플리케이션은 topology라는 그래프 구조로 처리 로직을 구성한다.

  • Stream: 시간순으로 정렬된 변경 불가능한(key-value) 데이터 기록들의 연속
  • Processor Topology: 프로세서(노드)와 스트림(엣지)으로 구성된 처리 그래프
  • Stream Processor: 한 레코드씩 받아 처리 후, 새로운 결과를 하위 프로세서로 전달

특수 프로세서

  • Source Processor: Kafka 토픽에서 데이터를 읽어오는 입구
  • Sink Processor: 결과를 Kafka 토픽에 기록하는 출구

Streams DSL 또는 Processor API를 통해 토폴로지를 정의할 수 있다.

 

 

시간 개념 (Time Semantics)

스트림 처리에서 시간은 매우 중요하다. Kafka Streams는 다음 3가지 시간 개념을 구분한다.

  1. 이벤트 시간(Event Time): 데이터가 실제 발생한 시각
  2. 처리 시간(Processing Time): 애플리케이션이 데이터를 처리한 시각
  3. 수집 시간(Ingestion Time): Kafka 브로커가 데이터를 토픽에 저장한 시각

Kafka의 설정에 따라 메시지에 자동으로 부여되는 타임스탬프가 어떤 의미를 갖는지 결정된다.

Kafka Streams의 TimestampExtractor를 통해

  • 레코드에 내장된 시간, 현재 시각 등을 기반으로 타임스탬프를 정의할 수 있다.

 

스트림과 테이블의 이중성 (Stream-Table Duality)

Kafka Streams는 스트림과 테이블 간의 이중성(duality) 개념을 지원한다.

  • 스트림 → 테이블: 스트림은 테이블의 변경 로그. 전체를 재생하면 현재 상태를 복원 가능
  • 테이블 → 스트림: 테이블은 특정 시점의 스트림 스냅샷. 키-값 쌍을 반복(iterate)하면 스트림으로 표현 가능

이 개념을 통해 CDC(Change Data Capture), 상태 복제, 인터랙티브 쿼리 등이 가능하다.
Kafka Streams는 이를 위해 KStream, KTable, GlobalKTable을 제공한다.

 

집계 (Aggregations)

여러 입력 레코드를 하나로 통합하는 연산이다 (예: count, sum).

  • 입력: KStream 또는 KTable
  • 출력: 항상 KTable
  • out-of-order 데이터가 도착하면, 기존 값을 새 값으로 덮어쓴다 (same key)

 

윈도우(Windowing)

윈도잉은 특정 키에 대해 레코드를 시간 창으로 그룹화하여 상태 기반 연산을 수행할 수 있게 해준다.

  • 윈도우는 키 단위로 관리됨
  • grace period를 설정하면 지연 도착한 데이터도 허용 가능
  • Kafka Streams는 out-of-order 데이터를 적절히 처리 가능 (이벤트 시간 기반일 때만 적용)

 

상태 저장 (State)

Kafka Streams는 stateful 연산을 위해 로컬 상태 저장소를 제공한다.

  • Join, Aggregation 등은 이전 상태를 기억해야 하므로 상태가 필요
  • 상태 저장소는 디스크 기반, 인메모리 등으로 설정 가능
  • Interactive Query를 통해 외부에서 읽기 전용 접근 가능

 

처리 보장 (Processing Guarantees)

Kafka 0.11부터는 exactly-once 처리를 위해 Kafka와 Streams가 긴밀히 통합됨.

  • Kafka의 트랜잭션, idempotent producer 기능 활용
  • 입력 offset commit, 상태 저장, 출력 기록을 원자적으로 처리
  • processing.guarantee를 EXACTLY_ONCE_V2로 설정하면 적용 (broker 2.5 이상 필요)

 

Out-of-Order 데이터 처리

Out-of-order 데이터는 다음 경우 발생:

  1. 한 파티션에서 타임스탬프가 오름차순이 아님
  2. 여러 파티션에서 레코드를 처리할 때, 타임스탬프 기준으로 처리 순서를 강제하지 않음

처리 방법

  • Stateless 연산: 영향 없음
  • Stateful 연산 (join, aggregation): 영향 있음 → 윈도우 설정버퍼링으로 해결
  • Versioned State Store를 사용하면 Stream-Table join 시에도 out-of-order 처리를 강화할 수 있음

 

5. Architecture

Kafka Streams는 Kafka의 프로듀서/컨슈머 라이브러리를 기반으로

Kafka의 내재된 기능을 적극 활용하여 데이터 병렬 처리, 분산 조정, 장애 허용, 운영 단순성을 제공

 

스트림 파티션과 태스크 (Stream Partitions and Tasks)

  • Kafka는 데이터를 파티션 단위로 저장/전송하며, Kafka Streams는 처리 단위로 파티션을 사용한다.
  • Kafka Streams는 Kafka 토픽의 파티션 수를 기준으로 태스크(task)를 생성하며, 각 태스크는 고정된 파티션 목록을 담당한다.
  • 각 태스크는 자신만의 토폴로지를 실행하며, 배정된 파티션의 레코드를 하나씩 처리한다.
  • 따라서 애플리케이션을 여러 인스턴스로 실행하면 각 인스턴스가 태스크를 분담하여 병렬 처리하게 된다.

🟨 예: 입력 토픽에 5개 파티션이 있으면, 최대 5개의 인스턴스에서 병렬로 처리 가능

  • Kafka Streams는 리소스 관리자가 아니라 라이브러리이기 때문에, 인스턴스가 죽으면 다른 인스턴스로 자동 재할당된다.
  • Kafka Streams는 StreamsPartitionAssignor 클래스를 사용하여 파티션을 태스크에 고정적으로 배정한다.

 

스레드 모델 (Threading Model)

  • Kafka Streams는 애플리케이션 인스턴스 내에서 사용할 스레드 수를 설정할 수 있다 (num.stream.threads).
  • 각 스레드는 하나 이상의 태스크를 실행하며, 서로 상태를 공유하지 않는다.
  • 인스턴스를 추가하거나 스레드를 늘리는 것은 토폴로지를 복제하여 병렬 처리를 확장하는 것과 같다.

🟩 Kafka 2.8부터는 실행 중에도 스레드를 추가/제거 가능하며, Kafka Streams가 자동으로 파티션을 재분배함

 

로컬 상태 저장소 (Local State Stores)

  • 상태 기반 연산 (join, aggregation 등)을 수행하려면 **상태 저장소(state store)**가 필요하다.
  • Kafka Streams는 태스크별 로컬 상태 저장소를 생성하며, 해당 저장소는 애플리케이션 내 API로 접근 가능하다.
  • DSL 연산자(join(), aggregate(), window())를 사용할 경우 Kafka Streams가 자동으로 저장소를 관리한다.

 

장애 허용 (Fault Tolerance)

Kafka Streams는 Kafka의 내재적 장애 허용 기능을 활용하여 애플리케이션 실패 시에도 안정적으로 복구한다.

  • Kafka 파티션은 복제되므로, 데이터는 안전하게 저장되어 있고 필요 시 재처리 가능하다.
  • 태스크가 죽으면 다른 인스턴스에서 자동으로 재시작된다.

🟦 상태 저장소 복구

  • Kafka Streams는 각 상태 저장소에 대한 changelog 토픽을 Kafka에 만들어, 변경사항을 지속적으로 기록한다.
  • 이 토픽은 log compaction이 활성화되어 있어 불필요한 데이터는 정리된다.
  • 장애 발생 시, changelog 토픽을 재생하여 상태를 복구한 후 처리 재개한다.

📌 복구 속도 최적화:

  • standby replica(예비 복제본)을 설정하면 복구 시간이 단축된다.
  • Kafka 2.6부터는 동기화된 복제본이 있는 인스턴스에 우선 할당되도록 보장된다 (num.standby.replicas 설정).

 

랙 인식 태스크 할당 (Rack-Aware Assignment)

  • Kafka Streams는 랙(rack) 정보를 활용해 예비 복제본을 다른 랙에 분산 배치할 수 있다.
  • 설정 키 예:
    • client.rack: 클라이언트(컨슈머)의 랙 정보
    • broker.rack: 브로커의 랙 정보
    • rack.aware.assignment.tags: 태스크 할당 시 사용하는 랙 태그 키
    • rack.aware.assignment.strategy: 랙 기반 태스크 할당 전략