Nikita Shirikov Ed blog

Pipeline

Автор: Тимур Алиев (tg: @talievv)

Пайплайны в контексте процессов разработки в компаниях — то, что происходит с кодом от момента зарождения идеи до релиза на продакшене.

Системы контроля версий: Git и монорепозитории

Проблемы Git в микросервисной архитектуре

В крупных компаниях программирование часто устроено так, что используется Git, и проект делится на множество репозиториев. Например, компания вроде mini Netflix с 20 микросервисами может иметь 20 репозиториев. Преимущества такого подхода с Git и множеством репозиториев:

Недостатки:

Монорепозитории: преимущества и недостатки

Большие компании используют концепцию монорепозитория, где все репозитории объединены и доступны всем разработчикам. Преимущества монорепозитория:

Недостатки:

Ограничения Git для больших монорепозиториев

Медианный размер репозитория на GitHub составляет пару мегабайт. Размер репозитория Linux - гигабайт, Chromium - 10 гигабайт, а Yandex Arcadia - терабайт. Это в 1000 раз больше, чем Linux, и работа с Git становится невыносимо долгой. В Google, где репозитории еще в 100 раз больше, пользоваться Git в принципе невозможно. Сравнение производительности старых систем (SVN, Mercurial) и Git на больших репозиториях показывает, что Git может просто не завершить операции.

Решение проблемы: виртуальные файловые системы

Системы, используемые крупными компаниями (такие как те, что разрабатываются в Google, Facebook, Яндексе), решают проблему большого размера монорепозитория, храня большую часть информации на сервере и загружая ее лениво (только то, что используется в данный момент). Они создают виртуальную файловую систему. В отличие от Git, эти системы требуют постоянного подключения к серверу.

Концепции разработки: Trunk Based Development vs. GitFlow

Trunk Based Development

Trunk Based Development (Разработка на основе магистрали) — это концепция разработки, при которой основная работа ведется в одной ветке, называемой Master или Trunk.

Процесс в Trunk Based Development:

  1. Создать фичер-ветку от Master.
  2. Разработать фичу.
  3. Перед мерджем выполнить rebase на последнюю версию Master.
  4. Смерджить обратно в Master.
  5. Для релиза просто отводится ветка от Master.

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

Горячие исправления (Hotfixes)

Если после релиза обнаруживается баг, вместо создания новой Feature-ветки от Master, мерджа в Master и создания нового релиза, можно черепикнуть (cherry-pick) изменения с исправлением сразу в Release ветку. Это гораздо быстрее. Позже эти изменения также попадут в Master.

GitFlow (сравнение)

GitFlow отличается от Trunk Based Development использованием дополнительной ветки DevelopПроцесс в GitFlow:

  1. Разработка фич ведется в фичер-ветках, которые мерджатся в ветку Develop.
  2. От Develop отводится ветка Release.
  3. Релизная ветка тестируется, выкатывается на тестовые стенды.
  4. После успешного релиза ветка Release мерджится в Master.
  5. Горячие исправления (Hotfixes) отводятся от Master, а затем мерджатся как в Release ветку, так и в Develop.

Отличия от Trunk Based Development:

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

Тестирование

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

Основные типы тестов

Важность тестов

Тесты — это ваш главный друг, а не враг. Они нужны не только для самопроверки, но и для того, чтобы никто в будущем не сломал логику вашего кода. Даже для очевидной логики (например, сортировки) стоит написать тест, чтобы отловить баг, который может появиться позже из-за изменений другого разработчика.

Существует концепция Test Driven Development (TDD), когда сначала пишутся тесты, а затем код. Важно найти баланс: программа не должна быть совсем без тестов, но и тестов не должно быть больше, чем самого кода. В некоторых случаях (простое изменение названия переменной, простая логика, которую видно в метриках/логах) тесты могут быть излишни.

Дополнительные аспекты тестирования

Тесты часто запускаются несколько раз.

Санитайзеры

Санитайзеры — это инструменты для обнаружения ошибок, связанных с памятью или потоками.

