Наверх ▲

SkyTools: помощь в вопросах масштабирования

Артем Носов Артем Носов Системный архитектор проектов в компании "МирТесен".

Артем Носов: Добрый вечер! Меня зовут Артем Носов. Я представляю вашему вниманию доклад «SkyTools: помощь в вопросах масштабирования». Начало проекту SkyTools было положено в компании Skype в 2006-м году. Вдохновил создателей SkyTools проект Slony. На 10-й конференции, посвященной PostgreSQL, компания Skype анонсировала свой продукт. В 2007-м году продукт был выложен в открытый доступ.

 

В своем докладе я рассмотрю следующие вопросы. 

• SkyTools: готовые решения от Skype. 

• Основы очередей PgQ. Рассмотрю такие основные понятия, как очередь, поставщик и потребитель.

• Репликация londiste.

• Проблемы масштабирования, с которыми мы столкнулись, используя SkyTools 2. Также в этой части будут приведены примеры практического использования фреймфорка PgQ.

• Светлое будущее, которое практически уже наступило.

Проект SkyTools включает в себя 3 основных компонента. 

• Фреймворк PgQ, который представляет собой очередь FIFO. Он реализован в рамках реляционной базы данных PostgreSQL.

• Следующий компонент – это приложение londiste, которое базируется на этом фреймворке. Как утверждает Skype, это первое приложение, которое использовало фреймворк PgQ.

• WalManager. WalManager позволяет реализовать репликацию PostgreSQL, основанную на ее wal-файлах.

Применение PgQ

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

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

Сначала мы начали использовать PgQ в менее значимых задачах – например, для оповещения пользователей о новых комментариях или сообщениях на стене. Когда мы полностью изучили возможности фреймворка и прониклись ими, он начал появляться в самых ответственных задачах. Например, таких, как списание у пользователей абонентской платы за какие-то регулярные события. Это регистрация пользователей, подтверждение регистрации. 

Важнейшее преимущество PgQ – это обработка группы событий. Приложение получает не единичное событие, а сразу несколько событий. 

Рассмотрим такой пример. У нас появилась какая-то активная тема. Пользователи активно ее минусуют или плюсуют. Что мы вынуждены были бы сделать в общем случае? На каждое действие пользователя мы делали бы соответствующее обновление в базе данных. При использовании PgQ мы получаем группу событий, высчитываем некоторую дельту плюсов и минусов и делаем единичное обновление в базе данных. Тем самым мы экономим ресурсы нашей базы данных. 

Репликация основана на londiste-принципе, она не несет в себе ничего нового. Тем не менее, она проста в использовании и сводится буквально к выполнению трех команд на ведущем и ведомом серверах. К ее плюсам можно отнести возможность репликации отдельных таблиц в базе данных. Первоначальное копирование таблицы не блокирует ведущий сервер.

Что же "под капотом" у фреймворка PgQ?

PgQ представляет собой набор таблиц. Весь фреймворк PgQ реализован на языке PG/PL SQL. Внутри для каждой очереди создается 3 таблицы, в которые поступают новые события и которые определенным образом ротируются. 

Для разработчика фреймворк выглядит следующим образом. У нас есть некоторая очередь (англ. queue) в базе данных, cозданная штатными средствами SkyTools либо какими-то прикладными привязками (англ. binding),которые реализованы в рамках этого фреймворка. У нас есть поставщик событий. Типичный поставщик событий – это веб-клиент. Пользователь что-то плюсует или минусует. В нашу очередь, соответственно, попадают события. Событие попало в очередь, оно записалось. 

Следующая задача – это появление "потребителя" (англ. consumer). Первой задачей "потребителя" является подписка на очередь. Только после того как он подписан, он начинает получать события из этой очереди. События, которые поступили в очередь до его подписки, он не получит. "Потребитель" подписался, и после этого он с некоторым интервалом он пулит очередь для получения из нее событий. Важное замечание: фреймворк PgQ отдает не единичные события, а группирует их пакетами (англ. batch).

Поясню на том примере, который уже был рассмотрен. Пользователь плюсует некий контент на сайте. Что происходит в этом случае? (Это реальная система, которая используется у нас.) Событие попадает в очередь "activity queue", записывается. На эту очередь у нас подписано два "потребителя": "Rating Consumer" и "Activity Consumer". 

