기간: 2025년 7월 28일 ~ 9월 26일 (9주)
목표: Kafka 핵심 개념을 담은 메시지 큐 MVP 구현 + 분산 시스템 설계 역량 습득
- 25년 7월 27일 설계 발표 자료
- 25년 9월 27일 프로젝트 발표 자료(TODO)
기존 시스템에서 서비스 간 직접 호출 방식은 다음과 같은 한계를 가진다:
- 결합도: 프로듀서와 컨슈머 간 강결합으로 서비스 변경 시 영향도가 큼
- 확장성: 대용량 처리 시 동시성 확보 어려움
- 신뢰성: 네트워크 이슈 발생 시 메시지 유실 가능
- 복원력: 장애 시 전체 흐름 중단 (예: 동기 호출 실패 전파)
| 항목 | 목표 수치 | 테스트 조건 |
|---|---|---|
| 처리량 (Throughput) | ≥ 5,000 msg/s (1KB 메시지 기준) | 단일 브로커 + 2파티션 (HTTP/JSON 한계 고려) |
| 지연시간 (Latency) | 평균 ≤ 100ms, P95 ≤ 200ms | E2E 측정 (Producer→Consumer) |
| 내구성 | 승인된 메시지 100% 복구 보장 | WAL 기반 완전 복구 테스트 |
| 장애 복구 시간 | ≤ 30초 (수동 복구 포함) | 브로커 강제 종료 후 복구 시나리오 |
| 순서 보장 | 파티션 내 100% 순서 보장 | 메시지 순서 테스트 |
- 토픽/파티션 구조: hash(key) % partitions 라우팅
- 메시지 배칭 및 전송: 네트워크 효율성 향상 (100개 또는 10ms 단위)
- 오프셋 기반 소비 및 재처리: 장애 시 특정 지점부터 재시작
- 메시지 영속화 (WAL): 브로커 재시작 후에도 데이터 유지
- Consumer Group 지원: 메시지 병렬 처리 및 로드 밸런싱
- Long Polling 기반 Pull: 실시간성과 효율성 균형
- 리더-팔로워 복제: 데이터 이중화 (단순 구현)
- Heartbeat & 파티션 할당 유지: Consumer 생존 확인
- 고성능 처리: 5K msg/s 이상 (HTTP/JSON 제약 고려)
- 평균 지연시간: 100ms 이하
- 장애 복구: 시나리오 기반 복구 매뉴얼
- 테스트 기반 개발: TDD 적용
- 확장 가능 아키텍처: Phase 2에서 gRPC/TCP 마이그레이션
- 로그 수집: 애플리케이션 로그 → 중앙 저장소 전달
- 이벤트 처리: 주문 생성 이벤트 → 재고/결제 서비스 알림
- 데이터 파이프라인: 실시간 데이터 → 분석 시스템 전달
flowchart TD
Producer["Producer(CLI or SDK)"] --> Broker1["Broker-1(Leader)"]
Broker1 --> WAL1[(WAL File)]
Broker1 --> Broker2["Broker-2(Follower)"]
Broker2 --> WAL2[(WAL File)]
Consumer1["Consumer-1(Group-A)"] --> Broker1
Consumer2["Consumer-2(Group-B)"] --> Broker1
Admin[Admin CLI] --> Broker1
| 컴포넌트 | 역할 | 설명 |
|---|---|---|
| Producer | 메시지 전송, 파티셔닝 | key 기반 라우팅 (hash(key) % N) |
| Broker | 메시지 수신/저장/서빙 | WAL 기반 영속성, Consumer 요청 응답 |
| Consumer | 메시지 Pull, 오프셋 관리 | Long Polling, 컨슈머 그룹 지원 |
| Coordinator (MVP 단순화) | Consumer Group 및 할당 상태 유지 | 인메모리 구현, Heartbeat로 리밸런싱 감지 |
| WAL | 메시지 복구용 로그 | offset, checksum 포함 |
| Replication | 리더→팔로워 비동기 복제 | 리더 기준 최신 메시지 전달 |
| 항목 | MVP 설계 기준 |
|---|---|
| Group Metadata | 브로커 인메모리로 관리 (etcd 등 외부 도구 없음) |
| Consumer Registration | group_id, consumer_id로 등록 |
| Heartbeat API | 주기적 호출 (예: 5초) 없으면 리밸런싱 |
| Partition Assignment | Round-Robin 방식 단순 구현 |
| Failover 대응 | heartbeat 누락 시 → 파티션 재할당 |
// 메시지 구조
type Message struct {
Topic string `json:"topic"`
Partition int32 `json:"partition"`
Offset int64 `json:"offset"`
Key string `json:"key,omitempty"`
Value string `json:"value"` // MVP에서는 string으로 단순화
Timestamp int64 `json:"timestamp"`
Size int32 `json:"size"` // 메시지 크기 (KB 제한용)
}
// WAL 엔트리
type WALEntry struct {
Offset int64 `json:"offset"`
Message Message `json:"message"`
Checksum uint32 `json:"checksum"` // CRC32 체크섬
}
// 파티션 구조
type Partition struct {
ID int32 `json:"id"`
Messages []Message `json:"-"` // 인메모리
OffsetIndex map[int64]int `json:"-"` // 오프셋 → 인덱스
LastOffset int64 `json:"last_offset"`
WALPath string `json:"wal_path"`
MaxSize int64 `json:"max_size"` // 파티션 최대 크기 (MB)
mutex sync.RWMutex `json:"-"` // 동시성 제어
}| 항목 | gafka | Kafka |
|---|---|---|
| 메시지 경로 | Producer → Broker (Leader) → WAL → Consumer | 유사 |
| 복제 방식 | 리더 → 팔로워 비동기 | ISR 기반 복제 |
| Offset 관리 | In-memory + 일부 API 지원 | Zookeeper or Kafka Internal |
| 통신 방식 | HTTP + JSON | TCP + Binary Protocol |
| 메시지 전송 방식 | Batching 지원 | Batching + Compression + Zero-Copy |
| 장애 복구 | WAL 기반 수동 복구 | 자동 리더 선출 + ISR 복제 |
- HTTP 기반 통신: MVP 개발 속도를 위한 의도된 선택이지만, 성능 한계 존재
- Coordinator 단순화: 외부 시스템 (e.g., Zookeeper) 제거 → 구조 단순화
- WAL 설계: 단일 파일 기반 WAL로 최소 영속성 구현, 향후 세그먼트 관리 필요
| 항목 | 기술 | 이유 |
|---|---|---|
| 언어 | Go 1.22 | 빠른 동시성 처리, 간단한 배포 |
| 통신 | HTTP + JSON | 개발 편의성 우선 (성능 제약 인지) |
| 저장 | In-memory + WAL 파일 | 영속성과 성능 간 균형 |
| CLI | Cobra | 커맨드 라인 도구 개발 |
| 테스트 | go test |
TDD 기반 개발 |
| 성능 측정 | wrk, custom tool | Throughput, P95 측정 |
| 로깅 | logrus | 단계별 로깅 처리 |
| 제약 요소 | 영향 | 완화 방안 |
|---|---|---|
| HTTP/JSON 오버헤드 | JSON 파싱으로 인한 처리량 제한 (5K msg/s) | 배칭으로 완화, Phase 2에서 gRPC 마이그레이션 |
| 인메모리 저장소 | 파티션당 1M 메시지 제한 | WAL 기반 재시작 시 복구, 향후 세그먼트 분할 |
| 제한 항목 | 최대값 | 이유 |
|---|---|---|
| 메시지 크기 | 1MB | HTTP 요청 크기 제한, 메모리 사용량 관리 |
| 토픽당 파티션 수 | 100개 | Consumer Group 관리 복잡도 제한 |
| 파티션당 메시지 수 | 1M개 | 인메모리 저장 한계 |
| Consumer Group 수 | 제한 없음 | 각 그룹은 독립적으로 관리 |
| Topic명 길이 | 255자 | 파일시스템 호환성 |
| Consumer ID 길이 | 128자 | HTTP URL 길이 제한 |
# 1. 프로젝트 클론 및 빌드
git clone <repo> && cd gafka
make build
# 2. 브로커 시작 (리더)
./bin/broker --id=1 --port=9001 --data-dir=/tmp/gafka-1 --role=leader
# 3. 브로커 시작 (팔로워)
./bin/broker --id=2 --port=9002 --data-dir=/tmp/gafka-2 --role=follower --leader=localhost:9001
# 4. 토픽 생성
./bin/admin create-topic --name=test-topic --partitions=2 --broker=localhost:9001
# 5. 메시지 전송
./bin/producer --topic=test-topic --broker=localhost:9001 --message="Hello World" --key="user123"
# 6. 메시지 소비 (Long Polling)
./bin/consumer --topic=test-topic --group=test-group --broker=localhost:9001 --timeout=30s# 메시지 전송
curl -X POST http://localhost:9001/api/v1/produce \
-H "Content-Type: application/json" \
-d '{"topic":"test-topic","key":"user123","value":"Hello World"}'
# 메시지 소비 (Consumer Group 기반 - 브로커가 offset 관리)
curl "http://localhost:9001/api/v1/consume?topic=test-topic&count=10&consumer_id=consumer-1&group_id=test-group&timeout=30s"
# 메시지 소비 (개별 Consumer - 직접 offset 지정)
curl "http://localhost:9001/api/v1/consume?topic=test-topic&partition=0&offset=100&count=10&timeout=30s"
# Consumer Group 상태 확인
curl "http://localhost:9001/api/v1/consumer-groups/test-group/status"
# Consumer Heartbeat
curl -X POST "http://localhost:9001/api/v1/consumer-groups/test-group/members/consumer-1/heartbeat"| 테스트 항목 | 도구 | 목표 | 검증 방법 |
|---|---|---|---|
| 처리량 측정 | wrk + Lua | ≥5K msg/s | 1KB 메시지로 30초 부하 |
| 지연시간 측정 | Go client | P95 ≤ 200ms | produce → consume 측정 |
| 장애 복구 | 수동 | ≤30초 | 브로커 kill 후 복원 |
| 배치 효과 | wrk | TPS 비교 | 배치 vs 단건 전송 비교 |
-- scripts/produce.lua (wrk용)
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
-- 1KB 메시지로 테스트
local message_1kb = string.rep("a", 1024)
function request()
local body = string.format('{"topic":"test-topic","key":"test-%d","value":"%s"}',
math.random(1000), message_1kb)
return wrk.format(wrk.method, wrk.path, wrk.headers, body)
end
function response(status, headers, body)
if status ~= 200 and status ~= 201 then
print("Error: " .. status .. " " .. body)
end
end
function done(summary, latency, requests)
print("Throughput: " .. string.format("%.2f", summary.requests / (summary.duration / 1000000)) .. " requests/sec")
print("Average latency: " .. latency.mean / 1000 .. "ms")
endRed → Green → Refactor 사이클 반복
1. 실패하는 테스트 작성
2. 테스트를 통과하는 최소 코드 구현
3. 코드 개선 (리팩토링)
| 주차 | TDD 테스트 대상 | 예시 테스트 케이스 |
|---|---|---|
| 1-2주차 | 메시지 구조체, 파티션 로직 | TestMessage_Serialize(), TestPartition_AddMessage() |
| 3-4주차 | 프로듀서/컨슈머 API | TestProducer_SendMessage(), TestConsumer_PullMessages() |
| 5-6주차 | WAL 영속화, Consumer Group | TestWAL_WriteAndRecover(), TestConsumerGroup_Rebalancing() |
| 7-8주차 | 통합 테스트, 성능 테스트 | TestBroker_HandleHighLoad(), TestFailover_Recovery() |
| 9주차 | E2E 테스트, 문서화 | TestFullWorkflow_ProducerToBrokerToConsumer() |
| 질문 | 대응 |
|---|---|
| 왜 Kafka 클론을 Go로 만드나요? | 동시성, 학습 효율, 가독성에서 Go가 적합 |
| 외부 Zookeeper/etcd 왜 안 썼나요? | MVP에선 오히려 복잡도 증가. 향후 확장 계획 있음 |
| 메시지 순서는 어떻게 보장되나요? | 파티션 내 offset 단위 순차 처리, key 기반 파티셔닝 |
| 브로커 장애 시 복구는 어떻게 되나요? | WAL 파일 기반 자동 복구 + 팔로워→리더 수동 승격. 리더 선출 자동화는 Phase 2 |
| Pull 방식의 실시간성은 어떻게 보장되나요? | Long Polling 사용, 최대 30초 이내 응답 보장 |
| WAL 손상 시 복구는 어떻게 되나요? | CRC32 기반 손상 검증, 복구 실패 시 fail-safe 처리 필요 |
| 메시지 중복 방지는? | at-least-once만 보장. 중복 방지는 Consumer에서 처리 필요 |
| Consumer Group에서 동시성 문제는 어떻게 해결하나요? | Heartbeat + 파티션 할당으로 한 파티션은 한 Consumer만 소비. 409 Conflict로 중복 방지 |
| HTTP + JSON 성능 한계는 어떻게 해결하나요? | MVP에서는 개발 편의성 우선. 5K msg/s 목표로 현실적 설정. Phase 2에서 gRPC로 마이그레이션 |
| Zero-copy 최적화는 HTTP에서 가능한가요? | HTTP 스택에서는 불가능. Phase 2에서 TCP 기반 커스텀 프로토콜로 전환해야 구현 가능 |
✅ 충분히 발전 가능한 설계
- 모든 핵심 컴포넌트가 모듈화되어 있음 (
internal/broker,internal/consumer등) - 테스트 기반 개발 프로세스 탑재 (TDD + wrk)
- 디버깅과 확장 모두 고려한 디렉토리 구조
하지만 아래와 같은 요소는 시스템 확장의 실질적 장애물로 작용 가능
- 통신 계층 한계
- HTTP + JSON → 5K msg/s 제한
- TCP 기반 커스텀 프로토콜 필요 (Zero-copy 등)
- Coordinator 지속성 없음
- 현재는 인메모리 기반 (Consumer Group, Offset 등 모두 휘발성)
- 복제/장애 복구 자동화 부족
- 팔로워의 리더 전환 수동
- ISR, 리더 선출 알고리즘 부재
- 세그먼트 및 인덱싱 기능 부재
- WAL은 하나의 긴 로그로만 존재 → 복구/압축/GC 어렵다
| 주차 | 핵심 구현 사항 | TDD 목표 | 검증 기준 |
|---|---|---|---|
| 1주차 (7/28-8/3) |
프로젝트 셋업, 메시지 구조체 | Message, Partition 단위 테스트 | 테스트 커버리지 > 80% |
| 2주차 (8/4-8/10) |
파티션 로직, WAL 기본 구현 | WAL 읽기/쓰기 테스트 | 파일 영속화 동작 확인 |
| 3주차 (8/11-8/17) |
HTTP API, 프로듀서 구현 | HTTP 핸들러 테스트 | 1K msg/s 처리 확인 |
| 4주차 (8/18-8/24) |
컨슈머, 오프셋 관리 | 컨슈머 그룹 테스트 | E2E 메시지 플로우 동작 |
| 5주차 (8/25-8/31) |
배칭, 성능 최적화 | 배칭 로직 테스트 | 5K msg/s 목표 달성 |
| 6주차 (9/1-9/7) |
Consumer Group, 파티션 할당 | 리밸런싱 테스트 | Consumer 추가/삭제 시 자동 재할당 |
| 7주차 (9/8-9/14) |
복제 기능, 장애 복구 | 복제 동기화 테스트 | 데이터 일관성 확인, 30초 내 복구 |
| 8주차 (9/15-9/21) |
성능 테스트, 버그 수정 | 부하 테스트 자동화 | 모든 성능 목표 달성 |
| 9주차 (9/22-9/26) |
문서화, 데모 준비 | 통합 테스트 완료 | README, API 문서 완성 |
gafka/
├── cmd/ # 애플리케이션 진입점
│ ├── broker/
│ │ └── main.go # 브로커 서버 실행
│ ├── producer/
│ │ └── main.go # 프로듀서 CLI
│ ├── consumer/
│ │ └── main.go # 컨슈머 CLI
│ └── admin/
│ └── main.go # 관리 도구 CLI
├── internal/ # 내부 패키지 (외부 노출 불가)
│ ├── broker/ # 브로커 코어 로직
│ │ ├── broker.go
│ │ ├── broker_test.go
│ │ ├── partition.go
│ │ ├── partition_test.go
│ │ ├── wal.go
│ │ ├── wal_test.go
│ │ ├── replication.go
│ │ └── replication_test.go
│ ├── producer/ # 프로듀서 로직
│ │ ├── producer.go
│ │ ├── producer_test.go
│ │ ├── batcher.go
│ │ ├── batcher_test.go
│ │ └── partitioner.go
│ ├── consumer/ # 컨슈머 로직
│ │ ├── consumer.go
│ │ ├── consumer_test.go
│ │ ├── group.go
│ │ ├── group_test.go
│ │ └── offset.go
│ └── api/ # HTTP API 핸들러
│ ├── handlers.go
│ ├── handlers_test.go
│ ├── middleware.go
│ └── routes.go
├── pkg/ # 외부 패키지 (재사용 가능)
│ ├── protocol/ # 메시지 프로토콜
│ │ ├── message.go
│ │ └── message_test.go
│ └── client/ # Go 클라이언트 SDK
│ ├── client.go
│ └── client_test.go
├── configs/ # 설정 파일
│ ├── broker.yaml
│ └── examples/
├── scripts/ # 스크립트 및 도구
│ ├── produce.lua # wrk 테스트 스크립트
│ ├── setup.sh # 환경 설정
│ └── benchmark.sh # 성능 테스트
├── docs/ # 문서
│ ├── api.md # API 문서
│ ├── architecture.md # 아키텍처 가이드
│ └── operations.md # 운영 가이드
├── examples/ # 사용 예시
│ ├── simple_producer/
│ └── simple_consumer/
├── Makefile # 빌드 스크립트
├── go.mod # Go 모듈 정의
├── go.sum # 의존성 체크섬
├── README.md # 프로젝트 개요
└── .gitignore # Git 무시 파일
- 기본 프로듀서/컨슈머 구현
- 단일 브로커 WAL 저장소
- HTTP API 기반 통신
- HTTP → gRPC or TCP 커스텀 프로토콜
- WAL 세그먼트 구조 및 인덱싱 추가
- 메시지 압축 (gzip/snappy)
- Raft 기반 리더 선출
- Offset 및 Metadata 영속화 (BoltDB/Badger 등 사용 가능)
- 브로커 클러스터링 + ZooKeeper or etcd 연동
- 메시지 처리량: 단일 브로커 5K msg/s 이상 (1KB 메시지, HTTP/JSON 기준)
- 메시지 순서 보장: 파티션 내 100% 순서 유지
- 데이터 영속성: 승인된 메시지 WAL 기반 100% 복구 보장
- 기본 복제: 리더-팔로워 간 비동기 복제 정상 동작
- Consumer Group: 파티션별 단일 Consumer 보장
- 테스트 커버리지: 80% 이상 유지
- TDD 준수: 모든 기능을 테스트 먼저 작성
- 코드 품질: Go lint 규칙 준수
- API 문서: OpenAPI 스펙 완성
- 메시지 큐 핵심 개념: 토픽, 파티션, 오프셋 이해
- 분산 시스템 기초: 복제, 일관성, 장애 복구 경험
- 고성능 I/O: WAL, 배칭 기법 구현
- TDD 역량: 테스트 주도 개발 완성