Augmented reality

Originally posted on www.habrahabr.ru

JavaScript Augmented Reality — тест JSARToolkit

Если картинка не отображается, пожалуйста, напишите об этом автору
JSARToolkit это JavaScript библиотека, портированная с FLARToolkit (Flash) и предназначенная для отслеживания AR Маркеров на видео. ARToolKit преобразует данные из маркеров в 3D-координаты, используя их можно наложить изображения или 3D-объекты на плоскую поверхность. 

Вы уже наверно видели JSARToolkit в действии на демке Ilmari HeikkinenRemixing Reality.
Демо Ilmari это часть Мозилловской “Web O’ Wonder”, — сайт, демонстрирующий новые технологии, которые будут добавлены в Firefox 4.

Исследования HTML5 клипов

Заказчик поставил нам задачу — оценить возможность использования JSARToolkit для онлайн HTML5 клипов. (Нас попросили рассмотреть только тех пользователей, которые использовали последнюю версию Firefox и Chrome)
Вот некоторые из вопросов, на которые мы бы хотели ответить:
— Будет ли обработка быстрой на медленных компьютерах?
— Сколько AR Маркеров мы можем отследить одновременно?
— На сколько быстро можно двигать маркер, чтобы он стал не отслеживаемым?
— Какое наибольшее расстояние на котором камера может отследить маркер? 

Ответы на эти вопросы, исходный код и демки можно найти ниже.

Запись видео

Для записи тестового видео я использовал Flip Ultra HD. Качество видео Flip Ultra HD приемлемое, учитывая то, что это не профессиональная камера. Мы знали что ничего хорошего с такой камеры мы не получим, но этого было достаточно для теста. Основная проблема такой низкокачественной камерой — неспособность переключать скорость затвора.

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

Печать AR Маркеров

Я напечатал несколько маркеров, которые шли вместе с JSARToolkit и начал снимать тестовые кадры на кухне. Я даже и не ожидал, что все заработает с первого раза, каждое видео, добавленное в библиотеку оказалось рабочим. 

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

Перекодирование видео в VP8 WebM

Мы записывали видео в формате H.264 Mpeg. Чтобы все работало с HTML5 video мы должны были сконвертировать видео в WebM. Первый декодер видео, который я попробовал был Ffmpeg2Theora, судя по названию он должен был кодировать видео в WebM. Однако я понял, что с Ffmpeg2Theora есть проблемы. Когда я кодировал видео под Linux оно могло не проигрываться в Windows и наоборот. 

После тестов прочих кодировщиков я остановился на Miro Video Converter. К сожалению Miro не умеет обрабатывать несколько видео, но видео, которое он производит работает на всех ОС и браузерах.

Создание обертки

Я хотел написать простое API на основе JSARToolkit, которое можно было использовать повторно. Что-нибудь что я бы мог подключить к другой JavaScript библиотеке, например Popcorn.js. Код, который я нашел в демке Ilmari был специфичный для конкретной задачи. Так как код не изобиловал комментариями, у меня были проблемы с пониманием, что происходит. Пришлось экспериментировать. Я пришел к простому способу написания обертки для JSARToolkit. 

Первый шаг в использовании обертки JSARToolkit — установка вашего трекера. Пример как это можно сделать:

// Пример создания трекера со всеми возможными опциями
    var myTracker = jsartoolkit.tracker({
        src       : 'my-video.webm',                  // Исходник для видео
        autoplay  : true,                             // Стоит ли включить видео сразу
        repeat    : true,                             // Включить повтор
        volume    : 0,                                // Звук из видео
        target    : doc.getElementById('DOMTarget'),  // DOM element в который будет добавлен canvas
        width     : 720,                              // Ширина кадра
        height    : 360,                              // Высота кадра
        threshold : 100,                              // Настройка освещенности кадра
        ratio     : 0.5,                              // Настройка размера скрытого canvas для трекинга (1 = 1в1 как видео)
        debug     : false                             // Выводить ли отладочную информацию - для порога освещенности кадра
    });

Как только трекер был создан, следующим шагом стало добавления контента маркерам.
Мы добавили статическое изображение, а затем и 3D объект, экспортированный из Blender3D:

    // Добавления картинки первому маркеру
    myTracker.marker(0).image('my-image_01.png');

    // Добавление модели Blender3D
    myTracker.marker(2).model('HTML5_Logo001');
Этот пример показывает как обновить свойства маркера после того как он был создан:
    // Настройка свойств маркера 0
    myTracker.marker(0)
      .scale(1)
      .axis(0, 0, 1)
      .angle(0)
      .position(0,0,0)
    ;
Вы также можете добавить более сложное поведение, используя JSARToolkit-Wrapper. Следующий пример демонстрирует как обновить свойства маркера в реальном времени. Этот код заставляет первый маркер крутиться и пульсировать:
    // Анимация свойств маркера Marker_0 по таймеру
    var interval = global.setInterval( function(){
      var date    = + new Date(),
          scl     = 1.5 + (Math.sin( date/200 ) * 0.5),
          axs     = Math.cos( date/300 ),
          posX    = Math.sin( date/300 ),
          posY    = Math.cos( date/300 )
      ;

      myTracker.marker(0)
        .scale(scl)
        .axis(0, axs, 0)
        .position(posY, posX, 0)
        .angle(date / 230)
      ;
    }, 20);
