Last Stable Offset, откатанные записи и почему транзакции без read_committed бессмысленны.
Транзакции в Kafka позволяют записать пачку сообщений (возможно, в разные партиции и топики) так, чтобы они стали видимы читателям одновременно — либо все, либо ни одного. Продюсер открывает транзакцию, пишет записи и завершает её commit или abort.
Ключевая деталь: транзакции работают только в паре. Записи откатанной транзакции физически остаются в логе — Kafka не умеет удалять уже записанное. Их отфильтровывает читатель, и только если он настроен на isolation.level=read_committed. При значении по умолчанию read_uncommitted консьюмер увидит и обработает откатанные записи. Это классическая ошибка: транзакции включили, изоляцию — нет.
В режиме read_committed появляется вторая граница чтения — Last Stable Offset (LSO). Это оффсет, ниже которого нет незавершённых транзакций. Такой консьюмер читает до LSO, а не до High Watermark, и пропускает записи откатанных транзакций.
Отсюда важное следствие для проектирования: открытая транзакция задерживает чтение всей партиции. Долгая транзакция в Kafka — это не «медленно потом», а «не видно сейчас». Транзакции стоит держать короткими, а transaction.timeout.ms — осмысленным.
И честная оговорка про exactly-once: транзакции дают его для схемы «читаем из Kafka, обрабатываем, пишем в Kafka», где коммит оффсетов входит в ту же транзакцию. Как только в цепочке появляется внешняя база или HTTP-вызов, гарантия заканчивается и нужен идемпотентный обработчик.
Разобрать это в симуляторе Интерактивный сценарий: настраиваешь кластер, ломаешь его и смотришь, что происходитНакладные расходы заметны при коротких транзакциях: каждая добавляет служебные записи и координацию. Пачки разумного размера обычно приемлемы.
Всё подряд до High Watermark, включая записи откатанных транзакций.
Нет. Для внешних систем нужен идемпотентный обработчик или паттерн outbox.