Блог компании Badoo
Высокая производительность
Программирование
Разработка веб-сайтов
Тестирование веб-сервисов
Комментарии 29
+1
а как же вариант?
Делаем багфикс релиз с частичным циклом тестирования?
+1
Упустили, когда делал опрос и я думаю что сейчас уже совсем не круто менять варианты, но это здорово, что вы отписались тут. А не расскажите, чем у вас отличается цикл тестирования багфикса от полного и почему? Не гоняете автотесты или только автотестами и обходитесь? Может, какой-то промежуточный вариант?

И прям очень интересно было бы узнать, насколько автоматизирован процесс, если не хотите описывать, то может хотя бы оцените по шкале 0-10 баллов?
+1
Прогоняем регрессионные автотесты для области в которую делался фикс. И ещё проверяется непосредственно сам фикс, что он действительно исправил проблему. У нас к счастью проект разбит по областям, а фиксы в ядро не так часто делаются :)

А полный цикл содержит в себе ещё множество кейсов которые проверяются руками, из-за невозможности их автоматизировать (требуется взаимодействие со сторонними системами), но более я не знаю, этим рулит уже qa отдел.
0
первые восемь символов от MD5-хеша

Что это значит? MD5 128-битный. Кто такие "символы"?

0

Как правило, эти 128 бит представляют в виде последовательности из 32 шестнадцатиричных цифр, например так делает стандартная утилита md5sum:


$ echo 1 | md5sum
b026324c6904b2a9cb4b88d6d61c81d1  -

Мы берем первые 8 из этих чисел, которые, по сути, просто символы из набора [0-9a-f]. Немного удивлен вопросу, потому что думал что неподготовленному человеку это должно быть более понятно. Постараюсь с утра придумать альтернативный, лаконичный вариант, спасибо!

+2
8/32*128=32(бит). Теперь вопрос, почему 8? Почему не 7 или не 9? Не 4 или не 16? А вы уверены что если вот так брать и откусывать кусок байт от хэша, то оно хоть сколько-то адекватно будет работать? А чего сразу не взять 32-битный хэш-алгоритм?
+5
Есть такое понятие как common sense. Я полагаю что тестирования на возникновение колллизий не производилось, но выбранной (с запасом) точности хватает для решения поставленной задачи. Риск (вероятность возникновения ошибки, помноженная на стоимость её устранения) присутствует при разработке любого ПО. Как мне кажется, в данном случае риск не стоит того чтобы применять алгоритмы хеширования, отличные от повсеместно распространённых.
0
Взятие первых 32 бит от MD5 является типичным алгоритмом хэширования? И там ведь сверху ещё велосипедик навёрнут:

Теперь, помимо версии, мы проверяем размер файла: если в репозитории он такой же, то, скорее всего, это один и тот же файл.

+2
А вы уверены что если вот так брать и откусывать кусок байт от хэша, то оно хоть сколько-то адекватно будет работать?

Лавинный эффект позволяет так думать. Если лень читать, то это свойства, при котором выход функции будет сильно, "лавинно", меняться при небольшом изменении входа.


Взятие первых 32 бит от MD5 является типичным алгоритмом хэширования?

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


И там ведь сверху ещё велосипедик навёрнут

Я думаю, что стоило "подстелить" этот велосипед еще в самом начале, потому что даже полный md5 хэш не гарантирует, что коллизии не будет. Этот шаг — скорее попытка разрешить коллизию, когда она появится, чем полностью предотвратить ее.

0
Для криптографических хешей любая его часть тоже является криптографической хэш функцией. Тот же crc32, к примеру, не криптографический хэш, и его качество в качестве алгоритма хеширования будет хуже куска md5. Ну а 8 символов было выбрано, чтобы имена файлов были не слишком длинные :). Лучше было сделать побольше, по всей видимости :).
+1
Несмотря на то, что MDK частично заимствует идеи у Git, у них есть и несколько отличий. Самое главное — то, как хранятся файлы в рабочей директории (т.е. на машинах). Если Git хранит там только одну, текущую, версию, то MDK держит там все доступные версии файлов. При этом на текущую версию кода указывает только один симлинк current.map, который использует в своей работе autoload и который можно атомарно поменять. Для сравнения, Git для изменения версии использует git-checkout, который меняет файлы по очереди и не атомарен.

В git еще есть worktree, позволяющий одновременно иметь несколько открытых веток:
https://git-scm.com/docs/git-worktree


Просто для информации — вдруг кто-то прочитает, и решит, что в Git нет такой функциональности.

0
Кстати, отличная штука для некоторых задач автоматизации процессов, был очень рад, когда они добавили эту функциональность.
+1

Того же самого можно было достичь, используя "double buffer" из двух рабочих директорий Git и одного симлинка — вот вам и версионирование и атомарность

0
Если рассматривать только эти два требования — да, это сработает. Но в таком варианте вам нужно следить за тем, чтобы выкладывать код не чаще, чем max_execution_time скрптов или запросов, которые этот код используют.

