Log Segment와 파일 구조

Kafka는 메시지를 디스크에 효율적으로 저장하기 위해 Log Segment 구조를 사용합니다.

디렉토리 구조

기본 레이아웃

/kafka-logs/                          # log.dirs 설정
├── my-topic-0/                       # {topic}-{partition}
│   ├── 00000000000000000000.log      # 데이터 파일
│   ├── 00000000000000000000.index    # 오프셋 인덱스
│   ├── 00000000000000000000.timeindex # 타임스탬프 인덱스
│   ├── 00000000000005242880.log      # 다음 세그먼트
│   ├── 00000000000005242880.index
│   ├── 00000000000005242880.timeindex
│   ├── leader-epoch-checkpoint
│   └── partition.metadata
├── my-topic-1/
│   └── ...
└── __consumer_offsets-0/             # 내부 토픽
    └── ...

파일명 규칙

파일명은 해당 세그먼트의 시작 오프셋을 20자리로 표현합니다.

00000000000000000000.log  → 오프셋 0부터 시작
00000000000005242880.log  → 오프셋 5,242,880부터 시작
00000000000010485760.log  → 오프셋 10,485,760부터 시작

Log Segment 구조

세그먼트 구성 요소

┌─────────────────────────────────────────────────────────────────┐
│                        Log Segment                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────┐  ┌──────────────────┐  ┌───────────────┐ │
│  │   .log 파일       │  │   .index 파일    │  │ .timeindex    │ │
│  │   (데이터)        │  │   (오프셋 인덱스) │  │ (시간 인덱스)  │ │
│  └──────────────────┘  └──────────────────┘  └───────────────┘ │
│                                                                 │
│  ┌──────────────────┐  ┌──────────────────┐                    │
│  │ .txnindex 파일    │  │ .snapshot 파일   │                    │
│  │ (트랜잭션 인덱스)  │  │ (Producer 상태)  │                    │
│  └──────────────────┘  └──────────────────┘                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Active Segment vs Inactive Segment

Partition Log:
┌────────────┬────────────┬────────────┬────────────┐
│ Segment 0  │ Segment 1  │ Segment 2  │ Segment 3  │
│ (Inactive) │ (Inactive) │ (Inactive) │ (Active)   │
│ 읽기 전용   │ 읽기 전용   │ 읽기 전용   │ 쓰기 가능   │
└────────────┴────────────┴────────────┴────────────┘
                                              ↑
                                        새 메시지 추가

.log 파일 (데이터 파일)

Record Batch 구조

┌─────────────────────────────────────────────────────────────────┐
│                        .log 파일                                 │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐                │
│ │Record Batch │ │Record Batch │ │Record Batch │ ...            │
│ │  (배치 1)    │ │  (배치 2)    │ │  (배치 3)    │                │
│ └─────────────┘ └─────────────┘ └─────────────┘                │
└─────────────────────────────────────────────────────────────────┘

Record Batch 상세

Record Batch:
┌─────────────────────────────────────────────────────────────────┐
│ Base Offset (8 bytes)         - 배치의 첫 번째 오프셋            │
│ Batch Length (4 bytes)        - 배치 크기                       │
│ Partition Leader Epoch (4 bytes)                                │
│ Magic (1 byte)                - 메시지 포맷 버전                 │
│ CRC (4 bytes)                 - 체크섬                          │
│ Attributes (2 bytes)          - 압축, 타임스탬프 타입 등          │
│ Last Offset Delta (4 bytes)   - 배치 내 마지막 오프셋 델타        │
│ First Timestamp (8 bytes)                                       │
│ Max Timestamp (8 bytes)                                         │
│ Producer ID (8 bytes)                                           │
│ Producer Epoch (2 bytes)                                        │
│ Base Sequence (4 bytes)                                         │
│ Records Count (4 bytes)                                         │
├─────────────────────────────────────────────────────────────────┤
│ Records (가변 길이)                                              │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐                            │
│ │ Record  │ │ Record  │ │ Record  │ ...                        │
│ └─────────┘ └─────────┘ └─────────┘                            │
└─────────────────────────────────────────────────────────────────┘

개별 Record 구조

Record:
┌─────────────────────────────────────────────────────────────────┐
│ Length (varint)              - 레코드 크기                       │
│ Attributes (1 byte)                                             │
│ Timestamp Delta (varint)     - Base Timestamp 대비 차이          │
│ Offset Delta (varint)        - Base Offset 대비 차이             │
│ Key Length (varint)                                             │
│ Key (가변)                                                       │
│ Value Length (varint)                                           │
│ Value (가변)                                                     │
│ Headers Count (varint)                                          │
│ Headers (가변)                                                   │
└─────────────────────────────────────────────────────────────────┘