Для доступа к видео трекера вы можете сделать что-то такое:
    // Доступ к видео элементу трекера и установка currentTime
    myTracker.video.currentTime = 1;

Ответы на вопросы

 

Будет ли обработка быстрой на медленных компьютерах?
Обработка видео и определение положения маркера очень быстрое. Я практически не заметил разницы в отслеживании 1 маркера и 100 маркеров. Основная нагрузка — наложение контента на видео.

Сколько AR-Маркеров мы можем отследить одновременно?
Я отслеживал 100 маркеров одновременно без каких-либо проблем.

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

Какое наибольшее расстояние на котором камера может отследить маркер?
Опять все зависит от нескольких факторов — от скорости перемещения камеры/объектов и от освещения. В хорошо освещенной комнате (не профессиональной студии). У меня получилось отслеживать Маркеры с 10 метров в направлении объектива в разрешении 720р. Чем выше разрешение камеры, тем выше качество отслеживания маркеров. Одну вещь которую стоит отметить: вы можете снимать ваше видео в разрешении 1080, кэшировать результаты трекинга и уменьшить число обработок на стороне клиента. Можно покрутить ratio если что-то плохо отслеживается или threshold если кадр был плохо освещен.

Заключение

Плюсы
— Легко внедрить
— Оптимальный алгоритм отслеживания, не нагружает процессор
— Можно отслеживать по крайней мере 100 маркеров
— Можно экспортировать напрямую с Blender3D
— Можно накладывать любой контент: картинки, видео, 3D объекты 

Минусы
— Слишком много глобальных переменных в коде исходной библиотеки
— Необходимо немного подкрутить экспорт из Blender3D
— Не поддерживается одновременная обработка нескольких видео
— Слишком много затратных вызовов getElementById() 

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

Примеры, Тесты и ссылки

JSARToolkit-Wrapper Demo
JSARToolkit Video Tests
JSARToolkit Marker Images
JSARToolkit-Wrapper on Github 

Для просмотра вам необходима последняя версия Firefox 4 или Chrome.

От переводчика

Автор к сожалению пока не выложил демку. Применение AR в таком виде в котором он представлен в переводе не очень широкое, но с появлением HTML5 Device Element (ещё
статья)
Posted in General | Leave a comment

Веб-кластер

Originally posted on www.habrahabr.ru

Приветствую, уважаемые сообщники!

Эта статья — о том, как мы реализовали веб-кластер для новостного портала (с пиком посещений в 130 тысяч уникальных посетителей в день — это 7Тб траффика за 3 дня — выборы и 2 последующих. Сейчас в среднем кластер раздаёт 35-40 Тб траффика в месяц), о том, как по-разному понимают одинаковые задачи программисты и журналисты, о том, как можно достичь одной и той же цели, идя разными путями.

Она будет интересна тем, кто хочет построить легко масштабируемый географически распределённый веб-кластер, не вкладывая астрономических сумм в оборудование (а по меркам телевидения — будут вообще смешные суммы).

Я больше чем уверен, что маркетологи, толкающие убер-решения свежевыпущенных продуктов, имеющих в своём названии слова «масштабируемый веб-кластер» или «horizontal infinite scalable web cluster», меня возненавидят.

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

Я не буду приводить банальных конфигов, которые можно найти в любом тоториале по настройке PHP, Nginx и Firebird (у последнего, строго говоря, и настраивать-то нечего — всё работает с пол-пинка «из коробки») и вообще буду рассказывать о сути решения, а не о том, какая из версий PHP лучше.

Опытным проектировщикам вряд ли будет интересно — они и так всё знают, а вот тем, кто только начинает путь на нелёгком поприще проектирования систем сложнее, чем «Hello, World!» наверняка будет что подчерпнуть — решение в боевом режиме уже скоро как 2 года, при этом никаких архитектурных проблем не возникало (хотя выход из строя сразу двух жёстких дисков на двух из трёх узлов был, но никто ничего не заметил — сайты чуть-чуть медленнее открывались, чем обычно).

Итак, пара слов, с чего всё начиналось. Жил-был сайтик группы независимых журналистов, которые очень мечтали стать настоящим телевидением (забегая вперёд, скажу, что у них это получилось — они создали своё, вполне успешное телевидение с «блэкджеком и шл…» — далее по тексту). Страна у нас маленькая, ничего страшного не происходит (и мы этому рады), но раз в 4 года у нас традиционно проходят выборы в Парламент. Который уже традиционно никак не избирает Президента. (Не бойтесь, политики тут не будет, это просто для общего понимания момента).

Разумеется, в период перед выборами и немного после все интернет СМИ очень сильно колбасит. Некоторые сайты не просто лежат, они валяются, некоторые переодически пытаются выдать хотя бы строчку текста, но проблема всеобщая и известная — сайты не справляются с наплывом посетителей. Я про обычные, текстовые сайты. А у наших клиентов сайт был необычный. Дело в том, что у них было и видео — новостные сюжеты, они производили 10 гигабайт в месяц — в то время, сейчас они создают такое количество видео в день. Ко всему прочему (это последнее упоминание политики, честное слово) эти журналисты не отличались особой лояльностью к власти. Говорили и писали что хотели. Совсем обнаглели, да? Мы всегда себя позиционируем как наёмников — клиент предлагает задачу, мы предлагаем её решение. Всё остальное нас не интересует, мы соблюдаем нейтралитет.

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

