Web, кэширование и memcached (выступление на HighLoad++ 2008)

Итак, HighLoad++ состоялся. Если говорить кратко, конференция мне понравилась. Ниже мои личные впечатления о конференции, краткие тезисы доклада и презентация.

Текст доклада дописываю, есть мечта к концу недели это доделать (сейчас готова ровно половина). Тогда же текст опубликую, возможно в серии отдельных постов и в виде одной большой PDF-ки тут.

Мои впечатления о конференции

Итак, мне понравилось. Интересные доклады - много интересных докладов. Жалко, что не было

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

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

Краткие тезисы

Требуемое время: 50 минут

Докладчик: Андрей Смирнов

Цель доклада – рассказать о проблемах кеширования в распределенных высоконагруженных проектах и о возможных путях решения этой проблемы. Предполагаемый уровень подготовки аудитории – начинающий++.

Современный высоконагруженный проект может использовать десятки гигабайт распределенной памяти, используемой под кеш, организованной в виде кластера memcached-серверов. Зачем нужен memcached? Как работать с таким хранилищем, как распределить ключи по элементам кластера? Как назвать ключ, соответствующий кешу? Как обеспечить атомарность операций, “блокировки”?

Как эффективно использовать такое хранилище? Как исключить возможность одновременного построения “тяжелых” кешей разными мордами? Как сбросить одновременно группу кешей? Как отлаживать (собирать статистику) о кешировании? Как работает slab-аллокатор? Для чего еще может быть полезен memcached в веб-проекте?

Видео с конференции

Презентация

Полный текст доклада

Материалы

  • Тезисы (RTF)
  • Презентация (PowerPoint)
  • Полный текст доклада (PDF)

Анонс: выступление на HighLoad++ (2008)

6-7 октября в Москве пройдет конференция HighLoad++. На этой конференции я представлю доклад на тему "Web, кеширование и memcached" (текущая программа конференции).

Краткие тезисы доклада привожу ниже:

Цель доклада – рассказать о проблемах кеширования в распределенных высоконагруженных проектах и о возможных путях решения этой проблемы. Предполагаемый уровень подготовки аудитории – начинающий++.

UPD: доклад будет во второй день, 7 октября, 17:20-18:10, второй зал.

Современный высоконагруженный проект может использовать десятки гигабайт распределенной памяти, используемой под кеш, организованной в виде кластера memcached-серверов. Зачем нужен memcached? Как работать с таким хранилищем, как распределить ключи по элементам кластера? Как назвать ключ, соответствующий кешу? Как обеспечить атомарность операций, “блокировки”?

Как эффективно использовать такое хранилище? Как исключить возможность одновременного построения “тяжелых” кешей разными мордами? Как сбросить одновременно группу кешей? Как отлаживать (собирать статистику) о кешировании? Как работает slab-аллокатор? Для чего еще может быть полезен memcached в веб-проекте?

Выступление на РИТ: Высокие нагрузки (2008)

22-23 сентября в Москве состоялась конференция РИТ: Высокие нагрузки. На ней я представил доклад "Доставка видеоконтента пользователям" (доклад строился на нашем опыте в NetStream по проектированию Smotri.Com).

Ниже вы найдете краткие тезисы доклады, полные тезисы, презентацию, а также видео с конференции.

Краткие тезисы

Цель доклада: познакомить аудиторию с возможными способами организации отдачи статического контента (видеофайлы) и «живых» потоков вещания в условиях высокой нагрузки и географической распределенности.

Аудитория доклада: уровень — средний, может быть интересно широкому классу специалистов.

Постановка проблемы:

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

Основные вопросы:

  1. Организация файлового хранилища: доступ к серверам, мониторинг, бэкап данных.

  2. Настройка сервера для FLV-стриминга.

  3. Географическая распределенность (зеркала) «своими руками»: концепция и её реализация.

  4. Применение географической распределенности к файлам видео.

  5. Организация вещаний (трансляций), вещания с большим числом зрителей, ретрансляция. Оптимизация сервера вещаний.

  6. Применение географической распределенности к вещаниям.

Оценка значимости и области применимости:

Данные вопросы интересны как для видеохостинга, так и для произвольных вариантов вещаний через Internet (например, групповые видеочаты). Схожие проблемы могут возникать у любых ресурсов, имеющих контент довольно большого размера и требующих «быстрой» отдачи их пользователям (например, файлохранилища).

Описываемое решение не является полностью уникальным, существуют другие более общие решения для части проблем, например, для файлов это GFS, концепция CDN и т.п. Для вещаний, насколько я знаю, подобные вопросы обсуждаются впервые.

Видео с конференции

Презентация

Полный текст доклада

Доставка видеоконтента пользователям

Материалы

Лог комита: зачем он нужен на самом деле?

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

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

  • "фикс бага";
  • "закомитил всё, что сделал";
  • "тестовый комит";
  • "исправил опечатку";
  • и т.п.