В нашем случае, это плохо работает потому, что у нас есть скрипты, которые работают часами. Если я ничего не путаю, то мы договаривались что версия кода должна оставаться «рабочей» на машине в течение 2 часов. Не могу сказать, что мне нравится это требование, но с ним приходится жить. Для статистики могу добавить, что за 2 последних часа вчерашнего дня мы выложили 28 версий кода, т.е. нам понадобилось бы 28 рабочих директорий. Это все еще можно использовать, но с Git и доставлять не так удобно, особенно, сгенерированные файлы.
-1
Это все еще можно использовать, но с Git и доставлять не так удобно, особенно, сгенерированные файлы.

С гитом доставлять как раз очень удобно. Схема очень проста.
Артефакт любой сборки версионируется с помощью гита, что позволяется иметь версионность прокадшен кода. На сервере, на который нужно выложить данный код, запускается git pull. Всё. Выкладка кода на сервер на этом заканчивается.

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

Докер позволяет всё это красиво упаковать по самостоятельным контейнерами:
— git-pull контейнер, который деплоит код в отдельный том.
— php-fpm (или что угодно) контейнер, который делает rsync кода в другой том (если нужно делить код между несколькими контейнерами) или в локальную систему (например, для cli-контейнера).
Протокол обновления:
— стартуем git-pull контейнер, который деплоит код на сервер;
— как он отработал, запускаем rsync-контейнер, который подготовит текущую версию к использованию (если этот этап нужен);
— как он отработал, запускаем новые upstream контейнеры с приложением, а старые постепенно глушим;
— если нужен скрипт, то запускаем cli-контейнер, который сам себе делает rsync, работает сколько ему нужно, после чего удаляется.

Статика деплоится по такой же схеме:
— git-pull контейнер на базовых серверах деплоит текущую версию статики.
— rsync-контейнеры (или любой другой способ) на cdn-нодах.
Статика собирается с хешом в именах файлов, так что на cdn спокойно живут версии разных файлов.

Для статистики могу добавить, что за 2 последних часа вчерашнего дня мы выложили 28 версий кода, т.е. нам понадобилось бы 28 рабочих директорий.

Версия с докером подразумевает, что может быть до 28 дополнительных директорий с кодом, если на сервере будет крутиться до 28 версий апстримов приложения.
Неиспользуемые тома от уже заглушенных версий можно использовать при инициализации новых версий, чтобы копировать только изменения, а не весь объем кода.
0
Красиво расписали. А теперь представьте что серверов не 3 и не 5, а несколько тысяч. И что выкладка делается без остановки всего и вся, «наживую». И подумайте еще раз.
+2
По сути, MDK очень похож на git в смысле «git pull и всё». Так что с этой точки зрения — мы ровно так и деплоимся. К сожалению git — инструмент не для деплоя. Он прост и удобен и хорошо работает на малых масштабах, но pull — достаточно прожорливая операция. 100-200 параллельных git pull и сервер задыхается. Да, можно заткнуть эту проблему железом — поставить сервер «пожирнее», потом, когда его не станет хватать — сделать несколько «реплик» git-репозиториев на разных серверах и выкладывать кластерами, каждый из которых будет смотреть в свою реплику. Но этим тоже нужно управлять: следить за тем, что свежая версия выехала на все реплики, что кластер ходит в свою реплику и может сходить в соседнюю при недоступности своей и т.п.

Касаемо поддержки множества версий готовых к быстрому включению в работу: да, можно хранить 28 дополнительных директорий, но если считать, что одна версия весит 1ГБ и код этот лежит на 1000 серверов, получается 28ТБ места. Да, тут тоже можно оптимизировать: можно разворачивать директорию с нужной версией только непосредственно перед использованием, но тогда время переключения версии «везде» будет занимать не 2-3 секунды, а несколько минут. Можно использовать хардлинки или специфические свойства ФС (снепшоты и т.п.), но в этом случае сложность системы всё растет и добавляются всё новые проблемы.

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

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

MDK в данном случае — это ещё один инструмент со своим набором плюсов и минусов.
Он прост и один решает из коробки комплекс задач: атомарность переключения (symlink current.map), быстрое и почти синхронное переключение версии на большом количестве машин, экономия трафика при выкладке (за счет доставки только недостающих изменений), высокая стойкость к нагрузкам (у нас каждый ДЦ сейчас обслуживается всего одним сервером и он не умирает, когда тысяча машин с производства внезапно хотят сразу все получить свежие данные), отсутствие необходимости держать несколько PHP-FPM во время переключения (и тратить на них драгоценную память), возможность автоматически обнаруживать неконсистентность кода (если кто-то пришёл и руками поправил на машине файлик).

Благодаря его использованию у нас получилась _простая_ система: в ней нет большого количества узлов и механизмов, она даёт нам много свободы, за ней просто следить и её просто эксплуатировать, она хорошо вписалась в нашу инфраструктуру.
+1
да, можно хранить 28 дополнительных директорий, но если считать, что одна версия весит 1ГБ и код этот лежит на 1000 серверов, получается 28ТБ места.