(Это упрощённая схема на ноябрь, в дальнейшем почти все сервера перенесли к Hetzner-у, так как у Netdirekt-a в то время постоянно колбасило канал. Сейчас у них с сетью ситуация обстоит намного лучше).
Обычные посетители видят один из 3-х серверов, при этом, мы сделали так, что «лёгкий» контент в виде текста и картинок все посетители из Молдовы тянули с одного их 3-х, а «тяжёлый» контент (видео) — тянули с сервера, расположенного у местного провайдера. Внешние посетители просто не видели молдавское зеркало и весь контент тянули с одного из немецких серверов.

Вот, что у нас получилось с посетителями в результате (каждая часть портала имеет свой счётчик):

Эта схема позволяет сменить управляющий сервер в любой момент, сама проверяет доступность узлов кластера, легко масштабируется — в качестве резервного рассматривался и Amazon EC – более того, Amazon EC даже использовался некоторое время для видеостриминга (около 4-х месяцев), но из-за дороговизны траффика решено было всё-таки стриминг-ноды перенести к немецкому Hetzner-у.
Непосредственно за 2 недели до часа «Х» мы взяли резервные сервера и держали их наготове (но пользователи их не видели, так как держать активным сервер несколько дешевле, чем использовать его в боевом режиме — только из-за траффика).

Как это всё работает? Очень просто — молча и круглосуточно ;) .

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

Основная задача этого сервера — позволять журналистам добавлять новости. Раз в определённое время (3-5 минут) стартует скрипт, который создаёт… offline-копию сайта. Разумеется, генерируются только страницы, которые были изменены или которые нуждаются в перестройке из-за кроссылок и зависимостей.

Это очень просто реализуется при помощи генераторов (sequense) и каскадных триггеров в Firebird — процедуре требуется внести изменения только в основную страницу сайта, а каскадные триггеры обновят все зависимости, пометив каждую страницу, которая нуждается в обновлении. Пометка выставляется не в виде флага 1/0, а в виде уникального номера, получаемого на основе генератора. Скрипт создания оффлайновой версии при старте узнаёт новое значение генератора, считывает значение этого генератора от своего предыдущего запуска и пересоздаёт все страницы в полученном диапазоне. При этом, так как мы используем транзакционный механизм Firebird – скрипту глубоко наплевать, какие изменения происходят во время его выполнения — т.е. у нас всегда создаётся целостная и непротиворечивая версия сайта, что бы при этом не делали репортёры.

Таким образом, у нас создаётся мастер-копия портала (ну или двух порталов, если угодно — текстового и видео). Скрипт (как и сама админка) написан на PHP и для работы с Firebird использует ADODB – так что его довольно-таки просто можно перестраивать по желанию заказчика*.

(* Но мы собираемся избавиться от ADODB в скором времени во всех наших будущих проектах — его универсальность только вредит, так как нормального механизма работы с БД, позволяющего использовать все особенности Firebird SQL сервера (впрочем, как и остальных) там не реализовано — к примеру, невозможно перехватывать исключения при выборке из селективных процедур, нет гибкого управления транзакциями и вообще, у этих классов слишком много ИИ — я предпочитаю самостоятельно решать, когда я хочу откатить тразакцию, а когда — подтвердить.)

Единственное, что пришлось менять в настройках Firebird – это значение размера кэша страниц БД — так как количество подключений к БД очень небольшое (редко когда более 50-60 одновременных подключений), то и количество страниц экспериментальным путём было увеличено до 2048 (мы используем Classic вариант, для архитектуры Super это значение спокойно можно увеличить в 10 раз, так как там общий кэш страниц. В грядущей версии Firebird 3.0 разработчики обещают одну SMP-friendly архитектуру с общим кэшем, так что для неё вполне можно будет использовать большие значения для настроек страничного кэша).

Затем, при помощи обычного rsync-а разница изменений раскидывается по зеркалам, которые из себя представляют обычные узлы для раздачи статики на основе Nginx. Я думаю, не требуется рассказывать, на что способен 4-хядерный сервер с 12 Гигабайтами оперативки при раздаче одной только статики? ;-)

При этом, 10% канала каждой ноды (это как раз 10 или 100 мегабит, в зависимости от конкретного подключения) зарезервировано для синхронизации. Синхронизация происходит в 2 этапа — сначала синхронизируется «тяжёлый» контент — картинки и видео, потом — текст (html/xml/js).

Иногда (при загруженных каналах от управляющего сервера к зеркалам) посетитель может увидеть маленькие несоответствия в виде негрузящихся картинок и/или видеороликов — так как используется round-robin DNS, то текст страницы пользователь может получить с одного зеркала, а ссылку на видео — с другого. Это не мешает порталу работать — текст есть всегда, а картинка или видео рано или поздно объявятся.

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

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

