Brubeck — быстрый, statsd-совместимый агрегатор метрик от GitHub
История появления
Одной из главных целей команды разработчиков GitHub всегда была высокая производительность. У них даже существует поговорка: «it’s not fully shipped until it’s fast» (продукт считается готовым только тогда, когда он работает быстро). А как понять, что что-то работает быстро или медленно? Нужно мерять. Измерять правильно, измерять надёжно, измерять всегда. Нужно следить за измерениями, визуализировать всевозможные метрики, держать руку на пульсе, особенно, когда дело имеешь с высоконагруженными онлайн системами, такими как GitHub. Поэтому метрики — это инструмент, позволяющий команде предоставлять столь быстрые и доступные сервисы, почти без даунтаймов.
В своё время GitHub одними из первых внедрили у себя инструмент под названием statsd от разработчиков из Etsy. statsd — это агрегатор метрик, написанный на Node.js. Его суть состояла в том, чтобы собирать всевозможные метрики и агрегировать их в сервере, для последующего сохранения в любом формате, например, в Graphite в виде данных на графике. statsd — это хороший инструмент, построенный на UDP сокетах, удобный в использовании как на основном Rails приложении, так и для сбора простейших метрик, наподобие вызова nc -u. Проблема с ним начала проявляться позже, по мере роста количества серверов и метрик, отправляемых в statsd.
Так, например, некоторые метрики показывались некорректно, а некоторые, в особенности новые, вообще не собирались. Виной тому были почти 40%-ые потери UDP пакетов, которые просто не успевали обработаться и отбрасывались. Природа однопоточного Node.js с использованием единственного UDP сокета дала о себе знать.
Но масштабировать было не так просто. Для того, чтобы распределить сбор и обработку пакетов по нескольким серверам, нужно было шардировать не по IP, а по самим метрикам, иначе бы на каждом сервере был свой набор данных для всех метрик. А задача шардирования по метрикам непростая, для её решения GitHub написал свой парсер UDP пакетов и балансировку по названию метрики.
Это сгладило ситуацию, позволило увеличить количество инстансов statsd до четырёх, но являлось полумерой:
4 сервера statsd, еле собирающих метрики, плюс самописный балансировщик нагрузки, занимающийся парсингом UDP пакетов, в итоге вынудил переписать всё более правильно, на чистом С, с нуля, сохранив обратную совместимость. Так появися Brubeck.
Brubeck
Но переписывание Node.js приложения (event-loop, написанный на С на основе libuv) на чистый С, с использованием того же самого libuv — сомнительное занятие. Поэтому решено было пересмотреть саму архитектуру приложения.
Во-первых, отказались от event-loop на сокете. Действительно, когда в тебя льётся 4 миллиона пакетов в секунду, нет смысла каждый раз крутиться в цикле и спрашивать, не появились ли новые данные для чтения, так как, скорее всего, они там уже появились, и не одни :)
Event-loop заменили на пул потоков воркеров, использующих один общий сокет с сериализацией доступа к нему. Позже, механизм улучшили ещё, добавив поддержку SO_REUSEPORT для сокетов из linux 3.9, что позволило отказаться от сериализации доступа воркеров к сокету в самом агрегаторе. (прим. по этой теме интересно будет почитать статью как nginx внедрил поддержку SO_REUSEPORT).
Во-вторых, наличие нескольких потоков, работающих с одними и теми же метриками, означает, что у нас разделение данных. Для безопасного доступа к разделяемым данным необходим механизм синхронизации доступа, например, локи (lock), что не есть хорошо в условиях высокой конкуренции за доступ к данным и при необходимости высокой производительности. На помощь приходят lock-free алгоритмы, в частности, lock-free реализация хеш-таблицы, в которой хранятся метрики. (на самом деле там lock-free только на чтение, а на запись optimistic locking, но это не страшно для приложений с высоким reads-to-writes rate, т.к. метрики добавляются и удаляются гораздо реже, чем приходят в них сами данные).
В-третьих, агрегация данных внутри одной метрики синхронизировалась через spinlock — крайне дешёвый механизм в плане затратов ресурсов CPU и переключения контекстов, что так же не вызвало затруднений, т.к. борьбы за данные внутри одной метрики почти не было.
Результат
Простая многопоточная архитектура агрегатора позволила добиться неплохих результатов: на протяжении последних двух лет единственный сервер с Brubeck дорос до обработки 4.3 миллиона метрик в секунду, без потери пакетов даже в пиковой нагрузке. Вся инфа и данные достоверно взяты с блога разработчиков.
Brubeck был выложен в open-source: github.com/github/brubeck
В нём уже есть многое из statsd, но ещё не всё. На данный момент разработка ведётся активно, сообщество находит баги и быстро исправляет.