В 2018 году я столкнулся с непобедимым багом в сторонней библиотеке. Баг запустил целую цепочку событий, которая привела к куче интересных выводов.
Предыстория
У рекламных бизнесов вроде 2ГИС или Google есть только один вариант выживания – это нравиться не только пользователям, но и рекламодателям. Пользователи не платят, а рекламодатели платят.
Пользователям нравится, когда сервис классно решает свою задачу + когда сервис идёт в ногу со временем. Обычно пользователи мобильных и веб-приложений 2ГИС запускают приложение чтобы что-то найти. Они кликают по результатам поиска, по домикам на карте, настраивают разные параметры. Пользователи делают много разных вещей. Компании хотелось бы понимать, как обычные пользователи (их десятки миллионов) пользуются приложением (и как не пользуются).
Рекламодателям нравится, когда расходы на рекламу прозрачны. Например, это означает, что все клики в проплаченные позиции в поиске должны быть отслежены и предоставлены рекламодателю в виде красивого отчёта.
Поэтому наша команда создала и развивала систему приёмки и конвейер обработки бизнес-событий, таких как клики пользователей и запуски приложения. Первая версия этой системы работала в продакшене и обрабатывала пару сотен запросов в секунду (в каждом запросе могло быть тысячи бизнес-событий). В качестве основы второй версии системы мы выбрали Kafka. У Kafka всё отлично с масштабированием. Нас это интересовало потому что десятки миллионов хаотично кликающих пользователей - это не шутки. Плюс Kafka позволяет доставлять события без дублей и потерь (в первой версии системы мы с этим настрадались).
На тот момент мы писали большую часть кода на C++, а клиентская библиотека Kafka для C++ была только одна – librdkafka. Библиотека открытая, благодаря чему мы хотя бы как-то могли разобраться в том, как она работает. Документация и программные интерфейсы у библиотеки были далеки от идеала. Блеск и нищета опенсорса, как говорил наш тимлид. Я не осуждаю автора библиотеки, наоборот, я его очень уважаю. Он написал огромную и полезную библиотеку практически в одиночку. Ожидаемо, что на что-то у него не хватало времени.
Однажды QA нашли баг, который приводил к потере событий. Потеря событий происходила из-за того, что система полностью выходила из строя. Баг зажёвывал систему как кассету в видаке.
История
Я сел разбираться с этом багом. Довольно быстро стало понятно, что баг в librdkafka. Где именно внутри librdkafka – не понятно. Реализация была крайне запутанной. Ядрёный многопоточный код на C/C++, куча синхронизаций потоков, куча нетривиальных оптимизаций ради производительности, эх.
Поразбирался ещё пару дней. Стало понятно, что исправить этот баг в librdkafka у меня нет шансов. Исправление требует глобальных переделок библиотеки, а так как тестов там не было, полноценно проверить результат переделок я не мог. Посоветоваться с автором я не мог. Стеснялся, наверное. У автора была кувалда на аватарке, а меня эта кувалда как-то напрягала.
В библиотеке для Java/Scala похожего бага не было, но и написана библиотека была совершенно по-другому. Вообще никаких параллелей с реализацией для С++. Устройство библиотеки для Java/Scala была на порядок проще.
За длинные выходные я переписал один из сервисов на Scala. Да, реализация сервиса была маленькая, около 1000 строчек кода. Функциональность не поменялась, но пришлось сменить формат файла конфигурации. Пришлось переделать метрики, снимаемые с сервиса. Пришлось переделать логи. Процесс сборки и развёртывания был заметно другой. В общем ещё тысяч 6 строк кода.
Я думал, что совершил геройский поступок, оперативно переписав всё с нуля. А дальше меня встретили два привета:
- Синдром запястного канала. Я думал, что болезнь машинисток - это редкость. Оказалось, это может легко произойти, если без перерывов печатать по 12+ часов несколько дней подряд. Даже несмотря на относительно эргономичную клавиатуру (Microsoft Sculpt) и эргономичную раскладку (Colemak). Скорее всего, без них было бы хуже. Отстёгнутая кисть руки - это сильно неприятно.
- Реакция QA. Почему-то оказалось, что такому кардинальному фиксу они не рады. Нужно всё перетестировать с нуля. Аргументы про то, что иначе баг не пофиксить, работали вяло.
По итогу синдром я вылечил (ушли годы), QA убедил (ушла пара дней). После убеждения QA пошёл к смежной команде и уговорил их переписать один из их сервисов с их любимого C# на Java. В C#-реализации был такой же баг. Я думал, меня встретят враждебно, ведь сишарперы в Интернете терпеть не могут Java. Оказалось, нет, люди всё поняли и приняли. Было приятно ревьюить их финальную реализацию, идеальную в техническом плане.
Выводы
- Хорошо, когда ты можешь посоветоваться с автором. Это касается не только опенсорс-библиотек, но и чего угодно. Не стоит стесняться, не нужно ждать, что подтянешь английский. Вот он, шанс его подтянуть. В принципе, следить за ходом мысли отцов любимой технологии или предметной области бесценно. Имеет смысл хотя бы знать десяток имён и фамилий первопроходцев в твоей области.
- О глобальных переделках нужно анонсировать заранее. Если уверен на 100%, не нужно просить разрешения, но предупредить и вкратце объяснить стоит. It’s easier to ask forgiveness than to get permission, как говорила Грейс Хоппер (она ввела термин “баг”).
- Компьютер может сильно подпортить здоровье. Имеет большой смысл вложить хотя бы пару месяцев своего труда (aka две зарплаты) в эргономику рабочего места. Будь честен с собой, впереди будут ещё сотни месяцев сидения за компьютером. Хорошая эргономика может превратить эти месяцы из тяжёлых и болезненных в лёгкие и приятные.
- Даже если тебе кажется, что смежная команда сожжёт тебя и твоё предложение, всё равно иди. Вежливых и открытых людей никто не сжигает. Друзья в смежных командах – это здорово.
- Нужно помогать коллегам не вляпаться в овертаймы на ровном месте.