Удаление ноды происходит проще — просто убирается запись из DNS. Добавление и удаление вполне поддаются автоматизации (именно так мы поступили с частью, которая отвечала за веб-стриминг около 1000 мегабитных потоков на Amazom EC), но если вы вдруг решитесь повторять подобное — советую сначала посчитать, сколько занимает первичная синхронизация данных (у нас это 2 Гигабайта для «лёгкой версии портала» и примерно 1 Терабайт для видео, хранится только несколько последних месяцев).

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

Отдельно стоит упомянуть про подсчёты отображений новости. У меня сложилось впечатление, что любимое занятие у журналистов (помимо написаний/съёмок репортажей) — это мерянье количеством посетителей той или иной новости. Примерно литр крови и километр нервов нам пришлось потратить, чтобы убедить журналистскую братию в том, что не требуется выводить изменения счётчиков в реальном времени.

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

Для подсчёта просмотров мы связались с Кирилом Коринским (также известным, как catap), который любезно согласился добавить фичу подсчёта просмотров URL в свою ветку Nginx-а. Ну а дальше уже всё просто — все узлы переодически опрашиваются и счётчики страниц учитываются в свойствах самой страницы. Так как счётчики (т.е. сами значения) хранятся в отдельных файлах (сейчас это по одному файлу на новость, в скором времени мы планируем сделать группу счётчиков в одном файле, чтобы уменьшить количество самих файлов) — то при синхронизации передаётся не страницы сайта целиком, а только файл-счётчик. При большом количестве файлов это создаёт дополнительную нагрузку на дисковую подсистему — поэтому, при использовании такого же подхода сразу продумывайте о том, как разбить счётчики по группам — мы остановились на разбиении счётчиков по типам новостей и дате — файлы относительно маленькие и со временем они перестают меняться, так как старые новости никого практически не интересуют.

Вот вкратце, плюсы использованного нами решения:

  1. Использование статических сайтов в качестве узлов веб-кластера позволяет свести администрирование всего кластера к нескольким рутинным задачам — обновлению операционной системы узлов и оплате траффика. Этот же пункт позволяет раскидать географически узлы кластера, что вкупе с GEO-DNS может рассматриваться вообще как некоторый аналог сети доставки контента (CDN) — по сути это оно и есть.
  2. Использование транзакционного механизма БД и перенос логики в саму БД позволяет всегда иметь целостную и логически непротиворечивую версию сайта — впрочем, я бы очень удивился, если бы «срез» данных с сервера был бы логически нецелостным.
  3. Если ожидается наплыв посетителей — то простым увеличением узлов кластера можно легко с ним справиться. В нашем случае, полный ввод нового узла в строй занимал чуть более часа для текстовой части портала и около суток (нельзя впихнуть невпихуемое) для видео. Если смириться с частичной синхронизацией сайтов и остальное «доливать» в фоне — то ввод нового узла для видео также можно сократить до часа.
  4. Административный сервер можно сделать из любого из узлов (при необходимости) — достаточно просто развернуть бэкап базы (в сжатом виде около сотни мегабайт). Весь остальной контент уже есть.

Ну и парочка минусов, чтобы не всё казалось таким безоблачным:

  1. Решение не подходит для случаев, когда есть части сайта, которые по-разному должны видеть разные пользователи, т.е. когда по условию задачи страницы генерируются персонально для каждого пользователя. В нашем случае этого оказалось и ненужно.
  2. Счётчики посещений обновляются с отставанием примерно в полчаса-час. Терпимо, но вам придётся в этом долго убеждать клиента.
  3. Больше всего проблем доставляет синхронизация с местным зеркалом — наши провайдеры ещё не продают траффик по цене в 7 евро за Терабайт и если и предоставляют 100 честных Мегабит в мир — то по очень неадекватным ценам.
  4. Проектируйте менее параноидальную систему слежения за узлами кластера — наша оказалась слишком чувствительной, пришлось переводить добавление и удаление узлов в ручной режим.

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

Для хранения оффлайн-копии сайта мы используем файловую систему JFS. Она себя очень хорошо зарекомендовала и при работе с множеством мелких файлов и при работе с большими файлами (по моему опыту ещё только XFS может практически моментально удалить файл размером в 200-300Гб).

Так вот — по умолчанию файловая система монтировалась с параметрами по умолчанию. Но так как у нас со временем стало очень много файлов, дисковые операции стали немного подтормаживать. Так как время последнего доступа к файлу нам не требуется, я добавил опцию «noatime» к параметрам монтирования ФС. Вот что получилось — момент добавления, думаю, вы определите сами:

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

  • 3 сервера для раздачи контента
  • 2 сервера для «админки»
  • 2 сервера для DNS и системы слежения за остальными серверами.

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

В месяц потребляется около 40Тб траффика, общий объём контента — чуть более 1 Терабайта, видеоконтент хранится около 3-х месяцев.

Я с удовольствием отвечу на вопросы хабрасообщества.
Приветствую, уважаемые сообщники!

Эта статья — о том, как мы реализовали веб-кластер для новостного портала (с пиком посещений в 130 тысяч уникальных посетителей в день — это 7Тб траффика за 3 дня — выборы и 2 последующих. Сейчас в среднем кластер раздаёт 35-40 Тб траффика в месяц), о том, как по-разному понимают одинаковые задачи программисты и журналисты, о том, как можно достичь одной и той же цели, идя разными путями.

