Где Kafka хранит offset’ы консьюмеров и какие существуют способы управления ими?

Ответ

Offset (смещение) — это указатель на последнее прочитанное сообщение в партиции топика. Его хранение и управление критически важны для гарантий доставки сообщений. Существует два основных подхода:

1. В брокере Kafka (автоматическое управление)

Это стандартный и наиболее распространенный способ. Kafka хранит offset'ы для каждой consumer group в специальном внутреннем топике с именем __consumer_offsets.

  • Как это работает: Консьюмер периодически (или после обработки сообщения) отправляет коммит (commit) своего offset'а брокеру. Брокер сохраняет эту информацию. При перезапуске или перебалансировке группы консьюмер запрашивает у брокера последний сохраненный offset и продолжает чтение с этого места.
  • Преимущества: Простота, надежность. Kafka сама заботится о хранении и отказоустойчивости.

2. На стороне консьюмера (ручное управление)

В этом случае консьюмер сам отвечает за хранение offset'ов. Для этого в настройках консьюмера необходимо отключить автоматический коммит (EnableAutoCommit=false).

  • Где хранить: В любом внешнем хранилище, поддерживающем транзакции, например, в реляционной базе данных (PostgreSQL, MySQL) или NoSQL-хранилище (Redis).
  • Зачем это нужно: Для реализации семантики обработки "exactly-once". Это позволяет атомарно выполнить две операции в одной транзакции: обработать сообщение и сохранить новый offset. Если одна из операций завершится неудачно, вся транзакция откатывается, и сообщение будет обработано повторно.

Пример на Go с библиотекой sarama (демонстрация ручного коммита):

// В настройках консьюмера нужно указать:
// config.Consumer.Offsets.AutoCommit.Enable = false

// В цикле обработки сообщений:
session, _ := sarama.NewConsumerGroup(..., ..., config)

// ... в методе ConsumeClaim ...
for msg := range claim.Messages() {
    // 1. Обрабатываем сообщение
    err := processMessage(msg)

    // 2. Если обработка успешна, коммитим offset вручную
    if err == nil {
        session.MarkMessage(msg, "") // Помечаем сообщение для коммита
    }
    // Коммит будет выполнен либо по таймеру, либо при следующем вызове
}

Ответ 18+ 🔞

Слушай, а вот этот твой офсет, он вообще что такое, а? Это ж типа закладка в книжке, только для Кафки. Показывает, где ты остановился читать, чтобы не начинать с начала каждый раз, как дурак. И хранить эту хуйню можно по-разному, вот в чём прикол.

Первый способ — классика, на брокере.
Это как оставить закладку прямо в библиотеке. Кафка сама для каждой твоей читательской компании (consumer group) ведёт секретную тетрадку под названием __consumer_offsets. Ты прочитал пару страниц — пишешь туда: «Я, Вася, остановился на странице 228». Перезапустился — подошёл, посмотрел в тетрадку, и снова с 228-й. Всё просто, надёжно, за тебя всё думают. Красота, блядь.

Второй способ — ручной, на своей стороне.
А вот это уже для параноиков и перфекционистов, которым мало «хотя бы раз» (at-least-once), а подавай «ровно один раз» (exactly-once). Ты говоришь Кафке: «Отъебись, сам всё сохраню». Выключаешь авто-коммит (EnableAutoCommit = false) и начинаешь головную боль.

Где хранить? Да где угодно! В своей PostgreSQL, в Redis, на хуй, на флешке в сейфе. Суть в чём: ты обрабатываешь сообщение и сохраняешь офсет в одной транзакции с бизнес-логикой. Получилось — ок, не получилось — откатил всё, включая офсет, и сообщение прилетит снова. Хитро, да? Зато гарантии — железные.

Вот, смотри, как это на Go с sarama выглядит, если делать вручную:

// Сказали Кафке — не лезь, сам всё сделаю.
// config.Consumer.Offsets.AutoCommit.Enable = false

session, _ := sarama.NewConsumerGroup(..., ..., config)

// ... внутри ConsumeClaim ...
for msg := range claim.Messages() {
    // 1. Обрабатываешь сообщение (например, пишешь в БД)
    err := processMessage(msg)

    // 2. Если не обосрался — помечаешь, что офсет надо закоммитить
    if err == nil {
        session.MarkMessage(msg, "") // Типа «запомни, эту страницу я прочёл»
    }
    // Потом, при следующем вызове или по таймеру, коммит улетит в Кафку
}

Вот и вся магия. Первый способ — для 99% случаев, работает из коробки. Второй — когда тебе реально надо спать спокойно и точно знать, что ни одно сообщение не потерялось и не обработалось дважды. Но и возни, блядь, овердохуища. Выбирай, что тебе ближе: простота или контроль до последней запятой.