Pull to refresh
30
0
Кореневский Денис @DenKoren

Release Engineer

Send message

Нет, не мешает. Никаких специальных приседаний для борьбы с GC мы не делали.

GC начиная с версии 1.5-1.6 уже не такой злой, каким был. Это раньше он мог на 50-100мс залочить исполнение программы и хрустеть там себе памятью.

Сейчас он блокирует мир на очень короткое время и влияния GC мы пока на метриках не видим.

Про "перегруженный" root:

В нашей архитектуре все root'ы обрабатывают одинаковую нагрузку. Если будет тупить какой-то один или два из трёх - это никак не скажется на лимитах. От таких тупящих root'ов на инстансы будет приходить меньшее значение счётчиков и они просто будут отбрасываться (из трёх разных чисел для одинакового счётчика от трёх рутов мы просто выбираем больший). Могут оказаться перегруженными все 3 рута одновременно. Мы пока до такого не доходили, но если такое случится, то YARL начнёт "занижать" нагрузку на весь кластер: часть хостов не смогли отправить счётчики -> суммарный счётчик для кластера складывается из меньшего количества значений -> оценка нагрузки на кластер меньше реальной -> мы лимитируем запросы позже, чем должны.

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

Про выпадение/обновление:

Никак не боремся. Система устойчива к исчезновению любого из root-серверов, она этого даже не замечает.

Каждый инстанс сервиса (в нашем случае хост с сервисом S3) просто отправляет свои счётчики во все root-серверы, о которых она знает, независимо друг от друга. Т.е. мы в конфиге S3 прям прописываем: вот тебе 3 root'а - ходи в них. Когда этот инстанс от каждого из root'ов получает свежие значения счетчиков для всего кластера, он выбирает самое большое значение и использует его.

Синхронизация происходит в фоне, так что залипший синхронизирующий запрос к одному из root'ов никак не сказывается на скорости обработки клиентского запроса. Выпал root - все синхронизации к нему будут таймаутиться, от него к каждому инстансу не будут приходить счётчики кластера. Инстанс будет просто опираться на данные от двух оставшихся root'ов. Выпадет второй root - будем лететь на одном. Выпадут все - лимитирование из распределенного превратится в локальное: синхронизация вырубится и каждая из тачек продолжит накручивать локальные счётчики (сколько конкретно в неё пришло), но не будет знать об общей нагрузке на кластер. В этом случае нам проще считать, что лимитирование вырубается, т.к. у нас в кластере больше 300 машин: чтобы накрутить тот же счётчик до условных 1 000RPS без синхронизации, нужно на весь кластер равномерно подать 300*1000 = 300 000 RPS.

Как только root вернётся - инстансы начнут с ним синхронизироваться и счётчики root'а быстро вернутся к актуальным значениям.

И да, и нет.

YARL встраивается в приложение. Либо в nginx с помощью модуля, либо в go-код прям вкомпиливанием пакета. Т.е. приложение в итоге знает обо всех квотах и счетчиках, которые имеют отношение к приложению.

Но модуль/пакет YARL при этом ничего не знает о логике приложения, и приложение не знает о кишках YARL'а. Общение происходит через простой интерфейс: "вот тебе уникальное имя квоты (текстовое), накрути/проверь/залимитируй".

Приложение, имея доступ к информации о запросе, может из этой информации сгенерить какое-то уникальное имя квоты. Тут мы в S3 можем использовать User ID, имя бакета, тип HTTP-запроса или любое другое свойство, которое из этого запроса можно достать. Из этих данных приложение генерит имя квоты и скармливает YARL'у.

Так что приложение хранит у себя в памяти алгоритм генерации имени квоты для запроса, а модуль YARL хранит в памяти 2 map'ы: ``<quota uniq name> => <quota info>`` и ``<counter ID> => <counter value>``. По имени квоты он достаёт из map'ы её параметры (лимит, high/low burst и ID счетчика), а по текущему значению счетчика понимает, нужно ли блокировать запрос.

В такой схеме достаточно в базе YARL завести квоту с правильным именем, чтобы начать лимитировать запросы с определенными свойствами. Например, если приложение для чтения любого объекта из бакета myawesomebucket проверяет квоту "s3_myawesomebucket_object:get", то достаточно добавить в базу YARL запись с таким именем и запросы автоматом начнут ограничиваться через пару-тройку секунд.

Спасибо, мы старались.

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

До самой публикации сомневался, "полетит ли".

На текущий момент да. Как дальше сложится пока не знаю.

Вот вы придираетесь-то.

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

Нет тут никакого фокуса. Я даже после нескольких лет ежедневного программирования на одном языке не могу сказать, что я его «выучил», если рассматривать это слово только как конечную остановку вида «всё, я знаю все 100%».

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