Она будет интересна тем, кто хочет построить легко масштабируемый географически распределённый веб-кластер, не вкладывая астрономических сумм в оборудование (а по меркам телевидения — будут вообще смешные суммы).

Я больше чем уверен, что маркетологи, толкающие убер-решения свежевыпущенных продуктов, имеющих в своём названии слова «масштабируемый веб-кластер» или «horizontal infinite scalable web cluster», меня возненавидят.

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

Я не буду приводить банальных конфигов, которые можно найти в любом тоториале по настройке PHP, Nginx и Firebird (у последнего, строго говоря, и настраивать-то нечего — всё работает с пол-пинка «из коробки») и вообще буду рассказывать о сути решения, а не о том, какая из версий PHP лучше.

Опытным проектировщикам вряд ли будет интересно — они и так всё знают, а вот тем, кто только начинает путь на нелёгком поприще проектирования систем сложнее, чем «Hello, World!» наверняка будет что подчерпнуть — решение в боевом режиме уже скоро как 2 года, при этом никаких архитектурных проблем не возникало (хотя выход из строя сразу двух жёстких дисков на двух из трёх узлов был, но никто ничего не заметил — сайты чуть-чуть медленнее открывались, чем обычно).

Итак, пара слов, с чего всё начиналось. Жил-был сайтик группы независимых журналистов, которые очень мечтали стать настоящим телевидением (забегая вперёд, скажу, что у них это получилось — они создали своё, вполне успешное телевидение с «блэкджеком и шл…» — далее по тексту). Страна у нас маленькая, ничего страшного не происходит (и мы этому рады), но раз в 4 года у нас традиционно проходят выборы в Парламент. Который уже традиционно никак не избирает Президента. (Не бойтесь, политики тут не будет, это просто для общего понимания момента).

Разумеется, в период перед выборами и немного после все интернет СМИ очень сильно колбасит. Некоторые сайты не просто лежат, они валяются, некоторые переодически пытаются выдать хотя бы строчку текста, но проблема всеобщая и известная — сайты не справляются с наплывом посетителей. Я про обычные, текстовые сайты. А у наших клиентов сайт был необычный. Дело в том, что у них было и видео — новостные сюжеты, они производили 10 гигабайт в месяц — в то время, сейчас они создают такое количество видео в день. Ко всему прочему (это последнее упоминание политики, честное слово) эти журналисты не отличались особой лояльностью к власти. Говорили и писали что хотели. Совсем обнаглели, да? Мы всегда себя позиционируем как наёмников — клиент предлагает задачу, мы предлагаем её решение. Всё остальное нас не интересует, мы соблюдаем нейтралитет.

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

(Это упрощённая схема на ноябрь, в дальнейшем почти все сервера перенесли к Hetzner-у, так как у Netdirekt-a в то время постоянно колбасило канал. Сейчас у них с сетью ситуация обстоит намного лучше).
Обычные посетители видят один из 3-х серверов, при этом, мы сделали так, что «лёгкий» контент в виде текста и картинок все посетители из Молдовы тянули с одного их 3-х, а «тяжёлый» контент (видео) — тянули с сервера, расположенного у местного провайдера. Внешние посетители просто не видели молдавское зеркало и весь контент тянули с одного из немецких серверов.

Вот, что у нас получилось с посетителями в результате (каждая часть портала имеет свой счётчик):

Эта схема позволяет сменить управляющий сервер в любой момент, сама проверяет доступность узлов кластера, легко масштабируется — в качестве резервного рассматривался и Amazon EC – более того, Amazon EC даже использовался некоторое время для видеостриминга (около 4-х месяцев), но из-за дороговизны траффика решено было всё-таки стриминг-ноды перенести к немецкому Hetzner-у.
Непосредственно за 2 недели до часа «Х» мы взяли резервные сервера и держали их наготове (но пользователи их не видели, так как держать активным сервер несколько дешевле, чем использовать его в боевом режиме — только из-за траффика).

Как это всё работает? Очень просто — молча и круглосуточно ;) .

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

Основная задача этого сервера — позволять журналистам добавлять новости. Раз в определённое время (3-5 минут) стартует скрипт, который создаёт… offline-копию сайта. Разумеется, генерируются только страницы, которые были изменены или которые нуждаются в перестройке из-за кроссылок и зависимостей.

Это очень просто реализуется при помощи генераторов (sequense) и каскадных триггеров в Firebird — процедуре требуется внести изменения только в основную страницу сайта, а каскадные триггеры обновят все зависимости, пометив каждую страницу, которая нуждается в обновлении. Пометка выставляется не в виде флага 1/0, а в виде уникального номера, получаемого на основе генератора. Скрипт создания оффлайновой версии при старте узнаёт новое значение генератора, считывает значение этого генератора от своего предыдущего запуска и пересоздаёт все страницы в полученном диапазоне. При этом, так как мы используем транзакционный механизм Firebird – скрипту глубоко наплевать, какие изменения происходят во время его выполнения — т.е. у нас всегда создаётся целостная и непротиворечивая версия сайта, что бы при этом не делали репортёры.

Таким образом, у нас создаётся мастер-копия портала (ну или двух порталов, если угодно — текстового и видео). Скрипт (как и сама админка) написан на PHP и для работы с Firebird использует ADODB – так что его довольно-таки просто можно перестраивать по желанию заказчика*.