Важная особенность PgQ заключается в том, что каждый "потребитель" получит свою независимую копию событий. Rating Consumer забирает события фактически в реальном времени, проверяет: «Ага, у темы был рейтинг 10, теперь рейтинг 11». Он записывает эту информацию в Comet и в Memcached. Comet быстро отдает эти сообщения пользователю, и у него появляется всплывающее окно: «+10 к вашему рейтингу». Запись в Memcached производится для того, чтобы при следующем обращении пользователя на эту страницу он увидел актуальный рейтинг (11).

Activity Consumer работает, допустим, раз в 10 минут. Он получает соответствующие накопившиеся пакеты в очереди, считает их и записывает в таблицу User Stats для долгосрочного хранения.

Сильные стороны PgQ 

• Поддержка транзакций. Так как все организовано в рамках PostgreSQL, все события записываются транзакционно, и вся обработка происходят транзакционно. Соответственно, исключена вероятность потери события.

• Эффективная обработка событий. Так как забираются пакеты событий, а не единичные события, "стоимость" обработки единичного события сведена к минимуму.

• У нас нет ограничений на количество "поставщиков" и "потребителей". Мы можем сделать 100 очередей, на каждую очередь будет по одному "потребителю". Мы можем сделать 100 очередей, на каждую из них зарегистрировать по 100 "потребителей".

• Надежность. Мы получаем логирование wal и Crash Recovery в случае сбоев. Эта функциональность проверена годами, и, в принципе, никаких нареканий ни у кого не вызывала.

• Простота использования.

• Разумеется, это Open Source. В частности, Open Source нам очень помог. На следующем слайде пойдет речь о специальном демоне PgQ Ticker. Когда у нас количество очередей возросло, ticker выполнял неэффективный запрос. Максим Богук оптимизировал этот запрос, я хотел бы его поблагодарить. Мы отправили наши наработки в Skype, и в следующем релизе ticker уже использовал полноценный эффективный запрос.

Что такое Ticker?

Это специальный процесс, в задачи которого входит "нарезка" временных отметок на временной прямой поступающих событий. У нас есть непрерывно поступающие события. Что входит в задачу процесса ticker? Запуск новых пакетов. Работает он по некоторому алгоритму, о котором мы тоже упомянем дальше. 

Почему этот процесс так важен? Если у нас нет процесса ticker, события у нас поступают, записываются (гарантированно записываются). Однако у нас не "нарезаются" пакеты, а "потребители" получают события исключительно пакетами. Если их нет, "потребители" работают, но ничего не обрабатывают. 

Тут мы понимаем, что есть проблемы. Запускаем ticker. Первым делом он ставит отметки о начале нового пакета. Замечательно. Проставил. "Потребители" проверяют соответствующие очереди и "видят": появился новый пакет. Надо его обрабатывать. 

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

Ключевое правило, которое из этого следует: процесс ticker надо всегда держать запущенным. Это очень важный процесс, его нужно всегда мониторить и держать руку на пульсе.

Параметры очереди можно выставить как глобально, так и для каждой очереди в отдельности. Здесь представлено 4 параметра. Рассматривать мы будем, в основном, два из них. 

Ticker_max_lag. Грубо говоря, мы выставим для определенной очереди ticker_max_lag в 2 минуты, и у нас каждые 2 минуты будет "нарезаться" очередной пакет.

Ticker_max_count. Это максимальное количество событий в пакете. Поставим 500 – больше пятиста событий в пакете мы не получим. 

Почему эти параметры важны? Например, задав максимальные значения, мы можем увеличить эффективность. У нас будет поступать больше событий. Мы будем считать дельту более эффективно, делать меньше обновлений. 

Но тут есть и свои тонкости. Рассмотрим пример с регистрацией пользователя. Он зарегистрировался, у нас поступило событие. Если у нас ticker_max_count стоит 500, соответственно, пока у нас 500 пользователей не зарегистрируется, подтверждение о регистрации пользователь не получит. Нужно знать о таких нюансах и для каждой очереди настраивать параметры по-своему. Это влияет как на производительность, так и на время ожидания пользователя.

Londiste

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

У нас есть ведущий (master), у нас есть ведомый (slave). Мы хотим реплицировать таблицу Source table. Мы выполняем из консоли одну команду, сообщаем: «Добавить Source table в репликацию». Что в этом случае происходит? Происходит следующее. На эту таблицу "вешается" триггер sqltriga. 

В задачу этого триггера входит следующее. Он URL инкодит все обновления и вставляет их в очередь (queue). Вставляет их в очередь, все записывается. Теперь у нас вступает в работу londiste-репликация. По сути, это обычный "потребитель", который пакетами получает события из очереди, декодирует их и осуществляет вставку в таблицу target table.