.index 파일 (오프셋 인덱스)

구조

.index 파일:
┌──────────────────────────────────┐
│ Relative Offset │ Position      │
│ (4 bytes)       │ (4 bytes)     │
├──────────────────────────────────┤
│      0          │      0        │ → 오프셋 0 → 파일 위치 0
│     100         │   15240       │ → 오프셋 100 → 파일 위치 15240
│     200         │   30480       │ → 오프셋 200 → 파일 위치 30480
│     ...         │   ...         │
└──────────────────────────────────┘

Sparse Index

모든 메시지가 아닌 일정 간격으로 인덱스를 생성합니다.

// 인덱스 간격 설정 (기본 4KB)
log.index.interval.bytes = 4096

메시지 검색 과정

오프셋 150 검색:
1. .index에서 이진 검색 → (100, 15240) 찾음
2. .log 파일의 15240 위치로 이동
3. 순차적으로 읽으며 오프셋 150 찾기

┌─────────────────────────────────────────┐
│ Index: 0→0, 100→15240, 200→30480       │
│                 ↓                       │
│ 오프셋 150 요청 → 100에서 시작하여 순차 검색│
└─────────────────────────────────────────┘

.timeindex 파일 (시간 인덱스)

구조

.timeindex 파일:
┌──────────────────────────────────────────┐
│ Timestamp        │ Relative Offset       │
│ (8 bytes)        │ (4 bytes)             │
├──────────────────────────────────────────┤
│ 1699000000000    │      0                │
│ 1699000060000    │    500                │
│ 1699000120000    │   1000                │
│     ...          │   ...                 │
└──────────────────────────────────────────┘

시간 기반 검색

// 특정 시간 이후의 메시지 검색
Map<TopicPartition, Long> timestamps = new HashMap<>();
timestamps.put(partition, targetTimestamp);
 
Map<TopicPartition, OffsetAndTimestamp> offsets =
    consumer.offsetsForTimes(timestamps);

Segment 관리

새 Segment 생성 조건

// 크기 기반 (기본 1GB)
log.segment.bytes = 1073741824
 
// 시간 기반 (기본 7일)
log.roll.ms = 604800000
// 또는
log.roll.hours = 168

Segment 롤링 과정

1. Active Segment가 조건 충족
   └── segment.bytes 초과 OR roll.ms 경과

2. 현재 Segment를 Inactive로 전환

3. 새 Segment 생성 (다음 오프셋으로 시작)

4. 새 Segment가 Active가 됨

관련 설정

설정기본값설명
log.segment.bytes1GB세그먼트 최대 크기
log.roll.ms7일세그먼트 롤링 주기
log.index.size.max.bytes10MB인덱스 파일 최대 크기
log.index.interval.bytes4KB인덱스 간격

파일 핸들링

Memory-Mapped Files

┌─────────────────────────────────────────────────────────────────┐
│                    Memory-Mapped I/O                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Application Memory        OS Page Cache        Disk            │
│  ┌───────────────┐       ┌───────────────┐   ┌───────────┐     │
│  │ MappedBuffer  │ ←───→ │  .index 파일   │ ←→│   Disk    │     │
│  │ (가상 메모리)  │       │  .timeindex   │   │           │     │
│  └───────────────┘       └───────────────┘   └───────────┘     │
│                                                                 │
│  인덱스 파일은 mmap으로 매핑되어 빠른 접근 가능                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

파일 디스크립터 관리

// 열린 파일 핸들러 수 제한
// 시스템 ulimit 설정 필요
ulimit -n 100000

데이터 확인 도구

kafka-dump-log.sh

# 로그 파일 덤프
kafka-dump-log.sh \
    --files /kafka-logs/my-topic-0/00000000000000000000.log \
    --print-data-log
 
# 인덱스 파일 덤프
kafka-dump-log.sh \
    --files /kafka-logs/my-topic-0/00000000000000000000.index \
    --print-data-log
 
# 특정 오프셋 범위만
kafka-dump-log.sh \
    --files /kafka-logs/my-topic-0/00000000000000000000.log \
    --offset-position 1000:2000

출력 예시

Dumping /kafka-logs/my-topic-0/00000000000000000000.log
Starting offset: 0
baseOffset: 0 lastOffset: 4 count: 5 baseSequence: -1 lastSequence: -1
producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 0
isTransactional: false isControl: false position: 0
CreateTime: 1699000000000 size: 150 magic: 2
compresscodec: NONE crc: 1234567890
| offset: 0 CreateTime: 1699000000000 keysize: 3 valuesize: 5 key: key value: value
| offset: 1 CreateTime: 1699000001000 keysize: 3 valuesize: 5 key: key value: value
...

관련 문서