(* Но мы собираемся избавиться от ADODB в скором времени во всех наших будущих проектах — его универсальность только вредит, так как нормального механизма работы с БД, позволяющего использовать все особенности Firebird SQL сервера (впрочем, как и остальных) там не реализовано — к примеру, невозможно перехватывать исключения при выборке из селективных процедур, нет гибкого управления транзакциями и вообще, у этих классов слишком много ИИ — я предпочитаю самостоятельно решать, когда я хочу откатить тразакцию, а когда — подтвердить.)

Единственное, что пришлось менять в настройках Firebird – это значение размера кэша страниц БД — так как количество подключений к БД очень небольшое (редко когда более 50-60 одновременных подключений), то и количество страниц экспериментальным путём было увеличено до 2048 (мы используем Classic вариант, для архитектуры Super это значение спокойно можно увеличить в 10 раз, так как там общий кэш страниц. В грядущей версии Firebird 3.0 разработчики обещают одну SMP-friendly архитектуру с общим кэшем, так что для неё вполне можно будет использовать большие значения для настроек страничного кэша).

Затем, при помощи обычного rsync-а разница изменений раскидывается по зеркалам, которые из себя представляют обычные узлы для раздачи статики на основе Nginx. Я думаю, не требуется рассказывать, на что способен 4-хядерный сервер с 12 Гигабайтами оперативки при раздаче одной только статики? ;-)

При этом, 10% канала каждой ноды (это как раз 10 или 100 мегабит, в зависимости от конкретного подключения) зарезервировано для синхронизации. Синхронизация происходит в 2 этапа — сначала синхронизируется «тяжёлый» контент — картинки и видео, потом — текст (html/xml/js).

Иногда (при загруженных каналах от управляющего сервера к зеркалам) посетитель может увидеть маленькие несоответствия в виде негрузящихся картинок и/или видеороликов — так как используется round-robin DNS, то текст страницы пользователь может получить с одного зеркала, а ссылку на видео — с другого. Это не мешает порталу работать — текст есть всегда, а картинка или видео рано или поздно объявятся.

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

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

Удаление ноды происходит проще — просто убирается запись из DNS. Добавление и удаление вполне поддаются автоматизации (именно так мы поступили с частью, которая отвечала за веб-стриминг около 1000 мегабитных потоков на Amazom EC), но если вы вдруг решитесь повторять подобное — советую сначала посчитать, сколько занимает первичная синхронизация данных (у нас это 2 Гигабайта для «лёгкой версии портала» и примерно 1 Терабайт для видео, хранится только несколько последних месяцев).

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

Отдельно стоит упомянуть про подсчёты отображений новости. У меня сложилось впечатление, что любимое занятие у журналистов (помимо написаний/съёмок репортажей) — это мерянье количеством посетителей той или иной новости. Примерно литр крови и километр нервов нам пришлось потратить, чтобы убедить журналистскую братию в том, что не требуется выводить изменения счётчиков в реальном времени.

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

Для подсчёта просмотров мы связались с Кирилом Коринским (также известным, как catap), который любезно согласился добавить фичу подсчёта просмотров URL в свою ветку Nginx-а. Ну а дальше уже всё просто — все узлы переодически опрашиваются и счётчики страниц учитываются в свойствах самой страницы. Так как счётчики (т.е. сами значения) хранятся в отдельных файлах (сейчас это по одному файлу на новость, в скором времени мы планируем сделать группу счётчиков в одном файле, чтобы уменьшить количество самих файлов) — то при синхронизации передаётся не страницы сайта целиком, а только файл-счётчик. При большом количестве файлов это создаёт дополнительную нагрузку на дисковую подсистему — поэтому, при использовании такого же подхода сразу продумывайте о том, как разбить счётчики по группам — мы остановились на разбиении счётчиков по типам новостей и дате — файлы относительно маленькие и со временем они перестают меняться, так как старые новости никого практически не интересуют.

Вот вкратце, плюсы использованного нами решения:

  1. Использование статических сайтов в качестве узлов веб-кластера позволяет свести администрирование всего кластера к нескольким рутинным задачам — обновлению операционной системы узлов и оплате траффика. Этот же пункт позволяет раскидать географически узлы кластера, что вкупе с GEO-DNS может рассматриваться вообще как некоторый аналог сети доставки контента (CDN) — по сути это оно и есть.
  2. Использование транзакционного механизма БД и перенос логики в саму БД позволяет всегда иметь целостную и логически непротиворечивую версию сайта — впрочем, я бы очень удивился, если бы «срез» данных с сервера был бы логически нецелостным.
  3. Если ожидается наплыв посетителей — то простым увеличением узлов кластера можно легко с ним справиться. В нашем случае, полный ввод нового узла в строй занимал чуть более часа для текстовой части портала и около суток (нельзя впихнуть невпихуемое) для видео. Если смириться с частичной синхронизацией сайтов и остальное «доливать» в фоне — то ввод нового узла для видео также можно сократить до часа.
  4. Административный сервер можно сделать из любого из узлов (при необходимости) — достаточно просто развернуть бэкап базы (в сжатом виде около сотни мегабайт). Весь остальной контент уже есть.