ИМХО - уровень знания «не знаю всех концепций и конструкций языка, но могу быстро написать prod-ready код, который решает поставленную задачу» можно назвать словом «выучил». Так же, как этим словом можно назвать тот уровень, который описываете вы, с детальным погружением в концепции, специфичные именно для этого языка. Или даже более глубокий уровень, с пониманием нюансов компилятора/транслятора/интерпретатора и эффектов, которые они оказывают на производительность полученной программы при обработке немного по-разному состряпанных фрагментов кода.

Всё зависит от конкретной задачи, которую необходимо решить.

Я правильно понимаю, что в вашем случае задача была измерять квоту на длинной дистанции (суммарное потребление чего-то за неделю/месяц/квартал)?

Если да, то такие штуки мы тоже делаем, но не через YARL. Это задача, которой я очень вскользь коснулся, когда говорил об учёте потребления дисков (объём объектов в бакетах). У такой задачи безусловно есть свои особенности, но обычно там сильно меньше требования и к скорости реакции на событие, и к скорости подсчёта потребления. Его можно хоть в фоне делать отдельной джобой по крону.

YARL затачивался именно под производные величины по времени. Кажется, математически это записывается как-то так: \frac{d}{dt}

Такие штуки очень резко меняются и там за лишнюю минуту времени реакции можно весь сервис успеть в ступор поставить. Поэтому важно как можно раньше понимать, что нужно начинать отстреливать клиентов.

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

Да.

Ответить на запрос клиента полноценно почти всегда означает напрячь базу, хранилище или кеш, сходить в сервис авторизации и т.п. то есть обработка дорогая, если сравнивать со статическим ответом «приходите позже» с 429 HTTP-кодом.

Да, YARL написан на Go.

Похоже, что примерно так и получилось. Я, к сожалению, не знаю истории развития и заморозки лимитера на эрланге.

Да, присоединяюсь. Понравилась манера подачи, читается как детективчик :)
С удовольствием провел время с пользой, спасибо.
Мы в Badoo ровно по этому пути и пошли.
99% задач этот flow покрывает, но увы, не все. Некоторые узкоспециальные вещи через REST API сделать нельзя. Поэтому лезть внутрь нам, хоть и крайне редко, но всё-таки приходится.
Будьте к этому морально готовы :)

У нас для экспериментов с JIRA есть staging-версия, которая раз в сутки синхронизируется с продом.
Эта staging-версия используется админами для накатывания обновлений, чтобы случайно не ушатать прод при переходе на новую версию JIRA или обновлении плагина. Её же мы (релизеры) используем для обкатки наших скриптов: вебхуков, запросов к API, правок workflow и пр.

Синхронизация с продом удобна тем, что у тебя для экспериментов всегда есть актуальное состояние JIRA и ты можешь безопасно поиграть с «настоящими» проектами и тикетами, не рискуя при этом поломать что-то работающим сейчас коллегам.
Спасибо за совет.
Похоже, это хорошее решение задачи, над которым лично я даже не задумывался. Мне почему-то не пришло в голову, что авторизацией в JIRA можно управлять через плагины и аналог того, что в Cloud версии JIRA называется токенами авторизации (которые используются вместо паролей для доступа к API) можно самому собрать на коленке.

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

По рабочим задачам мне приходилось писать JAVA-код для работы с задачами JIRA из ScriptRunner'а. До этого я с JAVA в роли разработчика не сталкивался. Всё в комплексе — знакомство и с JAVA и с JIRA JAVA SDK — это так себе удовольствие для человека «со стороны»: документация, на мой взгляд, скудная, при этом структура JIRA JAVA SDK ещё хуже чем её REST — для выполнения простых действий приходится делать кучу сложных телодвижений: фабрики всякие, контроллеры и пр. При этом есть большое количество deprecated-методов и те подходы что были каноническими 3-4 года назад сейчас уже «фу-фу» и «не надо так!».
На то, чтобы обновить значение кастомного поля в задаче и подвинуть её статус, мне пришлось гуглить и экспериментировать минут 30.
Разработка ещё осложняется тем, что быстро из коробки получить нормальную локальную среду с autocomplete'ом и подсказками по коду без знания «что качать», «откуда взять библиотеку классов JIRA» и «куда её в этой среде подпихнуть чтобы она подхватилась» не получается. И в итоге ты как слепой котенок — рыскаешь рецепты в сети, пробуешь, спотыкаешься и понимаешь, что рецепт уже не актуален т.к. методы deprecated или выпилены и идёшь на новый круг.

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

