Недавно мы проводили вебинар «Обкафкился по полной. Фейлы с Apache Kafka». На нём спикер Всеволод Севостьянов, Engineering Manager в HelloFresh, поделился фейлами из личной практики, а ещё рассказал, как мастерски ходить по тонкому льду Kafka и прокачать свой бэкенд. Для тех, кто пропустил или предпочитает читать, а не смотреть, подготовили текстовый вариант.

Фейл №0: Kafka сложнее, чем кажется

Apache Kafka подаётся как суперпростой инструмент, из-за чего многие специалисты не до конца понимают, как она работает. Например, есть заблуждение, что Kafka очень легко настраивается, поэтому с ней не должно возникать проблем. На самом деле у неё под капотом множество computer science. 

Есть топики — что-то вроде контейнеров, в которых хранятся сообщения, объединённые темой или форматом. Чтобы масштабироваться внутри Kafka, топики делятся на партиции — единицы хранения сообщений. И часто возникает проблема, связанная с тем, что сообщения дробятся рандомным образом: какие-то попадают в Partition1, а какие-то — в Partition2, какие-то — в Partition3 и т.д. В результате сервисы, привязанные к Kafka, начинают читать данные из топиков в неправильном порядке.

Для Kafka партиционирование — это нормально. Но я часто видел команды, которые усердно старались обойти топики, делали всего одну партицию и настраивали всё не так. Хотя здесь можно пойти простым путем: сделать Partition Key, который позволит хранить сообщения, зависящие друг от друга, в том порядке, в котором вы хотите их передать.

Фейл №1: создавали 150 топиков, а нужно было заменить Kafka на Rabbit

Я работаю в компании, которая занимается Meal Kit — доставкой продуктов для приготовления блюд по рецепту. И у нас есть тулза для создания рецептов. В ней заводят рецепты, записывают данные в Kafka и затем их куда-то отправляют. Кто потребляет эти данные, тулза не должна знать.

Проблема: Kafka слушают планировщик, составляющий меню на неделю, Data Warehouse, отвечающий за обработку данных, Predictor, оценивающий, сколько продуктов у нас уйдет на этой неделе и сколько нужно заказывать на следующую, и ещё куча сервисов. Всем сервисам нужны разные данные. Например, планировщику меню — только готовые рецепты, а Predictor — рецепты, которые заказали минимум 1 раз.

Изначально мы попытались решить проблему, сделав много разных топиков на каждого потребителя. И это закономерно привело к тому, что Kafka «встала колом». Kafka не умеет по содержимому топика понимать, куда и какому потребителю его надо доставить. Всё работает по принципу подписной модели, когда вы говорите: «Я хочу получать газету каждый день». И каждый день вам приходит газета со всеми новостями, а не исключительно с новостями, которые вам интересны.

Итак, на каждый топик у нас был ровно один потребитель. И логика, которая должна выполняться на стороне потребителя (он сам фильтрует, что ему интересно), выполнялась на стороне Recipe Dev. Это был полный обкафкинг, потому что у нас получилась не разделённая система, а система, которая просто знает о существовании всех своих компонентов и обменивается сообщениями между ними, используя Kafka как шину данных или механизм сохранения. Это довольно дорого и неэффективно.

Решение: мы заменили Kafka на RabbitMQ, который может по ключу определить, в какой канал отправить конкретное сообщение и кто его должен получить. Также по этому ключу потребители могут подписываться на обновления. 

У нас было давление со стороны инфраструктуры — система реплицировалась много раз, у неё был мультирегиональный деплой и около 30-40 инстансов. На таком масштабе RabbitMQ вышёл дешевле, чем Kafka. 

Фейл №2: Leader not available

Проблема: есть consumer, который потребляет данные. Вы радостно отправляете данные в Kafka, но потом замечаете, что consumer ничего не потребляет. Kafka работает, consumer работает, но ничего не происходит. 

Одна из самых частых причин, почему так происходит, — перебалансировка consumer. У Kafka существует механизм, который мы уже рассматривали выше: топики делятся на партиции. Предположим, вы добавили первый consumer. У вас появляется понятие «лидер группы». В данном случае он слушает и последовательно добавляет в consumer все партиции конкретного топика:

Если добавится второй consumer для этого топика, то к нему привяжутся P3 и P4:

Этот процесс занимает какое-то время, так как второй consumer должен сообщить лидеру группы, что он доступен. Затем лидер группы обрабатывает эту информацию и возвращается с ответом, какие партиции должен потреблять consumer. И смысл в том, что если ваш софт написан неправильно, или у вас, например, Java под капотом, или Spring Boot, который долго стартует, такая перебалансировка будет происходить регулярно.