Ну и парочка минусов, чтобы не всё казалось таким безоблачным:

  1. Решение не подходит для случаев, когда есть части сайта, которые по-разному должны видеть разные пользователи, т.е. когда по условию задачи страницы генерируются персонально для каждого пользователя. В нашем случае этого оказалось и ненужно.
  2. Счётчики посещений обновляются с отставанием примерно в полчаса-час. Терпимо, но вам придётся в этом долго убеждать клиента.
  3. Больше всего проблем доставляет синхронизация с местным зеркалом — наши провайдеры ещё не продают траффик по цене в 7 евро за Терабайт и если и предоставляют 100 честных Мегабит в мир — то по очень неадекватным ценам.
  4. Проектируйте менее параноидальную систему слежения за узлами кластера — наша оказалась слишком чувствительной, пришлось переводить добавление и удаление узлов в ручной режим.

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

Для хранения оффлайн-копии сайта мы используем файловую систему JFS. Она себя очень хорошо зарекомендовала и при работе с множеством мелких файлов и при работе с большими файлами (по моему опыту ещё только XFS может практически моментально удалить файл размером в 200-300Гб).

Так вот — по умолчанию файловая система монтировалась с параметрами по умолчанию. Но так как у нас со временем стало очень много файлов, дисковые операции стали немного подтормаживать. Так как время последнего доступа к файлу нам не требуется, я добавил опцию «noatime» к параметрам монтирования ФС. Вот что получилось — момент добавления, думаю, вы определите сами:

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

  • 3 сервера для раздачи контента
  • 2 сервера для «админки»
  • 2 сервера для DNS и системы слежения за остальными серверами.

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

В месяц потребляется около 40Тб траффика, общий объём контента — чуть более 1 Терабайта, видеоконтент хранится около 3-х месяцев.

 

Posted in General | Leave a comment

Spicesoft

Прогрессивные технологии, как способ выжать из сервера максимум

Вступление

Просто красивый rrdtool =)
Забавно, но когда программист разрабатывает какой-либо продукт, он редко задумывается над вопросом могут ли на одну кнопку в один момент времени нажать одновременно 2000 человек. А зря. Оказывается могут. Как ни странно но большинство движков, написанных такими программистами, очень плохо ведут себя под большими нагрузками. Кто бы подумал, а всего один лишний INSERT, не проставленный index, или кривая рекурсивная функция могут поднять load averages чуть ли не на порядок.

В этой статье я опишу как мы, разработчики проекта, сумели выжать из одного сервера с Pentium 4 HT / 512Mb RAM, максимум, держа одновременно 700+ пользователей на форуме и 120,000 на трекере. Да, проект этот — торрент трекер. Предлагаю сразу оставить в стороне разговоры о копирайтах и правах, мне это не интересно, что действительно интересно — это HighLoad.

Для начала опишу проект таким, каким он был:

Обычный торрент трекер на движке TorrentPier (он же в девичестве phpbb 2.x)

  • Сервер на FreeBSD 6.0
  • Pentium 4 HT / 512Mb RAM
  • Web-сервер Apache
  • База MySQL
  • Вся логика на PHP

То есть практически LAMP

Вкратце сразу распишу последовательно те шаги которые мы предприняли:

  • Установка на сервер opcode cache
  • Замена apache на nginx
  • Кэширование некоторых промежуточных выборок в НЕ RDBMS
  • Перевод ключевой части (читай трекера) на C++
  • Оптимизация сетевого стека FreeBSD, а также её обновление до последней -STABLE
  • Оптимизация MySQL
  • Кэширование BB-кодов
  • Переписка кода на использование SphinxSearch
  • Профайлинг кода и установка средств мониторинга
  • Разборка запросов из MySQL slow query log

Теперь о каждом пункте поподробнее

Установка на сервер opcode cache

Он нужен всегда! Установка php-cache дало 300%+ производительности, потратив 15 минут времени.
Кэши бывают разные: eAccelerator, xCache, APC и т.д… Мы остановились на последнем, из-за хорошей скорости и возможности хранить в нём пользовательские данные

Замена apache на nginx

Apache — тяжёлый и медленный, сначала стоял как основной web-сервер, потом перед ним был поставлен nginx, отдающий статику и сжимающий ответы gzip’ом. Далее от apache отказались вообще в пользу связки nginx+php-fpm (если быть точным на тот момент это был spawn_fcgi, но сейчас такой вариант лучше). Связка в те времена была не самая популярная для production, но у нас она работала замечательно!

Кеширование некоторых промежуточных выборок в НЕ RDBMS

RDBMS — это зло. Оно удобно, но за удобство надо платить. В данном случае скоростью. А нам именно она и нужна. Так, что часть результатов самых популярных и не критичных к актуальности запросов к мускулу мы закешировали в APC. Сразу предчувствуя множество вопросов почему не в memcached… Как бы вам ответить… мне уже надоело даже это слово слышать memcached,memcached,memcached как будто это панацея от всего. Его не предлагают последнее время разве, что только от диареи. В нашем случае выбор пал на APC ибо он не использует TCP соединение и из-за этого работает в разы быстрее. Темболее пока у нас всё отлично крутится на одном сервере и нам распределённое хранилище не так уж и нужно.
Вы можете выбрать любое другое key/value хранилище, не обязательно хранящее данные в оперативной памяти.
Но весьма вероятно, что в вашем случае memcached/memcachedb/memcacheQ будут лучшим вариантом.

