본문 바로가기

Lab/Mini-Broker6

[Mini-Broker] 06. 벤치마크 측정 Intro5편까지 mini-broker의 핵심 기능을 모두 구현했다. append-only log, sparse index, NIO TCP 서버, Producer/Consumer 클라이언트, 파티션, 컨슈머 그룹.이제 궁금한 건 하나다. 얼마나 빠를까?그냥 빠른지 느린지가 아니라, Apache Kafka와 비교했을 때 어느 정도인지. 그래서 JMH(Java Microbenchmark Harness)로 벤치마크를 작성했다.그런데 첫 번째 시도는 완전히 틀린 비교였다. 숫자는 나왔지만 의미가 없었다. 이 글은 그 실수와 수정, 그리고 최종 결과를 기록한다.첫 번째 시도 - 틀린 비교처음 작성한 벤치마크는 두 가지였다.AppendBenchmark — LogSegment.append() 직접 호출:@Benchm.. 2026. 5. 29.
[Mini-Broker] 05. 파티션과 컨슈머 그룹, Kafka가 병렬 처리를 하는 방법 Intro4편까지 만든 브로커는 토픽 하나 = 파일 하나 구조였다. Producer가 메시지를 보내면 하나의 파일에 순서대로 쌓이고, Consumer가 처음부터 읽어오는 단순한 구조.근데 이 구조에는 한계가 있다.파티션이 하나뿐이면 Producer가 아무리 많아도 결국 하나의 파일에 순차적으로 써야 한다. 병렬 처리가 불가능하다. Consumer도 마찬가지다. 여러 Consumer가 같은 토픽을 읽으면 중복이 생긴다. "어디까지 읽었는지"를 추적하는 장치가 없기 때문이다.5편에서는 두 가지를 구현한다.파티션 — 토픽을 여러 파일로 나눠서 병렬 처리 가능하게컨슈머 그룹 — 각 파티션을 누가 담당하고, 어디까지 읽었는지 관리파티션이란?하드디스크 파티션이랑 비슷한 개념이다. 하나의 디스크를 여러 구역으로 나누.. 2026. 5. 26.
[Mini-Broker] 04. Consumer 구현, sparse index 사용 Intro3편에서 Producer가 브로커에 메시지를 보내고, 브로커가 파일에 저장하는 흐름을 만들었다.근데 저장만 하고 꺼내오지 못하면 반쪽짜리다. 4편에서는 Consumer를 구현한다.Consumer가 브로커에 "orders 토픽의 offset 0부터 줘" 라고 요청하면, 브로커가 2편에서 만든 IndexReader.findPosition() 으로 빠르게 찾아서 응답하는 흐름이다. 2편의 sparse index가 여기서 진짜로 쓰인다.프로토콜 확장3편까지는 Producer 요청만 있었다. Consumer가 추가되면서 브로커가 두 종류의 요청을 구분해야 한다.request type 바이트 추가0x01 → PRODUCE (메시지 저장)0x02 → FETCH (메시지 조회)모든 요청 맨 앞에 1바이트 .. 2026. 5. 21.
[Mini-Broker] 03. NIO Selector로 TCP 서버 직접 만들기 Intro1편에서 append-only 로그를, 2편에서 sparse index를 만들었다. 파일 I/O는 완성됐다.근데 지금까지 만든 건 "혼자 쓰는 파일 시스템"에 가깝다. 실제 브로커라면 Producer가 네트워크로 메시지를 보내야 한다.3편에서는 두 가지를 구현한다.NIO Selector 기반 TCP 서버 (BrokerServer)브로커에 메시지를 보내는 클라이언트 (ProducerClient)그리고 실제로 메시지를 전송해서 orders.log 파일에 저장되는 전체 흐름을 동작시킨다.왜 NIO Selector??Java에서 TCP 서버를 만드는 방법은 크게 두 가지다.전통적인 방식 — ServerSocket + 스레드while (true) { Socket client = serverSocke.. 2026. 5. 15.
[Mini-Broker] 02. 100만 개 중 하나를 빠르게 찾는 법, sparse index Intro1편에서 append-only 로그 파일을 만들었다. 메시지를 바이트로 파일에 쓰고, 처음부터 읽어서 복원하는 것까지 됐다.근데 문제가 하나 있다.메시지가 100만 개 쌓인 파일에서 offset 999,999번 메시지 하나만 읽고 싶다면?지금 RecordReader.readAll() 은 파일 전체를 처음부터 읽는다. 100만 개를 전부 메모리에 올린 다음에 원하는 것 하나를 꺼내는 셈이다. 느리고, OOM 위험도 있다.2편에서는 이 문제를 해결하는 sparse index 를 구현한다.sparse index란?책 뒤에 색인이 있다. "사과 → 35페이지" 처럼. 책 전체를 안 읽고 바로 35페이지를 펼칠 수 있다.로그 파일도 마찬가지다. "offset 100 → 파일의 4,200번째 바이트" 를 .. 2026. 5. 13.
[Mini-Broker] 01. Kafka의 심장부를 직접 만들어보자 IntroStrimzi로 3-broker KRaft Kafka 클러스터를 운영하고 있다.Kafka를 사용하다가 어느 날 문득 이런 생각이 들었다. producer.send(record) 하면 메시지가 디스크에 어떻게 저장되는 걸까?consumer.poll() 은 어떻게 그 메시지를 정확히 찾아오는 걸까?설명할 수 없었다. 운영은 하고 있었지만, 내부는 블랙박스였다.그래서 직접 만들어보기로 했다. Java로, 표준 라이브러리만 써서, Kafka-like 단일 노드 브로커를.꼭 명심해야 할 점이 있다.이 프로젝트의 목표는 성능이 아니다. "왜 Kafka가 이렇게 설계됐는지"를 체감하는 것이다.그래서 Netty, Protobuf, Spring Boot 같은 라이브러리는 의도적으로 쓰지 않는다. 그것들이 대신.. 2026. 5. 10.