Санитайзеры работают, например, создавая “теневые байты” для каждого байта данных для проверки корректности обращений к памяти. Они сильно замедляют выполнение программы (может занимать вдвое больше памяти и дольше работать), что часто приводит к таймаутам. Решение — увеличение таймаутов для таких тестов.

Тесты могут собираться с различной информацией для отладки, например, Release with Debug Info(relvisdebinfo).

Проблемы с большим количеством тестов

В огромных сервисах количество тестов может быть бесконечно большим.

Flaky тесты

Flaky тесты (флапающие тесты) — это тесты, которые иногда проходят, а иногда падают без видимой причины.Основные причины:

Чтобы определить, является ли тест падающим или flaky, он часто запускается несколько раз при провале.

Ограничения стандартных тестов

Стандартные тесты (UT, FT) не всегда достаточны для огромных сервисов, таких как поиск или реклама, которые работают с гигантскими базами данных и сложным поведением. Сложно создать маленькую фейковую базу данных, которая бы воспроизводила все сложные сценарии.

Специализированные системы тестирования

Для тестирования сложного поведения существуют отдельные команды, которые пишут специальные сервисы для тестирования. Например, в Яндексе есть большой отдел, который пишет тестирующую систему для рекламы.

Крупномасштабное тестирование (регрессионное)

Такие системы могут работать следующим образом:

  1. Собирается большой набор реальных запросов (например, 100 000).
  2. Поднимаются две виртуальные машины: на одной разворачивается текущая версия (trunk), на другой — версия с тестируемым изменением.
  3. 100 000 запросов отправляются в обе машины.
  4. Сравниваются ответы (должны быть одинаковыми) и производительность (никто не должен просесть). Это похоже на FT, но выполняется в масштабе, невозможном локально (нельзя поднять огромную базу данных и множество сервисов локально).

Проблемы с такими системами включают flakiness из-за нестабильности микросервисов, к которым обращается тестируемый сервис. Этим занимаются целые команды.

Верификация кода вне тестов

Логирование и Метрики

Помимо тестов, для уверенности в работе кода используются логирование и метрики. Это похоже на отладку с помощью cout или cerr на курсах, но в промышленных масштабах.

Алерты

Поверх логов и метрик настраиваются алерты, которые срабатывают при обнаружении аномалий, сигнализируя о возможных проблемах.

Тестовые окружения (Staging/Preprod/Mirrors)

Когда тестов, логов и метрик недостаточно, или когда сложно написать тесты (например, из-за сложной логики или зависимостей), код выкатывают на тестовые стенды.

Отладка (GDB)

На тестовых окружениях (mirrors) единственным способом отладки, если другие методы не помогли, является подключение GDB к запущенному сервису. Можно поставить брейкпоинт, отправлять запросы вручную или использовать специальные инструменты для воспроизведения сломанных запросов, и пошагово отлаживать. Знание GDB полезно в сложных ситуациях, когда баг проявляется только в большом тесте или на реальном трафике. Для отладки с GDB сервисы на mirrors собираются со специальным флагом (-g), который добавляет отладочные символы.

Профилирование

Профилирование — это анализ производительности программы. Используются инструменты для построения графиков вызовов функций (например, Flame Graphs). Пример работы простого профайлера (Sampler): периодически останавливает программу (например, 100 раз в секунду) и снимает стек вызовов. По собранным стекам строятся графики, показывающие, сколько времени программа проводит в тех или иных функциях. Профилирование используется для поиска узких мест в коде, чтобы понять, какие части программы работают медленнее всего и где требуется оптимизация. Продовые сервисы часто собираются с флагом профилирования для автоматического сбора данных.

Процесс ревью кода (Code Review)

Ревью кода происходит только после того, как код прошел все тесты и визуальную проверку (например, по метрикам или на тестовом стенде). Нет смысла отправлять на ревью нерабочий код.