Как уже говорилось, репликация хороша тем, что можно реплицировать отдельные таблицы, и на время первоначального копирования таблицы не блокируется master-таблица.

С какими проблемами мы столкнулись?

Основная проблема такая. Допустим, у нас есть одна очередь. На нее зарегистрирован один "потребитель". Замечательно. Сейчас у нас сайт посещает 10 тысяч человек, все справляется. Вдруг посещаемость резко возрастает, в очередь поступает колоссальное количество событий, "потребитель" перестает справляться. 

Что предоставляет нам фреймворк PgQ? Собственно, ничего. Хотите – пожалуйста, увеличивайте производительность вашей машины. Распараллеливания очереди как такового нет. Мы можем зарегистрировать на очередь 10 "потребителей", однако каждый из них будет получать свою копию событий, совершенно не связанную с другим. Мы можем реализовать какой-то хитрый менеджмент, какие-то блокировки. Один обработал одно событие, другой обработал другое событие. Однако нам этот подход не понравился.

Существует ограничение репликации londiste. Оно заключается в следующем. Если мы используем фреймворк PgQ и репликацию londiste, то мы не можем реализовать hot stand by сервер. Почему? Потому что londiste не реплицирует схему PgQ, он реплицирует только таблицы. 

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

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

Как мы решили проблему масштабирования?

Мы решили ее следующим образом. Вот пример. У нас есть пользователь. У него есть популярный сайт (допустим, миллион зарегистрировавшихся на нем посетителей). Он создает какую-то новую тему и думает: «Дай-ка я уведомлю всех своих пользователей об этой теме». Замечательно. Жмет кнопку «уведомить о теме» – у нас происходит вставка одного события в очередь "request queue". Событие вставилось, пользователям вернули управление – он счастлив.

Дальше в игру вступает "request consumer". Он заходит в очередь "request queue" и видит: «Ага, одно событие». "Потребитель" забирает это событие, делает выборку из базы данных и видит: «Господи, да там же миллион пользователей!» 

Что он делает? Он вставляет 10 тысяч событий в некоторую виртуальную таблицу notification. Почему по 10 тысяч событий? Если у нас есть два сайта по миллиону пользователей. Если один пользователь нажал – и мы сразу вставим миллион событий, то второй попросту будет ждать завершения отправки всех событий его конкурента. Поэтому мы вставляем по 10 тысяч событий. Тем самым мы реализуем некоторое планирование.

Мы вставили 10 тысяч событий в таблицу notification. Что происходит дальше?

На таблице notification "висит" триггер. В задачу этого триггера входит следующее. Он по некоторому алгоритму вставляет в 100 очередей по 100 событий. Вставил 100 событий. Дальше каждую очередь обрабатывает соответствующий "потребитель". Получается 100 "потребителей".

Когда мы начинали проект, нам хватало десяти "потребителей" для обработки. Проект рос, росла посещаемость. Мы поняли, что десяти очередей нам недостаточно. Добавили 100 очередей. Появилась следующая задача: 100 "потребителей" на одной машине существовать не могут. Мы добавили еще одну машину, у нас стало по 50 "потребителей" на каждой машине. 

На текущий момент у нас порядка 180-ти "потребителей". Соответственно, их обслуживают 3 машины. В пиках они отправляют порядка 30-ти миллионов писем. 

Поскольку все у нас проходит за кадром (где-то что-то работает), очень важен мониторинг. Мониторинг можно реализовать как штатными средствами SQL (вызовом функции pgq.get_consumer_info), так и воспользовавшись консольной утилитой pgqadm, вызвав ее с параметром status.

Здесь мы вызвали функцию pgq.get_consumer_info и получили следующее. Получается, есть 10 очередей. К сожалению, не выведена информация о "потребителях". Соответственно, на каждую очередь у нас по одному "потребителю". 

Какая важная информация видна на этом слайде? Это lag. Эта информация говорит нам о том, к какому последнему пакету приступил "потребитель". По сути, это его отставание от текущей обработки. Мы видим, что для первого "потребителя" отставание составляет 10 секунд.

Следующее поле – last_seen. Когда же "потребитель" приходил за последней информацией. Он приходил 2 секунды назад. 

