Learning Platform
Глоссарий Troubleshooting
Урок 05.01 · 25 мин
Продвинутый
Log Segments.log.index.timeindexActive Segmentlog.segment.bytes

Лог-сегменты

Каждый раз, когда producer отправляет сообщение в Kafka, оно не просто «кладётся в очередь» — оно записывается на диск брокера в строго определённую физическую структуру. Понимание этой структуры критически важно для правильного конфигурирования Kafka, диагностики проблем с производительностью и объяснения гарантий, которые даёт платформа.


Партиция как директория на диске

Каждая партиция топика — это директория на файловой системе брокера. Если у вас топик orders с тремя партициями, на диске брокера вы увидите три директории:

/kafka-logs/
  orders-0/
  orders-1/
  orders-2/

Если топик реплицирован, каждый брокер, хранящий реплику партиции, будет иметь соответствующую директорию у себя. Брокер-лидер и фолловеры хранят одинаковую структуру файлов.

Внутри директории партиции находятся лог-сегменты — группы файлов, которые хранят данные определённого диапазона офсетов.


Три типа файлов сегмента

Каждый сегмент состоит из трёх файлов с одинаковым базовым именем (base offset) и разными расширениями:

Анатомия лог-сегмента
Лог-сегменты: .log / .index / .timeindex
Сегмент 1 (base=0)Закрытый сегмент 1: сегмент заполнен и запечатан. Базовое смещение (base offset) = 0 — первая запись в этом сегменте. Имена файлов: 00000000000000000000.log, .index, .timeindex. Сегмент закрывается когда его размер превышает log.segment.bytes (1 ГБ по умолчанию) или истекает log.roll.ms.
.log файлoffsets 0–999.log файл сегмента 1: содержит сами записи в бинарном формате. Каждая запись включает: offset (8 байт), timestamp (8 байт), key length, key bytes, value length, value bytes, headers. Записи хранятся последовательно. Формат: RecordBatch (батч записей) с magic byte=2 (Kafka 0.11+).
.index файлsparse index.index файл: разреженный индекс смещений. Отображает относительный offset записи на физическую позицию в .log файле. Не содержит запись для каждого offset — только каждые log.index.interval.bytes байт (4096 по умолчанию). Позволяет бинарным поиском найти приблизительную позицию в .log файле.
.timeindex файлtimestamp→offset.timeindex файл: индекс по времени. Отображает timestamp на смещение записи. Используется при поиске по времени (--reset-offsets --to-datetime). Разреженный: одна запись на каждые log.index.interval.bytes байт .log файла. Тип timestamp определяется message.timestamp.type: CreateTime (по умолчанию) или LogAppendTime.
Сегмент 2 (base=1000)Закрытый сегмент 2: следующий полный сегмент. Базовое смещение = 1000 (первая запись этого сегмента). Файлы: 00000000000000001000.log, .index, .timeindex. Закрытые сегменты доступны только для чтения. Могут быть сжаты (log compaction) или удалены по log.retention.bytes или log.retention.ms.
.log файлoffsets 1000–1999.log файл сегмента 2: содержит записи с offset 1000 по 1999. Log compaction (если включён) удалит старые версии записей с одинаковым ключом, оставив только последнюю. cleanup.policy=compact,delete — комбинирует compaction и удаление по времени.
.index файлsparse index.index файл сегмента 2: разреженный индекс для диапазона offset 1000-1999. Записи хранят relative_offset (4 байта) и position в .log файле (4 байта). При поиске offset N: находим ближайший индексный entry <= N, затем сканируем .log файл с найденной позиции.
.timeindex файлtimestamp→offset.timeindex файл сегмента 2: временной индекс для этого диапазона. Используется командой kafka-consumer-groups.sh --reset-offsets --to-datetime '2024-01-01T00:00:00'. Запись: timestamp (8 байт) + relative_offset (4 байта). Всегда возрастающий по времени внутри сегмента.
АКТИВНЫЙ сегмент (base=2000)Активный сегмент (active segment): единственный сегмент открытый для записи в каждой партиции. Новые записи добавляются только в конец активного сегмента (append-only). Базовое смещение = 2000. Имя файла: 00000000000000002000.log. Активный .index и .timeindex файлы тоже открыты для записи.
.log файлoffsets 2000–....log файл активного сегмента: открыт для записи. Новые RecordBatch добавляются в конец. Физически данные могут быть в OS page cache и ещё не сброшены на диск (контролируется flush.messages и flush.ms). Kafka полагается на репликацию для надёжности, не только на fsync.
.index файлрастёт.index файл активного сегмента: индекс пополняется по мере добавления записей. Каждые log.index.interval.bytes байт в .log файле добавляется новая запись в индекс. Максимальный размер индексного файла ограничен log.index.size.max.bytes (10 МБ по умолчанию).
.timeindex файлрастёт.timeindex файл активного сегмента: пополняется синхронно с .index файлом. Только при LogAppendTime временная метка гарантированно монотонно возрастает. При CreateTime временная метка устанавливается продюсером и может нарушать порядок.
HW = offset 2087High Watermark (HW): смещение 2087. До этого offset все ISR-реплики подтвердили получение данных. Консьюмеры читают записи только до HW — это гарантирует, что прочитанные данные не будут потеряны при отказе лидера. HW хранится в памяти лидера и передаётся фолловерам в FetchResponse.
LEO = offset 2095Log End Offset (LEO): смещение 2095 — следующий offset для записи. LEO лидера всегда >= HW. Разница LEO - HW называется «uncommitted records» — записи, которые лидер получил, но ISR ещё не подтвердила. При acks=all продюсер ждёт пока HW достигнет offset его записи.
Retention: удаление закрытых сегментовПолитика удаления сегментов (log.retention): закрытые сегменты удаляются по одному из критериев: log.retention.ms (7 дней по умолчанию), log.retention.bytes (нет лимита по умолчанию). Сегмент удаляется целиком когда его последняя запись старше retention период. LogCleaner (для компактных топиков) работает иначе — удаляет отдельные устаревшие ключи.
Compaction: cleanup.policy=compactLog compaction (cleanup.policy=compact): альтернатива удалению по времени. LogCleaner периодически сканирует закрытые сегменты и для каждого ключа оставляет только самую последнюю запись (или запись-томстоун с null value). Используется для changelog топиков (Kafka Streams, consumer offsets). Активный сегмент никогда не компактируется.