Почему это плохо?

Один ответ на этот вопрос заключается в том, что лог комита является документацией на код и на изменение. Идеальный лог комита должен позволять по истории изменений конкретного файла понять, что произошло в каждой ревизии, не заглядывая в само изменение (дифф файла). Лог комита может содержать дополнительную мета-информацию: номер тикета, имя человека, который провел code-review, ссылки на другие комиты и т.п.

Пытаясь удовлетворить приведенным выше требованиям разработчик часто возьмет заголовок тикета и впишет его в лог комита. Однако, чаще всего тикет формулируется в терминах не разработки, а исходной задачи, например: "Сделать отступ 10 пикселей между блоками А и Б". Такая фраза в качестве лога комита в CSS-файл сообщает информацию о изменении, но какую информацию она дает читающему такой лог? Ведь на самом деле, например, был создан новый CSS-класс с именем таким-то, из другого CSS-класса был удален какой-то стиль и т.п. То есть само по себе изменение описывается на другом языке, не на языке менеджера проекта. Было бы полезно иметь и то, и другое представление комита, например:

Сделать отступ 10 пикселей между блоками А и Б: был 
создан новый класс blockC, из класса blockA удалено 
padding-bottom: 0.5em, т.к. все paddingи перенесены в 
класс blockC.

Тикет: #1734

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

Что нового дает такой лог комита по сравнению с логом: "закомитил CSS-фикс"?

Мне кажется, основное преимущество состоит в вербализации изменения. И оно даже не для читающего этот лог когда-то, а для пишущего. Процесс программирования сегодня особенно в web-разработке очень быстрый. Мы не пишем псевдо-код, редко рисуем диаграммы, часто между постановкой задачи и выкладкой нового кода на рабочие сервера может пройти несколько часов. За эти часы код не может пройти естественные этапы: отлежаться "пару дней", чтобы автор мог вернуться к нему с "незамутненным взглядом", не успеет пройти code-review, другие разработчики не успеют получить его из репозитория, и т.п. В то время как основная масса кода оказывается лучше изучена и протестирована за время от одного релиза до другого. Но и очень часто мы допускаем ошибки просто в силу того, что упускаем из вида важные детали из-за каких-то еще причин: отсутствия опыта, усталости, частого переключения между задачами и т.п. Как обеспечить качество разработки в таких условиях?

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

Код написан, и вот сейчас подошло время фиксировать изменение в репозитории, а значит, писать лог комита! И это самое время попробовать вербализировать своё изменение на естественном языке. Если ограничиться отпиской вида "фикс бага" или скопировать часть фразы из тикета, процесс вербализации не включается, т.к. мы ничего не формулируем. А давайте попробуем на хорошем русском языке сформулировать что же именно мы изменили, почему мы изменили, какие это имеет последствия (возможно) для другого кода? Для того, чтобы сформулировать точно, что же именно мы изменили, потребуется просмотреть дифф текущего изменения, что замечательно, это фиксирует внимание на каждом блоке изменений в попытке рассмотреть каждую строчку кода, резюмировать изменения общей идеей, не упуская при этом важных деталей. Этот дополнительный взгяд на дифф изменений позволяет найти как простые технические ошибки, как, например, отсутствие докблока, так и нетривиальные - забытые отладочные операторы, изменения, сделанные временно в процессе программирования, "меньше" вместо "больше" и т.п.

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

ffmpeg: "утечка" памяти (av_read_packet)

Может, это кому-то поможет сохранить пару часов отладки. Когда читаем видеопоток из файла, делаем что-то наподобие следующего:

AVPacket *m_pPacket = (AVPacket *)malloc(sizeof(AVPacket))
while(1)
{
     int ret = av_read_frame(formatCtx, m_pPacket);

     if (ret


AVPacket *m_pPacket = (AVPacket *)malloc(sizeof(AVPacket))
while(1)
{
     int ret = av_read_frame(formatCtx, m_pPacket);

     if (ret </p></body></html>

МТС, Евросеть и Билайн

04.07.08 (пятница)

Еду в машине, никого не трогаю. В телефоне симка МТС, которой уже много-много лет. Оператора и телефонный номер сто лет не менял, но последнее время напрягала сумма платежей МТСу (доходило до 2000 руб. каждый месяц при звонках внутри Москвы). Так вот, еду, в телефоне MIP живёт, аська зеленеет. Тут соединение рвётся и MIP делает тщетные попытки переподключиться. Ему это не удается ни за десять ни за двадцать минут. Телефон мой пишет "Нед доступа к сети", что звучит вполне как приговор. Подумал, что сота накрылась в том месте, где я ехал, но сеть не появилась и через час.

Звонок в МТС. После стандартных советов "включите-выключите телефон" следует вердикт: "Скорее всего, сим-карта сломалась". И предлагают "вставить её в другой телефон". Я один был, нет у меня другого телефона. Ну ладно, а где у вас ближайший центр обслуживания, чтобы съездить в субботу?

05.07.08 (суббота)


Лень ехать чинить МТСовскую симку, еду в ближайшую Евросеть - пять минут на машине, предварительно выбрав понравившийся тарифный план Билайн на их сайте. Почему не МТС - потому что было интересно попробовать что-то еще, а вдруг качественнее и дешевле? Отстоял очередь в Евросети плативших за телефон (свободных сотрудников нет), и получил ответ "к этому тарифному плану Билайн больше не подключает". Странно, ну ладно. Выбрал другой, предоплатный тариф. "У нас таких нет в продаже" - получил я ответ. Осталось только развернуться и уйти после такого желания осчастливить клиента контрактом ;)

В следующем торговом центре (еще 10 минут на машине) как назло оказалась опять Евросеть, в ней нашелся нужный тарифный план, и вот я счастливый обладатель Билайна. Приехав домой и разглядев коробку с симкой повнимательнее, осознал, что ей уже год (sic!). И хотя и называется тарифный план так, как я хотел - расценки совсем не те. Окей, звоню в Билайн. "Девушка, а сколько у меня стоит минута?" ... "6 руб." .. "А как мне перейти на новые расценки по этому тарифному плану?". Девушка присылает мне СМС с номером, звоню по нему, на всё соглашаюсь, получаю ответ: "Вы и так находитесь на этом тарифном плане". Хм. Звоню снова, жалуюсь, молодой человек через минуту отвечает: "А у вас контракт не зарегистирован, вот поэтому и нельзя сменить тарифный план. Через семь рабочих дней всё будет хорошо". Отлично, из-за этого у меня еще и не работает управление контрактом через Интернет.

06.07.08 (воскресенье)


За это время обнаружил, что в отличие от МТС и Мегафон, Билайн отлично ловится у меня в квартире, а (о чудо!) в GPRS входит телефон с первой попытки. Прощай МТС (за качество связи).

Проснулся, приходит СМС на Билайн: "Ваш номер заблокирован". Хм, интересно, а ведь вчера 500 рублей кинул и они дошли до счета. Звоню. Девушка выясняет всё, сама удивляется, разблокирует. Говорит что проблема в том, что "мой контракт не зарегистрирован". Кажется, из-за этого все беды.

Продолжаю наслаждаться Билайном и тишиной (новый мой номер почти никто не знает, а старая симка так и лежит мертвая).

07.07.08 (понедельник)


Привычное уже "ваш телефон заблокирован". Звоню в Билайн, шучу с девушкой-оператором, что буду ей каждое утро звонить ;) Разблокировали, и (о чудо!) он до сегодняшнего дня больше не блокировался (тьфу-тьфу).

Заказываю в МТС бесплатную доставку сим-карты рано утром. Вечером звонят из МТС, но не успеваю подобрать вызов, не перезванивают.

08.07.08 (вторник)


Звонили из МТС, обещали привезти симку, уточняли адрес, он из формы до них дошел в странном виде. Ждём...

09.07.08 (среда)


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

Вставляю симку в телефон - новое чудо "сим-карта не активна". Стоп-стоп-стоп. Звоню в МТС, получаю совет "включить и выключить телефон, подождать". (Видимо, забывают сказать, что надо еще с бубном потанцевать). Матерюсь, меняю симку в телефоне Билайн на МТС и обратно - не помогает. Звоню еще раз - получаю гениальный ответ... Я такого ожидал: "Курьер привез симку слишком быстро, она не успела активироваться!". Отлично, курьер видимо забыл "потупить", как его учили в МТС. Обещают, что заработает через сутки.

10.07.08 (четверг)


МТСовская симка так и не заработала... Снова звоню, жалуюсь... Оператор задумался надолго. Догадывается у меня спросить номер симки... Диктую ему и получаю еще один фантастический ответ: "Вам не эту симку должны были привезти!" Хм, я отвечаю, что другой нет, и она вот такая в красивом красном конверте (про мужиков на фотографии умолчал). Оператор подумал и обещал до вечера всё исправить.

Вечером всё заработало, итак, почти неделя без МТСа...

Кто-нибудь еще хочет пользоваться услугами наших сотовых операторов? А покупать что-либо в Евросети?

P.S. МТС нужен только для того, чтобы установить переадресацию с автоинформатором на Билайн.

Страуструп: наставления начинающему программисту

Прочитал интервью Бьерна Страуструпа для австралийского ComputerWorld. В этом интервью ему задают вопрос:

> Do you have any advice for up-and-coming programmers?

Мне кажется, ответ на данный вопрос формулирует то самое, к чему должен стремиться любой программист. Итак, далее мой вольный перевод на русский:

> Можете что-то посоветовать начинающим программистам?

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

Web, кеширование и memcached: часть 2

Начало серии постов здесь. Продолжаем разбираться с этим вопросом.

Проблема №3. Одного мемкеша мало


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

Самый простой алгоритм хеширования вида:

$memcache_server_id = crc32($key) % count($memcache_servers);

начинает "веселиться", когда обнаруживает, что сервер упал. Когда обнаруживается проблема сетевого доступа к серверу, драйвер memcachа убирает его из списка $memcache_servers. При этом count($memcache_servers) изменяется, какая-то часть ключей "сдвигается" на другие сервера, при этом "пропадает" куча кешей (хорошо, если кешей, которые не так жалко потерять, а если речь идёт о сессиях пользователей?).

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

Когда куча ключей теряется, резко начинают перестраиваться кеши, вырастает нагрузка на БД, и т.д., и т.п., то есть падение одного сервера мемкеша заваливает весь "отказоустойчивый" кластер.

Что делать? Есть другие алгоритмы хеширования ключей, устойчивые к удалению/добавлению серверов в пул. Для PHP-шного модуля Memcache это:

ini_set('memcache.hash_strategy', 'consistent');

Проблема №4. Одновременное перестроение кеша несколькими мордами


Если мы выставляем ключу (кешу) некоторое время жизни, рано или поздно, memcache скажет, что такого ключа на сервере нет. А если это "популярный" кеш, например, использующийся на главной странице, то такую ситуацию могут "обнаружить" одновременно несколько морд. И они все попытаются построить этот кеш, то есть они все одновременно отправят запрос в БД, то есть они отправят много запросов в БД, причём за короткий промежуток времени. Конечно, как только первый запрос завершится, морда запишет новое значение ключа и больше новых запросов за этой выборкой не будет. Но мы уже подвергли БД высокой нагрузке, хотя хотели этого избежать при помощи кеширования.

Напрашивается решение: пока одна морда строит кеш, все остальные должны её "подождать". Как это реализовать? Например, с помощью блокировок в том же мемкеше. Перед тем, как начать строить кеш для ключа mykey, проверяем, не выставлен ли уже ключ mykey_lock, если он выставлен, засыпаем и ждём некоторое время, что ключ исчезнет, если он исчез - пробуем снова прочитать mykey, если он появился (другая морда его построила), то берем готовое значение. Не появился за какой-то разумный промежуток времени - начинаем строить сами (значит, никто другой не смог построить значение этого кеша).

Перед тем, как сами начинаем строить кеш (например, если блокировки не было обнаружено), обязательно создаем блокировку: выставляем ключ блокировки mykey_lock, например, на 10 секунд, тем самым не подпуская другие морды к построению такой же выборки. Как выборку построили - записываем её в мемкеш, ключ блокировки удаляем.

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

Web, кеширование и memcached: часть 1

Все сегодня знают, что нагруженный веб-проект не может постоянно обращаться к БД с запросами, знают, что БД не выдержит. Все знают, что надо кешировать результаты. Можно кешировать сгенерированный HTML-контент, но лучше кешировать данные (результаты запросов), т.к. одни и те же данные могут отображаться разными способами (разные блоки, ответы по API, JSON, что-то еще).

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

Ну вот, мы всё замечательно закешировали, однако проблемы всё равно остались. Какие бывают проблемы и как с ними можно бороться? В серии постов на эту тему постараемся с этим разобраться.

Проблема №1. Именование кеша


Как назвать ключ в мемкеше, под которым мы сохраним закешированный результат выборки? У нас есть ORM-прослойка, и параметры выборки, которые представляют из себя ассоциативный массив, мы превращаем в ключ так:

$key = md5(serialize($options));

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

Проблема №2. Сброс кеша


Если данные в БД поменялись, надо сбросить соответствующий кеш (удалить ключ из мемкеша), чтобы пользователи увидели актуальную информацию. Знаем параметры выборки, строим ключ, удаляем его, при следующем обращении к кешу будет произведена выборка и построен новый кеш.

Однако, этого не всегда достаточно. Например, у нас изменился объект XXX. Однако этот объект может являться частью большего числа выборок, например, входит в отдельную выборку объекта XXX (такой кеш легко очистить), а также может входить в различные списки, которые также закешированы. Сбрасывать все такие списки невозможно или даже неразумно. Было бы классно, если бы мемкеш мог "теггировать" ключи некоторыми метками и сбрасывать ключи с указанным набором меток, но эта операция вряд ли будет иметь сложность O(1), как другие его операции. В таких ситуациях сбросить кеш оказывается сложно, можно выставить таким кешам достаточно небольшое время жизни, чтобы неактуальная информация надолго не задерживалась.

Contents © 2015 Andrey - Powered by Nikola