Вообще была идея сделать многоуровневую кэш-прослойку в которой php искал значение в глобальных переменных, потом в APC, далее в memcached, а лишь потом лезет в базу SELECT’ом. Но так как проектом занимаемся в свободное от учёбы/работы/семьи время, то до этого пока не дошло.

Перевод ключевой части (читай трекера) на C++

120000 активных пиров создают не мало коннектов к nginx, что ещё хуже, так каждый из них дёргает php, который дёргает мускул. Вам не кажется что ето уж слишком? Нам тоже так показалось. Один из наших разработчиков собрался с силами и переписал код XBTT под фронтенд TorrentPier’а. Оно того стоило, теперь клиент обращается к трекеру на 2710 порт, который держит в памяти табличку с пирами, там его быстро находит, делает что надо и отдаёт ответ пиру обратно. Раз в минуту скидывает результаты в базу. Всё прекрасно. +100000% производительности.
Вот результаты теста, когда мы поставили время анонса — 1 минута
input (rl0) output
packets errs bytes packets errs bytes colls drops
20K 0 2.5M 16K 0 1.5M 0 0

Цена вопроса 100 Метров памяти и 30% загрузка однго проца. Итого получается, что с такой же загрузкой можно держать примерно 8 Миллионов пиров на одной машине при получасовом времени анонса

PID USERNAME THR PRI NICE SIZE RES STATE C TIME WCPU COMMAND
10 root 1 171 52 0K 8K RUN 1 538.6H 47.12% idle: cpu1
6994 root 1 108 0 98140K 96292K CPU0 0 3:57 33.98% xbt_tracker
11 root 1 171 52 0K 8K RUN 0 595.0H 31.20% idle: cpu0
35 root 1 -68 -187 0K 8K WAIT 0 17.1H 21.14% irq21: rl0
12 root 1 -44 -163 0K 8K WAIT 0 482:57 9.96% swi1: net

[root@****] /usr/ports/devel/google-perftools/> netstat -an | wc -l
24147

Оптимизация сетевого стека FreeBSD, а также её обновление до последней -STABLE

В последних версиях FreeBSD 6 очень хорошо переработан планировщик 4BSD, а в 7ке так вообще есть такая приятная вещь как ULE, с которой мускул работает в разы шустрее на SMP
Также в любой высокопроизводительной инсталяции FreeBSD нужно крутить sysctl, я рекомендую это делать по Сысоеву

Оптимизация MySQL

База данных это то, во что рано или поздно упирается любой проект, мы не исключение.
В то время myisam у нас использовался по двум причинам

  • он используется по-умолчанию
  • в нём есть FULLTEXT индекс для поиска по форуму

Так мы потратили немало времени крутя буферы. Особенно в этом помог tuning-primer.sh
В дальнейшем планируется перевод базы на Xtradb. В любом случае нам пока хорошо — база влезает в память =)

Кэширование BB-кодов

Оказывается phpbb «на лету» преобразовывает bbcod’ы в html. Не хорошо. Закешировали сгенерированный html код в отдельном поле базы данных для каждого поста/подписи. В итоге база потяжелела почти в 2 раза, зато сайт начал летать.

Переписка кода на использование SphinxSearch

Как-то читал презентацию фликера, по поводу того как они у себя делали поиск. Так как база у них на innodb они сделали отдельную ферму Master-MultipleSlaves на myisam, чтобы обрабатывать на ней поиски. Что ж, мы не так богаты, сервер у нас только один. Ещё один наш разработчик, взяв волю в кулак, перевёл весь поиск по сайту на сверхбыстрый SphinxSearch. Результат превзошёл все ожидания. Сервер опять залетал.
Как косвенный эффект это позволило нам ввести просто супер-мега-удобный rss со встроенным поиском, который почти не грузит сервер.

Профайлинг кода и установка средств мониторинга

Странно, но многие этим ещё не пользуются. А зря. Если не знаешь где находится bottleneck устранить его невозможно. Для этого мы напихали в php код hook’ов профайлера, а на сервер установили munin.

Разборка запросов из MySQL slow query log

Тут классика! 20% запросов к базе занимают 80% времени. Приглядитесь может и у вас так. А после разбора логов, подписок FORCE INDEX к запросам и закоментирования нескольких строчек в php загрузка в час пик упала в два раза, а главная страница начала грузится в 10(!!) раз быстрее.
В общем очень рекомендую проводить такую операцию раз-два в год или после введения множества мелких нововведений. Очень помог инструмент mysqlsla.

Вместо послесловия

Вот как несколькими шагами мы превратили обычный LAMP в комплексную систему. Сейчас мы живём на обычном Core2Duo 2Ггц с 3Гб оперативки, сейчас ноутбуки в магазинах продаются «покруче», но нам хватает, а загрузка в час пик не поднимается выше 1.5 при 200000 тысячах пиров и ~500 пользователях форума. Интересно каких объёмов потребовался бы парк если бы мы тупо росли горизонтально используя LAMP и репликацию?
Посмотрим как всё изменится когда нам перестанет хватать одного сервера.

Posted in General | Leave a comment