.log — данные сообщений

Файл .log содержит сами сообщения в формате записей (records). Записи добавляются последовательно в конец файла (append-only). Каждая запись содержит:

  • Размер batch (4 байта)
  • CRC32 контрольную сумму
  • Magic byte (версия формата)
  • Атрибуты (compression codec, timestamp type)
  • Timestamp
  • Offset delta (смещение относительно base offset batch)
  • Key length + key bytes
  • Value length + value bytes
  • Headers

Записи в .log файле читаются последовательно — это намеренное архитектурное решение. Последовательные чтения с диска работают в десятки раз быстрее случайных, что позволяет Kafka обслуживать миллионы сообщений в секунду на обычных жёстких дисках.

.index — индекс смещений

Файл .index содержит разреженное (sparse) отображение: offset → позиция в байтах в .log файле.

offset=0    → byte_position=0
offset=100  → byte_position=4321
offset=200  → byte_position=8750
...
NOTE

Индекс разреженный специально. Kafka не записывает позицию каждого офсета — только каждого N-го. Параметр log.index.interval.bytes (по умолчанию 4096) контролирует, через сколько байт данных добавляется новая запись в индекс. Это баланс между размером индекса и скоростью поиска.

Когда consumer запрашивает данные начиная с offset X, Kafka выполняет бинарный поиск по .index файлу, находит ближайший меньший индексированный offset, а затем читает .log файл последовательно с найденной позиции.

.timeindex — индекс по времени

Файл .timeindex содержит отображение: timestamp → offset. Используется при запросе данных по времени (операция seekToBeginningByTime):

timestamp=1712000000000 → offset=0
timestamp=1712003600000 → offset=5000
timestamp=1712007200000 → offset=12000

Это позволяет Kafka найти ответ на вопрос: «Где в логе находится первое сообщение, чей timestamp не меньше заданного значения?» Применяется при операции --reset-offsets --to-datetime в инструментах командной строки.


Именование сегментов

Каждый сегмент называется по базовому офсету первой записи, которую он содержит:

orders-0/
  00000000000000000000.log       ← сегмент с offset 0..N
  00000000000000000000.index
  00000000000000000000.timeindex
  00000000000000005000.log       ← сегмент с offset 5000..M
  00000000000000005000.index
  00000000000000005000.timeindex
  00000000000000012000.log       ← активный сегмент (пишется прямо сейчас)
  00000000000000012000.index
  00000000000000012000.timeindex

Имена файлов — это 20-значные числа с ведущими нулями, представляющие base offset сегмента.


Активный сегмент

В каждой партиции всегда есть ровно один активный сегмент (active segment) — тот, в который в данный момент записываются новые сообщения. Его .log файл открыт для записи. Все остальные сегменты закрытые (passive/closed) — они доступны только для чтения.

Активный и закрытые сегменты
Сегмент 0 (закрытый)Закрытый сегмент: offset 0-4999. Файл .log закрыт на запись. Доступен для чтения consumers. Будет удалён согласно политике retention.
Сегмент 5000 (закрытый)Закрытый сегмент: offset 5000-11999. Хранит 7000 записей. Flush на диск выполнен полностью.
Сегмент 12000 (активный)Активный сегмент: offset 12000+. Файл .log открыт для записи. Producer-ы пишут в этот сегмент прямо сейчас. .index и .timeindex обновляются по мере накопления записей.
время / офсет

Роллинг сегмента

Kafka закрывает активный сегмент и создаёт новый (roll) при выполнении одного из условий:

ПараметрЗначение по умолчаниюОписание
log.segment.bytes1 ГБ (1073741824)Максимальный размер .log файла
log.roll.ms168 часов (7 дней)Максимальное время жизни активного сегмента
log.roll.hours168Альтернативный способ задать log.roll.ms
log.index.size.max.bytes10 МБМаксимальный размер .index файла

Когда .log файл достигает log.segment.bytes, активный сегмент закрывается и создаётся новый. Новый сегмент именуется текущим LEO (Log End Offset) партиции.

TIP

Уменьшение log.segment.bytes увеличивает количество файлов и может замедлить поиск по времени (больше сегментов для проверки). Увеличение замедляет инициализацию брокера. Значение 512 МБ — разумный компромисс для топиков с высоким throughput.


Retention: удаление старых сегментов

Kafka не удаляет отдельные записи из сегмента — она удаляет целые сегменты. Удаление применяется только к закрытым сегментам. Активный сегмент никогда не удаляется.

Политики retention:

ПараметрОписание
log.retention.hoursУдалять сегменты старше N часов (по умолчанию 168 = 7 дней)
log.retention.bytesУдалять сегменты, если суммарный размер превышает N байт
log.retention.msАльтернатива log.retention.hours (миллисекунды)

Когда применяется log.retention.bytes, Kafka начинает удалять самые старые сегменты, пока суммарный размер данных партиции не опустится ниже лимита.

WARNING

Гранулярность удаления — это целый сегмент, а не отдельные сообщения. Если сегмент содержит сообщение, которому только что исполнилось retention.hours, но он содержит также более свежие сообщения — сегмент не удаляется, пока все его сообщения не устареют. Поэтому фактическое время хранения может быть до retention + segment.bytes/throughput дольше указанного.


leader-epoch-checkpoint

В директории партиции также хранится файл leader-epoch-checkpoint. Он записывает историю смены эпох лидера:

2
0 0
1 5000

Каждая строка — это (epoch, startOffset). Этот файл используется при восстановлении реплики: фолловер сравнивает свою историю эпох с лидером и определяет, нужно ли выполнить truncation для восстановления консистентности.


Проверка знанийKnowledge check
Почему .index файл в Kafka разреженный (sparse), а не содержит позицию каждого офсета?
ОтветAnswer
Хранение позиции каждого офсета потребовало бы памяти O(N), где N — число сообщений. Для миллиардов сообщений это сотни гигабайт только для индексов. Разреженный индекс: (1) хранится компактно — каждые 4096 байт данных одна запись; (2) полностью помещается в RAM брокера; (3) бинарный поиск находит ближайший индексированный offset за O(log N), после чего линейное чтение .log файла добирает нужный offset. Потеря в скорости поиска минимальна, выигрыш в памяти — многократный.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Партиция топика Kafka содержит 3 ГБ данных при log.segment.bytes=1073741824 (1 ГБ). Сколько файлов .log будет на диске (учитывая, что активный сегмент ещё не заполнен)?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5