В процессе ревью другие разработчики оставляют комментарии. Если комментарии требуют значительных изменений (не просто очевидный рефакторинг), приходится повторять цикл: внесение изменений, выкатка на тестовое окружение, прогон тестов, повторное ревью. Этот процесс может быть очень долгим и итеративным (пример: 177 комментариев, 21 итерация ревью для сложного комита). В больших компаниях стараются оптимизировать этот процесс, но не всегда успешно.

Релизный цикл

Роль дежурных и релизных команд

После мерджа кода в главную ветку, его выкатка на продакшен осуществляется дежурными или специальными релизными командами. В крупных сервисах (например, реклама) есть целые команды, отвечающие за релизные циклы и стабилизацию кода (уменьшение количества ошибок и флагов, увеличение частоты релизов).

Процесс релиза

Релизы могут происходить по расписанию. Процесс включает:

  1. Отведение релизной ветки от Master (Trunk).
  2. Длительное тестирование релизной ветки на всех инструментах и тестовых стендах (может занимать недели).
  3. Выкатка релиза.

Поиск ломающего комита (бинпоиск)

Если в релизе (содержащем, например, 200 комитов) что-то сломалось, для поиска ломающего комита используется бинпоиск. Откатывается половина комитов, проверяется, присутствует ли баг. Затем процесс повторяется на той половине, где баг остался. Это может быть долго, так как каждый шаг требует запуска длительных тестов.

Благодаря усилиям команд стабилизации, частота релизов может быть увеличена (например, с двух недель до одного раза в день).

Этапы выкатки

Ваш комит сначала выкатывается на Preretable (Preprod), где некоторое время (например, день) наблюдают за его поведением. Если все хорошо, его выкатывают на Stable (Prod).

Сложные сценарии выкатки

А/Б эксперименты (A/B Testing)

Цель A/B тестирования

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

Механизм A/B тестирования

Трафик разделяется на выборки. Например, для эксперимента на 5% трафика создаются две выборки по 5%:

Риски без A/B тестирования

Некоторые патчи, даже прошедшие тесты, могут быть опасны при полной выкатке. Пример: изменение метрики взвешенной балансировки. Если новая метрика дает значительно больший “вес” по сравнению со старой, весь трафик может направиться на поды с новой версией, перегрузить их и вызвать каскадное падение всего сервиса. Такие случаи приводили к значительным финансовым потерям.

Выкатка по кластерам

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

Микросервисная архитектура может позволить быстрее выкатывать и откатывать изменения для маленьких сервисов, так как последствия поломки менее масштабны.

Планирование задач и чтение кода

Откуда берутся задачи

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

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

Начало работы над задачей (чтение кода)

Получив тикет с описанием задачи, не стоит сразу писать код. Сначала нужно прочитать участок кода, в который будут вноситься изменения. Нередко код очень сложный. Важно соблюдать баланс: нельзя сразу идти спрашивать, что делает код, но если после прочтения все равно непонятно, нужно обратиться за помощью к коллегам, возможно, к автору кода. Сложности возникают, когда автор кода уволился, и никто не может объяснить его работу.

Документация

“Документация - это миф”

Документация как всеобъемлющее и актуальное описание кода практически не существует. Компании стараются ее поддерживать, но это сложно.

Почему документация не всегда актуальна/полна

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

Внутренние инструменты и библиотеки

В крупных компаниях часто приходится разрабатывать собственные инструменты и библиотеки, так как стандартные (например, Git или даже некоторые стандартные библиотеки C++) не выдерживают нагрузок или имеют недостатки. Например, в Яндексе редко используется стандартная библиотека C++ (STL) напрямую, она полностью обернута из-за проблем с многопоточностью. Есть отдельные команды, которые занимаются развитием этих внутренних инструментов.

Заключение

Весь процесс разработки, тестирования, релиза, наблюдения и планирования задач цикличен. Описанные процессы актуальны не только для разработчиков бэкенда, но и для других IT-специальностей (аналитиков, ML-разработчиков) с поправкой на специфику их работы.

#Os