Процесс погружения в JAVA/JIRA достаточно трудоёмкий, и если само написание плагина действительно может занимать час-два, то вместе со знакомством с языком, с доступной библиотекой классов, с инструментами, с процессом отладки и подготовкой окружения это может растянуться на неделю.
Лично мне плагины писать не довелось, хотя возможно кто-то этим в компании и занимался, так что не готов однозначно ответить. В большинстве случаев нам хватает Adaptavist ScriptRunner'а (он очень много плюшек даёт), WebHook'ов, настроек переходов Workflow и кастомных полей.

Про капчу — мы сами от этого периодически страдаем, так что я нормального способа не знаю :(
К сожалению JIRA не даёт возможность сказать про пользователя «да, это робот, я знаю, пусти!». Последний раз когда я проверял информацию про настройки капчи — её можно было либо выключить совсем для всех, либо для всех использовать. Мы предпочли её оставить.

Так что на капчу мы наступаем при каждом рестарте nginx, который живёт над нашей JIRA: соединения между nginx и JIRA рвутся и та вспоминает что AIDA — робот.

Единственное решение, которое мы для этого нашли — адекватно ругаться в exception о том, что мы схлопотали капчу:
throw new \Badoo\Jira\REST\Exception\Authorization(
        "Access to the API method is forbidden. You either have not enough privileges or the capcha shown to your user"
);

Это позволяет быстрее понять, почему именно отваливаются скрипты, зайти в JIRA и ввести капчу ручками, чтобы разблокировать пользователя.

P.S.: если вы ходите в JIRA напрямую, то возможно добавив nginx между вашим клиентом и JIRA вы уменьшите количество показываний капчи. Судя по тому что мы наблюдаем, JIRA не трогает роботов, если они ходят через уже существующие соединения. Так что nginx с keepalive-соединениями к upstream'ам могжет сильно облегчить боль. По сути, мимо nginx'а-то мы никогда и не ходили, так что возможно причина не в этом. Но то, что мы ловим капчу при каждом рестарте nginx'а заставляет меня думать что всё именно так работает.
Я натыкался на него перед публикацией, да. Использовать не довелось, так что могу провести только поверхностную оценку. Для нормального сравнения всё-таки стоит попользоваться обоими клиентами в реальных условиях.

Насколько я вижу из документации к клиенту — lesstif/php-jira-rest-client это только та часть нашего клиента, которая в \Badoo\Jira\REST\Client. Хотя lesstif/php-jira-rest-client покрывает API JIRA интерейсами лучше нас. Например, для того, чтобы создать новый проект через API, у нас придётся пользоваться \Badoo\Jira\REST\ClientRaw, а в клиенте lesstif есть для этого удобная обвязка.

Но помимо самого клиента к API мы предоставляем набор ActiveRecord классов, которые умеют прозрачно догружать данные из API при необходимости и обновлять их в JIRA. + каждое кастомное поле в JIRA можно представить отдельным классом и создавать такие классы с помощью генератора PHP-кода, так же поставляемого с клиентом.

Повторюсь, это поверхностная оценка, т.к. я не использовал lesstif/php-jira-rest-client, но пока мне кажется, что опубликованный нами клиент — это часть lesstif/php-jira-rest-client + куча дополнительных обвязок, которых в lesstif нету и которые делают использование API сильно удобнее.
И гит или не гит, с кластером или без, где и что кешировать, чтобы нужные версии кода быстро подсунуть новому контейнеру на конкретном сервере — это вопрос транспортной инфраструктуры, а не продакшен среды.


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

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


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

Если данные (код) не пихать в docker, то по сути нет большой разницы: с доставкой кода средствами MDK, FPM не будет нужды перезапускать ни в виде контейнера, ни в виде сервиса напрямую в системе. Если код пихать в контейнер — то опять вылезает задача эффективной доставки всего этого добра с новой версией на производство: вам либо надо тащить из-за пары файлов всё, либо как-то выкручиваться.
По сути, MDK очень похож на git в смысле «git pull и всё». Так что с этой точки зрения — мы ровно так и деплоимся. К сожалению git — инструмент не для деплоя. Он прост и удобен и хорошо работает на малых масштабах, но pull — достаточно прожорливая операция. 100-200 параллельных git pull и сервер задыхается. Да, можно заткнуть эту проблему железом — поставить сервер «пожирнее», потом, когда его не станет хватать — сделать несколько «реплик» git-репозиториев на разных серверах и выкладывать кластерами, каждый из которых будет смотреть в свою реплику. Но этим тоже нужно управлять: следить за тем, что свежая версия выехала на все реплики, что кластер ходит в свою реплику и может сходить в соседнюю при недоступности своей и т.п.

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

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

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

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

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

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Works in
Registered
Activity