Что было у нас: подключался первый consumer, начинал слушать топик. Затем стартовали второй и третий consumer, посылали сигналы о перебалансировке и подключались. Но в какой-то момент третий consumer говорил, что отваливается. Причём он только говорил так — реально он никуда не отваливался и продолжал работать. 

Это казалось мистикой, пока мы не открыли для себя удивительный мир параметров max.poll.interval и Session timeout. 

Дело в том, что Kafka — система пассивного чтения, сама она никому ничего не пересылает. И чтобы обеспечить равномерность распределения сообщений партиций по consumer, ей нужно знать, сколько consumer всего. Для этого выбирается Consumer Leader, который слушает heartbeat — сообщения consumer о том, что он существует и готов принимать данные. Heartbeat отправляются всеми consumer в Kafka, при этом промежуток между двумя heartbeat не должен превышать max.poll.interval.

Это довольно очевидно, что мы не должны превышать тайм-аут сессии. Но абсолютно не очевидно, что consumer считается мертвым не только, когда превысил этот интервал, но и когда он взял сообщение, начал его обрабатывать и не смог обработать за max.poll.interval. 

Нужно уметь оперировать значениями max.poll.interval и Session timeout. Они по умолчанию настроены на не очень большой интервал — около 2 секунд для тайм-аут сессии и 500 миллисекунд для max.poll.interval. Если у вас timeout-сессии 5 или 10 секунд, Kafka скажет: «Не успел — отключайся». То же самое и с обработкой сообщений: если не успел обработать сообщение в отведённый интервалом, возникает проблема.

Решение: мы увеличили max.poll.interval до двух часов и Session timeout до 5 часов. И пришли к следующей схеме — consumer подъезжал, загружал в себя данные и висел с ними около двух часов. В итоге топики блокировались, и из них ничего нельзя было достать. 

Мы решили снизить max.pool.interval, а Session timeout оставить, как есть. Для этого нам потребовалась group.instance.id, которая позволила Kafka в случае, если отвалится первый consumer, дождаться Session Timeout Milliseconds, когда поднимается второй consumer и заберёт себе партиции. 

group.instance.id — классная штука, но использовать её надо аккуратно, иначе может возникнуть ситуация, когда consumer лежит в тайм-ауте три часа, никто на это не реагирует, и один из топиков простаивает, пока все остальные двигаются вперёд. 

Фейл №3: TTL

Проблема: у нас отвалился второй consumer. Он провисел какое-то количество времени, а затем начал забирать данные из топика. И в результате мы обнаружили, что у нас пропала часть рецептов. 

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

Kafka рассчитана на большую пропускную способность и резервирует свои сообщения на диск. Время от времени она выполняет операцию compacting — берёт и отрезает часть сообщений. По умолчанию, TTL у Kafka — одна неделя.  Но часто об этом забывают, а потом обнаруживают, что в log отсутствует данные старше 7 дней. 

Решение: нужно использовать max.poll.interval, ставить разумный Session timeout и следить за здоровьем consumer. Желательно делать мониторинг. 

Пара слов о Kafka напоследок

Основная проблема в том, что Kafka воспринимают и настраивают неправильно. Казалось бы, всё очень просто (тот же Rabbit MQ в разы сложнее), но внутри есть огромное количество разных «крутилок». И очень важно научиться их правильно использовать. Не воспринимайте эту статью как рекомендацию не работать с Kafka. Напротив, Kafka — отличный инструмент. Но здесь мы попытались сделать честный обзор и рассказать о сложностях, с которыми вы можете столкнуться.

Для тех, кто хочет научиться работать с Kafka и избежать фейлов

У нас есть видеокурс «Apache Kafka для разработчиков». Он быстро переведёт вас на новый уровень владения инструментом и поможет:

  • уменьшить время на рабочие задачи с Kafka;

  • упростить работу с микросервисами;

  • разобраться в типовых шаблонах проектирования;

  • сделать приложение более отказоустойчивым;

  • получить опыт разработки приложений, использующих Kafka.

Также у нас есть курс «Apache Kafka База». Он ориентирован на системных администраторов, но архитекторам и разработчикам тоже будет полезен. Он комплексно охватывает Kafka и даёт понимание, какое место она занимает в жизни организации.