На мой взгляд, целесообразно мониторить следующие параметры. Суммарный lag, который нужно выводить графически, чтобы девочки-операторы могли на него смотреть. По каждой очереди нужно обязательно настроить соответствующее оповещение. Одна очередь более критична, по другой мы можем, наоборот, больше отставать. Last_seen целесообразно мониторить, поскольку он нужен для диагностики работы консоли.

Как обычно, когда "потребитель" начинает отставать, возникает вопрос: «Когда же все наладится?». По информации из предыдущего слайда, посмотрев на lag, мы не можем сказать, что "потребитель" справится за два часа, потому что у него lag 2 часа. Это не так. 

Здесь представлена временная шкала. Взят некоторый интервал с 08:00 до 20:00 и количество поступающих событий. Есть некоторая очередь, которая настроена так, что события формируются в ней раз в час. Соответственно, в 8 часов у нас поступило 300 событий, в 9 часов поступило 700 событий.

Пример. "Потребитель" работает. 8 часов обработал нормально, 9 часов – нормально, 10 часов. Дошел до 11-ти часов и начал "буксовать". База данных нагрузилась, обновления занимают у него много времени. Время идет. 12 часов, 13 часов. Получается отставание в 2 часа. Однако, как мы видим, событий там достаточно много.

Время идет дальше – 13, 14. Тут "потребитель" у нас "очнулся" и обработал 11 и 12 часов, остановился на 13-ти. По сути, получается отставание его тоже 2 часа. Однако эти 2 часа он обработает за существенно меньшее время (потому что событий меньше), чем предыдущие. 

Lag говорит нам о чем-то, но именно для ответа, через сколько все наладится, нужно самому идти в базу данных и искать ту таблицу, в которой эта куча событий набралась. Придется смотреть количество событий и оценивать, когда "потребитель" справится с их обработкой.

Мы использовали SkyTools 2. Уже скоро выйдет SkyTools 3, релиз один. Как шутят разработчики, релиз один, потому что к нему нет документации. В целом он готов.

SkyTools 3 выглядит очень аппетитно. В нем появились "потребители" типа "cooperative consumer". Ту задачу, которую мы решали путем создания ста очередей, теперь решается с их помощью. 

В SkyTools появился pgqd. Он полностью заменяет ticker. Как уже говорилось, ограничение ticker в том, что для каждой базы данных, которая использует PgQ фреймворк, нужно запускать отдельный ticker. Pgqd будет единым для целого экземпляра.

В Londiste появилось Parallel COPY. Теперь, скорее всего, снимется то ограничение, которое было (в 512 килобайт). Londiste будет параллельно копировать таблицы.

Вот и все, жду ваших вопросов.

Вопросы и ответы

Вопрос из зала: Какой проект?

Артем Носов: Проект «Мир тесен».

Вопрос из зала: В первоначальной конфигурации нет таких проблем?

Артем Носов: С первоначальной конфигурацией SkyTools в данном случае очень надежен и очень прост в использовании. За 3 года его использования у нас с ним каких-то существенных проблем не возникало.

Вопрос из зала: События, связанные с деньгами, как-то защищены от "падений"?

Артем Носов: Допустим, мы получили пакет, в нем есть 100 событий, которые как раз связаны с деньгами.

Реплика из зала: Потом демон упал, "потребитель" упал.

Артем Носов: Да. "Потребитель" упал. Но дело в том, что мы транзакцией делаем обновление. Соответственно, у нас просто будет callback, обратный вызов. Следующий момент. Допустим, мы отправляем уведомление по почте. Получили тысячу событий, сформировали 10 конвертов, и тут сервер упал. В PgQ это реализовано следующим образом. Мы просто делаем retry этим событиям. Более того, можно retry сделать… 

У нас биллинг реализован следующим образом: есть ежемесячное списание. Приходит событие, мы его обрабатываем и говорим: «Retry, вернись через месяц». Оно возвращается через месяц, мы его опять обрабатываем и даем команду "retry". Так происходит ежемесячное списание денежных средств.

Вопрос из зала: Каждое событие вы опять записываете в базу?

Артем Носов: Да-да-да.

Реплика из зала: Если он потом пришел, смотрит, что оно было обработано, и проблем не возникает.

Артем Носов: Мы сказали фреймворку PgQ: «Возврати это событие через месяц», - и он сам вернет это событие через месяц.

Вопрос из зала: Нет. Если не сказали, что будет? Обработали тысячу событий, чтобы пакет "зафинишить", а вы "упали". Как такие проблемы решаете?

Артем Носов: Я же говорю, в данном случае все решается в рамках транзакции. 

