[Live Event Stream #2] 요구사항 정의

2026. 8. 15. 09:39ㆍPROJECT/Toy Project

[Live Event Stream #2] 요구사항 정의

1. 시작하며

지난 글에서는 Live Event Stream 프로젝트를 시작한 이유와 학습 목표를 정리했다.

이번에는 실제 구현에 들어가기 전에 Live Event Stream이 어떤 이벤트를 받고, 어떤 방식으로 처리해야 하는지 요구사항을 정의해보려고 한다.

이번 프로젝트의 목적은 Kafka를 이용한 완성도 높은 데이터 플랫폼을 만드는 것이 아니다.

핵심은 다음과 같은 문제를 직접 경험하는 것이다.

  • Producer가 Consumer보다 빠르면 어떤 일이 발생하는가?
  • Partition과 Consumer 수는 처리량에 어떤 영향을 주는가?
  • Consumer 장애 시 이벤트는 어떻게 다시 처리되는가?
  • 같은 이벤트가 여러 번 전달되면 어떻게 해야 하는가?
  • 처리에 계속 실패하는 이벤트는 어떻게 분리해야 하는가?
  • 이벤트 Schema가 변경되면 기존 Consumer는 어떻게 대응해야 하는가?

따라서 초기 기능은 최대한 단순하게 구성하고, 이후 대량 이벤트와 장애 상황을 발생시키면서 필요한 기능을 하나씩 추가한다.


2. 서비스 범위

Live Event Stream은 OTT 서비스에서 발생하는 사용자 행동 이벤트를 받아 Kafka에 전달하고 Consumer가 이를 처리하는 이벤트 파이프라인이다.

기본적인 흐름은 다음과 같다.

사용자 행동
    ↓
Event API
    ↓
Producer
    ↓
Kafka
    ↓
Consumer
    ↓
Event 저장 / 처리

Event API는 요청을 받은 시점에 모든 후속 처리를 수행하지 않는다.

이벤트를 Kafka에 전달하는 것까지만 담당하고, 실제 저장이나 후속 처리는 Consumer가 비동기로 수행한다.

예를 들어 사용자가 콘텐츠 재생을 시작하면:

PLAY_START 발생
      ↓
Event API
      ↓
Kafka
      ↓
Consumer
      ↓
Event 저장

과 같은 흐름으로 처리한다.

실제 추천 시스템, 실시간 통계 시스템, 데이터 웨어하우스까지 구현하지는 않는다.

이번 프로젝트에서는 Kafka 기반 Event Pipeline 자체의 처리량, 안정성, 장애 복구와 데이터 정합성에 집중한다.


3. 주요 구성 요소

Live Event Stream은 우선 다음 구성 요소로 시작한다.

3.1 Event API

외부에서 사용자 행동 이벤트를 전달받는 API다.

예:

POST /api/v1/events

요청을 받으면 Event 형식을 검증한 뒤 Kafka Producer에게 전달한다.


3.2 Producer

Event API에서 전달받은 이벤트를 Kafka Topic에 발행한다.

Producer는 이벤트의 Key와 Payload를 Kafka에 전달한다.

초기에는 복잡한 Producer 최적화를 적용하지 않는다.


3.3 Kafka

Producer와 Consumer 사이에서 이벤트를 보관하고 전달한다.

초기에는 하나의 주요 Event Topic을 사용한다.

예:

user-events

Topic 내부에는 여러 Partition이 존재할 수 있다.


3.4 Consumer

Kafka에서 Event를 읽어 실제 처리를 수행한다.

초기 Consumer의 역할은 단순하게 이벤트를 DB에 저장하는 것으로 제한한다.

Kafka
  ↓
Consumer
  ↓
Event DB 저장

이후 실험 단계에서 Consumer 수, 처리 속도, Retry 등의 구조를 변경한다.


3.5 Event Store

Consumer가 정상적으로 처리한 이벤트를 저장한다.

초기에는 관계형 Database를 사용하여 처리 결과를 확인할 수 있도록 한다.

Event Store의 목적은 실제 분석 시스템을 만드는 것이 아니라 다음 내용을 검증하기 위한 것이다.

  • Event가 실제로 처리됐는가?
  • 중복 Event가 저장됐는가?
  • 처리 순서가 어떻게 되었는가?
  • Consumer 재시작 후 Event가 다시 처리됐는가?

4. Event 모델

모든 Event는 공통된 기본 구조를 가진다.

예:

{
  "eventId": "8f9e4c36-4b47-4aa8-a906-123456789abc",
  "eventType": "PLAY_START",
  "memberId": 100,
  "contentId": 200,
  "occurredAt": "2026-08-15T09:30:00+09:00",
  "schemaVersion": 1,
  "payload": {
    "deviceType": "MOBILE"
  }
}

주요 필드는 다음과 같다.

필드역할

eventId Event를 고유하게 식별
eventType Event 종류
memberId Event를 발생시킨 사용자
contentId 관련 콘텐츠
occurredAt 실제 Event 발생 시각
schemaVersion Event Schema 버전
payload Event별 추가 정보

eventId는 이후 중복 처리와 Idempotency 실험에서 핵심 값으로 사용한다.


5. Event 종류

초기에는 다음 Event를 사용한다.

CONTENT_VIEW

PLAY_START

PLAY_PAUSE

PLAY_END

SEARCH

CLICK

LIKE

COMMENT

모든 Event에 복잡한 비즈니스 로직을 구현할 필요는 없다.

Kafka Pipeline을 실험하는 것이 목적이므로 Event 종류를 구분할 수 있을 정도의 정보만 관리한다.


6. 기능 요구사항

FR-01. Event 수신

시스템은 사용자 행동 Event를 HTTP API로 받을 수 있어야 한다.

POST /api/v1/events

예:

{
  "eventId": "event-001",
  "eventType": "PLAY_START",
  "memberId": 100,
  "contentId": 200,
  "occurredAt": "2026-08-15T09:30:00+09:00",
  "schemaVersion": 1,
  "payload": {
    "deviceType": "MOBILE"
  }
}

FR-02. Event 기본 검증

Kafka에 전달하기 전에 최소한의 Event 형식을 검증해야 한다.

다음 값은 반드시 존재해야 한다.

eventId
eventType
memberId
occurredAt
schemaVersion

Event 종류에 따라 contentId 등 일부 값은 선택적으로 사용할 수 있다.

잘못된 Event는 Kafka에 발행하지 않는다.


FR-03. Kafka Event 발행

정상적인 Event를 Kafka Topic에 발행할 수 있어야 한다.

초기 Topic은 다음과 같이 정의한다.

user-events

Event API는 Kafka 전송이 정상적으로 접수되면 요청 처리를 종료한다.

Consumer의 DB 저장 완료까지 기다리지 않는다.

HTTP Request
    ↓
Event Validation
    ↓
Kafka Publish
    ↓
Response

        Kafka 이후 처리는 비동기

FR-04. Event Key 지정

Kafka에 Event를 발행할 때 Partition을 결정하기 위한 Key를 지정할 수 있어야 한다.

초기에는 memberId를 Key로 사용한다.

Key = memberId

이를 통해 같은 회원의 Event가 동일한 Partition에 전달되는 구조로 시작한다.

예:

memberId = 100

PLAY_START
PLAY_PAUSE
PLAY_END
     ↓
동일 Partition

향후 Hot Partition 등의 문제가 발생하면 Key 전략을 다시 검토한다.


FR-05. Consumer Event 수신

Consumer는 user-events Topic을 구독하고 Event를 읽을 수 있어야 한다.

Kafka
  ↓
Consumer

초기에는 하나의 Consumer Group을 사용한다.

예:

event-storage-consumer

FR-06. Event 저장

Consumer가 정상적으로 처리한 Event는 Event Store에 저장한다.

최소한 다음 값을 저장한다.

eventId
eventType
memberId
contentId
occurredAt
processedAt
schemaVersion
payload

이를 통해 Kafka에서 받은 Event와 실제 처리 결과를 비교할 수 있어야 한다.


FR-07. Offset 관리

Consumer는 처리 완료된 Event의 Offset을 관리해야 한다.

Event 처리가 성공하지 않았는데 Offset이 먼저 Commit되어 Event가 사라지는 구조는 피한다.

초기에는 At-least-once 전달을 전제로 구현한다.

즉 Event가 유실되는 것보다는 동일 Event가 다시 전달될 가능성을 허용한다.

Event 처리
    ↓
정상 완료
    ↓
Offset Commit

구체적인 Ack 방식은 설계 단계에서 결정한다.


FR-08. 중복 Event 처리

동일한 eventId를 가진 Event가 여러 번 전달될 수 있음을 전제로 한다.

예:

eventId = event-001
       ↓
Consumer 처리
       ↓
저장

Consumer 장애 / 재처리

eventId = event-001
       ↓
다시 Consumer

최종적으로 같은 Event가 여러 번 전달되더라도 처리 결과가 중복되지 않아야 한다.

다만 초기 구현에서는 이를 미리 해결하지 않는다.

먼저 중복 처리 상황을 재현한 뒤 Idempotency 전략을 적용한다.


FR-09. 처리 실패 Event 재시도

Consumer의 Event 처리 중 일시적인 오류가 발생한다면 재시도할 수 있어야 한다.

예:

Event
 ↓
Consumer
 ↓
DB 연결 오류
 ↓
Retry

Retry 횟수와 간격은 실험을 통해 결정한다.


FR-10. DLQ 처리

정해진 횟수만큼 Retry해도 계속 실패하는 Event는 정상 Topic의 처리를 계속 방해하지 않도록 별도의 DLQ로 이동할 수 있어야 한다.

예:

user-events
    ↓
Consumer
    ↓
실패
    ↓
Retry
    ↓
계속 실패
    ↓
user-events-dlq

DLQ에 전달된 Event는 이후 어떤 Event가 왜 실패했는지 확인할 수 있어야 한다.


FR-11. Consumer 재시작 후 처리 재개

Consumer가 종료됐다가 다시 실행되더라도 마지막으로 정상 처리한 위치 이후부터 Event 처리를 이어갈 수 있어야 한다.

Offset 100
Offset 101
Offset 102 ← 처리 완료

Consumer 종료

↓

Consumer 재시작

↓

Offset 103부터 처리

단, Commit 시점에 따라 이미 처리한 Event가 다시 전달될 수 있으며 이는 Idempotency를 통해 대응한다.


FR-12. Event Replay

필요한 경우 특정 Event 구간을 다시 처리할 수 있어야 한다.

예:

Offset 1000
   ↓
Offset 5000

Replay

Replay는 실제 운영 UI까지 만드는 것이 아니라 Kafka Offset 조작 또는 별도 Consumer를 이용해 과거 Event를 다시 읽을 수 있음을 확인하는 수준으로 구현한다.


FR-13. Event Schema Version 관리

Event에는 schemaVersion을 포함해야 한다.

예:

{
  "schemaVersion": 1
}

Event 구조가 변경되더라도 Consumer가 지원 가능한 Version인지 판단할 수 있어야 한다.

초기에는 복잡한 Schema Registry를 반드시 도입하지 않는다.

먼저 JSON 기반 Version 관리로 시작하고, 필요성이 확인되면 Schema 관리 방식을 확장한다.


FR-14. Consumer Group 확장

동일한 Consumer Group에서 여러 Consumer 인스턴스를 실행할 수 있어야 한다.

Topic
 ├─ Partition 0 → Consumer A
 ├─ Partition 1 → Consumer B
 └─ Partition 2 → Consumer C

Consumer 수를 변경하면서 Partition이 어떻게 재분배되는지 확인할 수 있어야 한다.


7. 초기 Event 처리 흐름

기본 구현에서는 다음과 같이 최대한 단순한 구조로 시작한다.

POST /api/v1/events
        ↓
Event 검증
        ↓
memberId를 Kafka Key로 지정
        ↓
user-events Topic 발행
        ↓
HTTP 응답


Kafka
  ↓
Consumer
  ↓
Event 역직렬화
  ↓
DB INSERT
  ↓
Offset Commit

초기에는 다음 기능을 넣지 않는다.

Retry Topic
DLQ
Idempotency
Redis Deduplication
Batch Consumer
복잡한 Partition 전략
Schema Registry
Replay API

먼저 정상적으로 Event가 흘러가는 Baseline Pipeline을 만든다.


8. 비기능 요구사항

이번 프로젝트에서는 기능 요구사항보다 Event Pipeline의 처리 특성을 확인하는 것이 더 중요하다.

NFR-01. Event 유실 방지

정상적으로 Kafka에 저장된 Event는 Consumer 장애가 발생하더라도 임의로 유실되어서는 안 된다.

Consumer가 처리하지 못한 Event는 다시 처리 가능해야 한다.


NFR-02. 중복에 대한 안전성

At-least-once 구조에서는 Event가 중복 전달될 수 있다.

최종 시스템에서는 동일한 eventId의 Event가 여러 번 전달되더라도 비즈니스 결과가 중복되지 않아야 한다.


NFR-03. Event 순서

모든 Event의 전역 순서를 보장할 필요는 없다.

다만 같은 memberId의 Event는 가능한 한 발생 순서대로 처리할 수 있는 구조를 사용한다.

PLAY_START
    ↓
PLAY_PAUSE
    ↓
PLAY_END

이를 위해 초기에는 memberId를 Kafka Key로 사용한다.

Partition 수나 Key 전략을 변경할 때 Ordering에 어떤 영향이 생기는지도 확인한다.


NFR-04. 처리량 측정

Producer와 Consumer가 각각 어느 정도의 Event를 처리할 수 있는지 측정할 수 있어야 한다.

주요 지표는 다음과 같다.

  • Producer Events/sec
  • Consumer Events/sec
  • 전체 처리 Event 수
  • 처리 실패 수

프로젝트 초기에는 목표 처리량을 임의로 정하지 않는다.

먼저 Baseline을 측정한 뒤 변경 전후의 값을 비교한다.


NFR-05. Consumer Lag 관측

Consumer가 Producer를 따라가지 못하는 상황을 확인할 수 있어야 한다.

최소한 다음 정보를 확인한다.

Latest Offset

Consumer Offset

Consumer Lag

부하가 증가하면서 Lag이 커지는지, 부하가 줄어든 뒤 다시 회복되는지를 측정한다.


NFR-06. Event 처리 지연 측정

Event가 발생한 시점부터 Consumer 처리가 완료될 때까지 걸린 시간을 측정할 수 있어야 한다.

occurredAt
     ↓
Kafka
     ↓
Consumer
     ↓
processedAt

Event Processing Latency

평균값만 보는 것이 아니라 필요하다면 p50 / p95 / p99도 확인한다.


NFR-07. Consumer 확장성

Consumer 처리량이 부족한 경우 Consumer 수를 증가시켜 병렬 처리할 수 있어야 한다.

다만 Consumer 수를 늘리는 것만으로 처리량이 증가한다고 가정하지 않는다.

Partition 수와 Consumer 수에 따른 처리량 변화를 실제로 측정한다.


NFR-08. Backpressure 대응

Producer의 Event 발생 속도가 Consumer의 처리 속도를 초과하더라도 시스템 상태를 관찰할 수 있어야 한다.

Producer > Consumer

↓

Consumer Lag 증가

Lag이 증가했을 때 다음 대응 후보를 비교할 수 있어야 한다.

  • Consumer 처리 속도 개선
  • Consumer 수 증가
  • Partition 증가
  • Batch 처리

실제 측정 결과를 근거로 변경한다.


NFR-09. 장애 복구 가능성

Consumer가 비정상 종료되더라도 재시작 후 Event 처리를 계속할 수 있어야 한다.

또한 장애 상황에서 다음 내용을 확인할 수 있어야 한다.

  • 어느 Offset까지 처리됐는가?
  • Event가 중복 처리됐는가?
  • Event가 유실됐는가?
  • Consumer Lag은 어떻게 변했는가?

NFR-10. 실패 Event 격리

하나의 잘못된 Event가 전체 Consumer Pipeline을 계속 막아서는 안 된다.

반복 실패하는 Event는 Retry 후 DLQ 등으로 격리할 수 있어야 한다.


NFR-11. 관측 가능성

Pipeline 내부에서 문제가 발생했을 때 단순히 "Kafka가 느리다"고 판단하지 않고 병목 위치를 확인할 수 있어야 한다.

최소한 다음 지표를 확인한다.

  • Event API 요청량
  • Producer 처리량
  • Producer Error
  • Kafka Topic / Partition 상태
  • Consumer 처리량
  • Consumer Lag
  • Consumer 처리시간
  • Retry 수
  • DLQ Event 수
  • DB 처리시간
  • JVM / CPU / Memory

필요한 모니터링 도구는 이후 설계 단계에서 정한다.


NFR-12. 동일 조건에서 비교

구조 변경 전후의 실험은 가능한 한 동일한 조건에서 수행한다.

예:

동일한 Hardware

동일한 Event 개수

동일한 Payload

동일한 Producer 속도

동일한 DB 데이터

동일한 테스트 시간

이를 통해 Partition이나 Consumer 수를 변경한 것이 실제로 어떤 효과를 냈는지 확인한다.


9. 이번 프로젝트에서 제외할 기능

Kafka Pipeline 학습과 직접적인 관련이 없는 기능은 구현하지 않는다.

다음 기능은 제외한다.

  • 회원가입 및 로그인 UI
  • 실제 영상 스트리밍
  • DRM
  • CDN
  • 실제 추천 알고리즘
  • 검색 엔진
  • 실시간 인기 콘텐츠 서비스
  • 데이터 웨어하우스 구축
  • Spark / Flink 기반 대규모 Stream Processing
  • 실제 광고 시스템
  • 관리자 UI
  • 모바일 / 웹 OTT 화면

또한 처음부터 다음 기술을 모두 넣지 않는다.

  • Redis
  • Schema Registry
  • Kafka Streams
  • Kubernetes
  • Elasticsearch
  • ClickHouse

필요성이 실제로 생겼을 때만 후보로 검토한다.


10. 초기 성공 조건

기본 Event Pipeline 단계에서는 다음 조건을 만족하면 Baseline 구현이 완료된 것으로 판단한다.

  • Event API가 Event를 받을 수 있다.
  • 잘못된 Event 형식을 거절할 수 있다.
  • 정상 Event를 Kafka에 발행할 수 있다.
  • memberId를 Kafka Key로 사용할 수 있다.
  • Consumer가 Event를 읽을 수 있다.
  • Consumer가 Event를 DB에 저장할 수 있다.
  • 저장된 Event의 eventId와 Payload를 확인할 수 있다.
  • Consumer 재시작 후 Event 처리가 계속된다.
  • 기본 Integration Test가 통과한다.

여기까지는 정상적인 Producer → Kafka → Consumer 흐름을 만드는 것이 목적이다.

아직 다음 문제는 해결하지 않는다.

중복 Event
Consumer Lag
Retry
DLQ
Hot Partition
Schema Evolution
Replay

이 문제들은 이후 단계에서 직접 재현한 뒤 해결한다.


11. 이후 실험에서 확인할 문제

기본 Pipeline이 완성되면 대량 Event를 발생시키면서 다음 문제를 순차적으로 확인한다.

Baseline Pipeline
        ↓
대량 Event 발생
        ↓
Producer / Consumer 처리량 측정
        ↓
Consumer Lag 재현
        ↓
Partition / Consumer 확장 실험
        ↓
중복 Event 재현
        ↓
Idempotency 적용
        ↓
Consumer 실패 재현
        ↓
Retry / DLQ
        ↓
Consumer 재시작
        ↓
Replay
        ↓
Schema 변경 실험

중요한 것은 각 기술을 계획대로 무조건 적용하는 것이 아니다.

예를 들어 Partition을 늘렸는데 처리량이 증가하지 않는다면, DB나 Consumer 처리 로직이 병목일 수 있다.

따라서 항상:

측정
 ↓
문제 발견
 ↓
원인 분석
 ↓
대안 비교
 ↓
변경
 ↓
재측정

순서로 진행한다.


12. 정리

Live Event Stream의 기본 기능은 생각보다 단순하다.

Event 발생
   ↓
API 수신
   ↓
Kafka 발행
   ↓
Consumer 처리
   ↓
저장

하지만 실제 학습은 이 Pipeline에 많은 Event를 넣고 일부러 장애를 발생시키는 시점부터 시작된다.

앞으로는 다음 질문에 답해볼 예정이다.

Producer가 Consumer보다 빠르면 Lag은 어떻게 증가할까?

Consumer를 늘리면 실제 처리량도 그만큼 증가할까?

Partition을 늘리면 언제 효과가 있고 언제 효과가 없을까?

같은 Event가 두 번 전달되면 DB에는 어떻게 저장될까?

Consumer가 Event를 처리한 직후 죽으면 무슨 일이 생길까?

계속 실패하는 Event 하나가 전체 처리를 막을 수 있을까?

Consumer를 다시 시작하면 어디서부터 Event를 읽게 될까?

다음 글에서는 이번에 정의한 요구사항을 기반으로 Live Event Stream의 초기 구조와 Event 모델, Kafka Topic 및 Consumer 구조를 설계해보려고 한다.

반응형