Комментарии (22)


  1. thegriglat
    22.06.2022 20:41
    +1

    Начали проект на кафке с горящими сроками и в итоге перелезли на NATS -- легко и очень быстро для наших нужд. Столкнулись как раз с указанными в статье проблемами....


    1. rinat_crone
      22.06.2022 23:21
      +1

      NATS классный! Жаль что у него нет (и не будет, по всей видимости: https://github.com/nats-io/nats-streaming-server/issues/524) возможности распараллеливания обработки сообщений в случаях, когда порядок обработки для вас важен (в event sourcing например) — то, что достигается в Кафке с помощью партиционирования и consumer groups.


      1. gecube
        22.06.2022 23:33

        А ещё натс указан в https://landscape.cncf.io


  1. gecube
    22.06.2022 22:11
    +7

    Решение: мы заменили Kafka на RabbitMQ, который может по ключу определить, в какой канал отправить конкретное сообщение и кто его должен получить. Также по этому ключу потребители могут подписываться на обновления. 

    ничего не понял. Что мешало сделать один большой топик и все рецепты кидать туда как в общую шину, а на стороне консумеров сделать фильтрацию на основе ключа, который покажет подтип сообщения? Аналогия с газетой очень хорошая. Консумер один будет читать первую и последнюю страницу, консумер второй - первую и вторую, а третий - какую-то посередине. Все довольны. Единственная проблема, которая возникнет - если понадобится поменять формат сообщения. Это надо будет обновить продюсер и все консьюмеры. Но, черт возьми, в ребите будет ТОЧНО такая же проблема. А Кафка еще и производительнее

    Проблема: есть consumer, который потребляет данные. Вы радостно отправляете данные в Kafka, но потом замечаете, что consumer ничего не потребляет. Kafka работает, consumer работает, но ничего не происходит. 

    по этой проблеме. Нужно было честно сказать - что речь о консумер группе, потому что никто не мешает два независимых консьюмера отправить читать топик с нулевого офсета. Но за рассказ спасибо. Это может реально кому-то сэкономить немного времени

    Решение: мы увеличили max.poll.interval до двух часов и Session timeout до 5 часов. И пришли к следующей схеме — consumer подъезжал, загружал в себя данные и висел с ними около двух часов. В итоге топики блокировались, и из них ничего нельзя было достать.

    ожидали какие-то чудеса? ну, нет никакой магии. И в принципе все логично. Если консумер ушел в какое-то cpu bound или i/o надолго и бесперспективно, то он не сможет читать сообщения.


    1. AbrekUS
      23.06.2022 00:22
      +2

      Тоже не понял почему нельзя было использовать один топик + много consumer-ов?
      У нас похожая система работает для обработки файлов: сообщения о новых PDF/JSON/etc. файлах пишутся в один топик и все обработчики слушают его и решают обрабатывать или игнорировать файл.


    1. denis-isaev
      23.06.2022 03:07

      А Кафка еще и производительнее

      Зачем использовать менее удобный (для конкретной задачи), но более производительный инструмент там, где эта производительность не важна?
      В сабжевой задаче, насколько я понял, rabbit благодаря своему гибкому роутингу, не выходя из rabbit-а, решил конкретную проблему, без сложностей вида «на стороне консумеров сделать фильтрацию».


      1. aceofspades88
        23.06.2022 08:10

        ну тут наверное можно сравнить стоимость замены кафки на кролика в стэке и написание доп 3 строчек кода на стороне приложения.


        1. denis-isaev
          23.06.2022 11:41

          Ну так-то, Кафку на кролика тоже можно «тремя строчками» поменять. Одна на инклюды/импорты, вторая на конфиг, третья на коннект :)


          1. ivankudryavtsev
            23.06.2022 22:28

            autocommit=true? Как бы, далеко не самый частый кейс... а ведь еще бывают транзакции...


      1. gecube
        23.06.2022 11:39

        Зачем использовать менее удобный (для конкретной задачи), но более производительный инструмент там, где эта производительность не важна?

        если производительность не важна - парни берут и делают очередь на редисе ) А Кафка уже во многих организациях есть как некое стандартное платформенное решение. Берешь и подключаешься.


        1. denis-isaev
          23.06.2022 12:03

          Rabbit тоже во многих конторах есть как стандартное платформенное решение.


          1. gecube
            23.06.2022 12:06

            Какая производительность? :) 

            это троллинг?

            Тут рецепты хавки в кафке хранились. 

            откуда я знаю - может это какая-нибудь ДоДо пицца была, с одновременным запуском в печь 10000 пиццуль. В конце-концов народ умудряется с кривой архитектурой и аналитикой данных бигдату и хайлоад на ровном месте учудить


            1. denis-isaev
              23.06.2022 12:42

              Странный диалог получается :)

              — Мы тут рецепты в кафке хранили и перенесли в rabbit, стало удобнее.
              — Вы чо! Кафка производительней!
              — Зачем нужна эта производительность?
              — А вдруг бигдата есть?! А вдруг додо пицца?! Верните кафку!

              Автор же уже пояснил, что им кролик удобнее. Значит им его производительности хватает.


              1. gecube
                23.06.2022 12:57

                да я не спорю, что кроль привычнее и удобнее разрабам. Но более правильным решением от этого не становится. Я сам неоднократно говорил, что на мой взгляд голая кафка слишком низкоуровневая. И это получается практически закат солнца вручную. Ну, та же история с ретраями, да? Как в Кафке это красиво сделать, без того, чтобы плодить топики. И чтобы не усложнять продюсер. Но именно по этой причине вокруг Кафки уже есть целая экосистема на любой вкус. Kafka Streams, Ksql. Сами конфлюентовцы предлагают Кафку как бд использовать. В принципе, это уже троллейбус из буханки получается, но для ряда задач - почему нет? а что с реббитом? да ничего ))) И, повторюсь, что если нужна простая очередь - ну, ее легко сделать на чем угодно - redis/pg, или взять что-то более нативное и современное вроде NATS (тоже есть в нескольких проектах и прекрасно себе работает)


                1. denis-isaev
                  23.06.2022 13:16

                  Что толку с того, что есть ksql, если нужен message broker? В случае с кафкой, как Вы сами сказали, придется чем-то ее сверху наворачивать или что-то свое пилить. С редисом похожая история. А в раббите все из коробки. Логично в этом случае взять именно раббит. А у колнфлюента еще и на каждом шагу с вас будут пытаться срубить тонны бабла. Хотите со стримами и ksql удобно поработать, купите у нас кондуктор за тонны нефти.


                  1. gecube
                    23.06.2022 13:31

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

                    наворачивание заключается в подключении правильной библиотеки. Для разработчиков это не должно быть проблемой.

                    А у колнфлюента еще и на каждом шагу с вас будут пытаться срубить тонны бабла.

                    я не предлагал пользоваться конфлюентом. Кафка - опенсурс и бесплатная. Бери и пользуй. И даже, наоборот, возможность купить поддержку у конфлюента является плюсом. А ребит кто там коммерчески поддерживает? Честно - не знаю.


  1. ivankudryavtsev
    22.06.2022 22:38

    Изначально мы попытались решить проблему, сделав много разных топиков на каждого потребителя. И это закономерно привело к тому, что Kafka «встала колом».

    Объясните пожалуйста почему Kafka встала колом от 150 топиков? Confluent, насколько мне известно, говорит о тысячах, если не десятках тысяч топиков без проблем. Ну и, в целом, чем плох паттерн с topic=адрес сервиса, если я правильно понял вашу топологию?

    The rule of thumb is that the number of Kafka topics can be in the thousands. Jun Rao (Kafka committer; now at Confluent but he was formerly in LinkedIn's Kafka team) wrote: At LinkedIn, our largest cluster has more than 2K topics. 5K topics should be fine.


    1. billyevans
      23.06.2022 07:45

      Я тоже этого не понял, вот сегодня только видел маленький кластер кафки с примерно 4к топиками и 16ТБ данных на 3х брокерах всего в весьма средних инстансах AWS.


  1. hrensgory
    22.06.2022 22:51

    Как связано время старта приложения и ребалансировка консьюмер группы?

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


    1. gecube
      22.06.2022 23:06

      Но вообще-то коллега прав. В момент старта кучи реплик консумера в консумер группе будет ребаланс. И когда консумер сдохнет. Тоже будет ребаланс. И сам по себе время от времени ребаланс может быть. И про это же ниже - что не должно быть так, что Кафка ТОЛЬКО ЛИШЬ ребалансом занимается ( я видел такие кейсы ).


      1. AbrekUS
        23.06.2022 00:12

        Incremental Cooperative Rebalancing поможет уменьшить время ребаланса.


  1. AlexSpaizNet
    23.06.2022 10:19
    +1

    Странно читать. Мы наоборот переходим с раббита на кафку в местах где нужны несколько консюмеров, для декаплинга доменов, где нужен order of messages например в скопе аккаунта или на более гранулярном уровне например reservation, или нужна sequence обработка что бы уменьшить оверхед работы optimistic concurrency.

    Раббит попрежнему используем для round robin обработки, ритраев, роутинга и т.д.

    Я думаю что все таки зависит от целей и бизнеса. Так категорично менять раббит на кафку везде мы бы не стали.

    Но вот некоторые вещи из статьи пригодятся!