Вопрос из зала: Вы все в одной базе делаете?

Артем Носов: Да, да. В основном, это делается все в одной базе. Даже если это в разных базах делается, то… А вы имеете в виду…

Вопрос из зала: Пакет "зафинишить" надо в конце. Этот "финиш" выполняется всеми операциями, которые идут по обработке всех событий.

Артем Носов: Так, по идее, можно, наверное, двойную транзакцию использовать TPC.

Вопрос из зала: Если критичные данные с деньгами, вы их как-то обрабатываете? Один и тот же пишется несколько раз.

Артем Носов: Данные с деньгами у нас обрабатываются в рамках одной базы данных.

Вопрос из зала: Кластеризуется ли данное решение? Какую типовую нагрузку он держит?

Артем Носов: Как я уже говорил, мы начинали с десяти очередей, конкретно по этому функционалу. Сейчас мы доросли до 180-ти очередей. Если мы видим, что этих очередей не хватает, мы меняем триггер, указываем ему: «Создай-ка ты нам 300 очередей». Регистрируем соответствующие "потребители" и добавляем машину, если она требуется. На данный момент получается линейное масштабирование. Пока проблем у нас не было.

Вопрос из зала: Машины как добавляются?

Артем Носов: Физически. "Потребитель" – это просто прикладное приложение, которое…

Вопрос из зала: Я имею в виду, сам PgQ масштабируется?

Артем Носов: Как я уже сказал, он не масштабируется в данном случае. В третьей версии появились "потребители" типа cooperative consumers, которые можно использовать. Более того, решение, которое предложили мы, мы обсуждали в свое время со Skype. Они одобрили его и сказали, что, может быть, даже будут сами его использовать.

Вопрос из зала: Сколько сообщений в секунду можно положить в очередь или вообще во фреймворк?

Артем Носов: Тут будет зависеть от ваших настроек Postgres, от вашего оборудования. Если у вас все на двух SATA-дисках, вы будете "упираться" в возможности дисков. Вы работаете, как с обычной базой данных. Единственное, у нее меньший оверхед, поскольку добавление событий – это достаточно "дешевая" операция. Она всегда делается с помощью Insert.

Вопрос из зала: Еще один маленький вопрос. Как быстро "потребитель" может получить сообщение? Я так понимаю, там некий ticker, он должен создать пакет. Как я понял, он должен его создать, даже если установлено ограничение max_count в единицу. Если я хочу одно сообщение сразу получить, вопрос: как быстро я могу его получить из очереди?

Артем Носов: Если вы хотите получать одно событие, вы настраиваете ticker_max_count в единичку. Получение вами событий из очереди будет уже зависеть от того, насколько часто "потребитель" обращается к данной очереди. Если он обращается раз в 10 секунд, соответственно, максимальное обращение будет 10 секунд. Если вы настроили обращаться часто, раз в секунду, максимумом будет 1 секунда. 

Вопрос из зала: В Postgres появился "payload", можно тоже прокачивать какие-то события от одной сессии к другой сессии. Не смотрели?

Артем Носов: Нет, мы пока его не смотрели. Сейчас мы планируем переход на 9.1, как мы использовали londiste, его сценарии использования. Мы переходили с 8.3 на 8.4, на major-версии, без простоя, с 8.4 на 9.0. Сейчас в планах переход с 9.0 на 9.1 с использованием londiste. Нареканий у нас не было.

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Артём Гавриченков

Артём Гавриченков

Ведущий разработчик в компании "Highload Lab".

В докладе описаны типичные ошибки программирования при написании серверных приложений на основе TCP-сокетов.

Сергей Аверин

Сергей Аверин

Руковожу разработкой проектов в Баду. Программирую server-side. Конференционный маньяк. Среди проектов, которые я делал — Хабрахабр, dirty.ru, trendclub.ru. Специализируюсь на больших/сложных веб-проектах.

twitter: twitter.com/ryba_xek
facebook: facebook.com/ryba.xek

Сергей Аверин (Badoo) рассказывает о постоянных соединениях (pconnect) и проблемах их внедрения.

Александр Якима

Александр Якима

Консультант по Lean и Agile методологиям, в настоящее время работает с командами в Индии и США, а также в Украине и России.

Мы поговорим о командных практиках, которые переносятся с масштаба одной командочки на масштаб всей программы, о распределенности. В том числе поговорим немножко об аутсорсинге и "Product Ownership", который обычно является ключевой проблемой и о многом другом.