Такая ситуация возможна, только если реально кто-то держит старые процессы, не давая им умереть. Если контейнеры со старыми версиями глушаться, то мы получим одновременно на сервере не более чем количество коммитов за max_execution_time + 1.

100-200 параллельных git pull и сервер задыхается

Тут честно скажу, что не представляю, какая будет нагрузка от git pull, который получает изменения от небольшого коммита, помноженная на сотни серверов. Возможно, что без кластеризации репозитория не обойтись в этом случае.

Сам же идея MDK мне понравилась. Элегантное решение, позволяющее заставить работать разные версии кода в рамках одного мастер-процесса. Докер-вей в этом плане проще, так как просто запусти контейнер с нужным кодом. И гит или не гит, с кластером или без, где и что кешировать, чтобы нужные версии кода быстро подсунуть новому контейнеру на конкретном сервере — это вопрос транспортной инфраструктуры, а не продакшен среды. Здесь «гит пул и всё» — это только вариант доставки кода приложения туда, где он будет использоваться контейнером.
0
И гит или не гит, с кластером или без, где и что кешировать, чтобы нужные версии кода быстро подсунуть новому контейнеру на конкретном сервере — это вопрос транспортной инфраструктуры, а не продакшен среды.


Так MDK — это как раз про решение задачи доставки данных и есть.

Докер-вей в этом плане проще, так как просто запусти контейнер с нужным кодом.


Да, но у него есть свои недостатки: вам, например, приходится выбрасывать все ресурсы, которыми обладал контейнер с предыдущей версией. И если между версиями поменялась буквально пара файлов — все кеши, память, подгруженные классы и пр. способы ускорения работы кода это не спасет. У вас новые процессы, соответственно новая память, новые autoload'ы классов и пр.

Если данные (код) не пихать в docker, то по сути нет большой разницы: с доставкой кода средствами MDK, FPM не будет нужды перезапускать ни в виде контейнера, ни в виде сервиса напрямую в системе. Если код пихать в контейнер — то опять вылезает задача эффективной доставки всего этого добра с новой версией на производство: вам либо надо тащить из-за пары файлов всё, либо как-то выкручиваться.
0

Далее всё сферическо-вакуумное.


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


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


MDK реально хорош для натива. Но, в том числе из-за того что это всё-таки фреймворк исполнения, мне ближе и интереснее докер-вей, как не вводящий очередные «рамки» в разработку.

+1

Очень важные требования к системе, упущенные в статье :) стоит добавить

0

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


В целом я немного удивлен, что именно эта часть статьи такая комментируемая, думал что люди чаще будут писать о своем опыте "багфиксинга", это было бы интересно (по крайней мере мне) почитать.

0
А теперь представьте что серверов не 3 и не 5, а несколько тысяч.

Что именно тут не масштабируется?
0

Все в этой жизни масштабируется, вопрос в трудозатратах. Но мне хочется другую сторону вопрос обсудить: а вы решаете вопрос доставки? Зачем в этой задач git-pull- и rsync- контейнеры, почему не сделать это просто на системе? Мне правда интересно, что с этого можно получить.


Статика собирается с хешом в именах файлов, так что на cdn спокойно живут версии разных файлов.

Т.е. статика у вас будет выглядеть как у нас код, только без карт содержимого?

+1
Т.е. статика у вас будет выглядеть как у нас код, только без карт содержимого?

Вебпак и ко по сути ввели стандарт, что вся сборная статика выглядит в виде [name].[hash].[ext].

git-pull- и rsync- контейнеры

Это и есть способ доставки.
Гит-пулл — чтобы доставить до сервера код, который на нем будет крутится.
Rsync — чтобы снять слепок этого кода, таким образом сняв «блокировку» с гит-пулл-тома. php-fpm будет использовать тот же том, что и rsync-контейнер, в том время как гит-пулл контейнер спокойно может обновлять у себя код до новой версии, ни о чем более не думая.

Простой кейс, когда приходит новый коммит.
— гит-пулл-контейнер обновляет свой том до этого коммита;
— rsync-контейнер ищет неиспользуемый rsync-том (чтобы не с нуля) и синхронизирует в нем код с гит-пулл-тома;
— поднимается php-fpm-контейнер с этим синхронизированным томом;
— старый php-fpm-контейнер глушится, освобождая для повторного использования свой rsync-том.
0

Кажется, разобрался. Не сразу сообразил, что под томами вы имеете ввиду volume, не был знаком с этим термином в русскоязычном варианте.


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

0

В другом комментарии уже написал, что контейнер со старым кодом можно и не глушить. Изолировать его каким-нибудь образом, чтобы новые запросы на него не шли, спокойно синхронизировать его том и вернуть в пул.
Либо пойти ещё дальше и делать сине-зеленый деплой.

0

Если же говорить о чисто «багфиксинге», то все сводится к попаданию кода в продакшен-ветку. После чего она собирается, артефакт кладется в деплой-репу. Все, кто мониторят деплой-репу, обновляются. Продакшен, стейдж, что угодно — просто разные ветки.

Только полноправные пользователи могут оставлять комментарии. , пожалуйста.