Загрузка видео «без единого разрыва»

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

В чем проблема? — спросите вы. Ставишь серваки с большими дисками, настраиваешь балансировщик — и понеслась. Однако опытный видео-ниндзя знает, что проблем тут целый ворох:

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

Да, это

c30b3f3ef4fa4bc38e4b7c4f4940a1ff.jpg

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

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

Каждый день к нам на серверы попадает 15–20 терабайт нового видео, а пиковая нагрузка достигает 5 гигабит/сек. Большинство загружаемых видео — это короткие ролики, снятые пользователями на смартфоны. В то же время по суммарному объему загрузок в терабайтах web все еще остается лидером.

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

41bd2fb31df84182bc61be12c6f149d0.jpg

Разумеется, вместе с повышением качества растет и размер загружаемых видеофайлов: при переходе в качестве на 1 шаг вверх (720p —> 1080p, 1080p —> 1440p и т.д.) объем дискового пространства, занимаемого видео, возрастает примерно в два с половиной раза.

Давайте посмотрим, как реализована загрузка видео на портал в наших клиентах — в браузере (веб-версия), в мобильных приложениях (iOS/Android/WinPhone) и на мобильном портале.

Web-версия
Мы огромная соцсеть. Кроме высоких нагрузок эти три слова означают, что мы должны поддерживать весь имеющийся на сегодняшний день зоопарк браузеров начиная с IE 8 и заканчивая еще не вышедшими ночными сборками Firefox.

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

А если автоматически обновляются — тоже могут быть проблемы. Вот вышел неделю назад Firefox 40, и у миллионов наших пользователей, которые автоматически проапдейтились на него, сломалась загрузка фоток. Мы быстро все починили (в этот раз во всем были виноваты несовместимости в Content Security Policy), но бывает, что починка занимает довольно существенное время.

Как же нам удалось добиться стабильной работы портала в таком зоопарке браузеров? Fallback, fallback и еще раз fallback!

Клиентская часть нашего загрузчика видео написана на JavaScript c использованием фреймворков RequireJS, jQuery и замечательной библиотеки FileAPI от RubaXa из Mail.Ru.

FileAPI определяет, поддерживает ли пользовательский браузер HTML5. Если поддерживает, то все процедуры выполняются с помощью HTML5. Если не поддерживает — то FileAPI сам переключается на Flash. Также от браузера пользователя зависит и размер отправляемых chunk«ов.

Для каждого браузера мы экспериментальным путём подобрали свой размер чанка. В среднем он равен 2 Мб, а диапазон колеблется от 100 Кб до 10 Мб.

Загрузка файлов сделана максимально удобной для юзера. Поддерживается джентельменский набор функций: drag&drop, одновременная загрузка нескольких файлов, автоматически возобновляемая загрузка, кнопка паузы, индикатор хода выполнения и т.д. и т.п.

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

d227e54b53cc4fba8d166f14c28ee5c2.jpg

До кучи FileAPI не накладывает никаких ограничений на размер загружаемых пользователем файлов. При желании, можно загрузить даже терабайтный файл — какое-нибудь многочасовое видео в разрешении 4К. Другое дело, что ждать окончания загрузки пользователю придётся довольно долго даже при широком канале.

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

081178d7564b404ab34ac9efb6ebdf66.jpg

Логика этого процесса очень проста. FileAPI отправляет на сервер загрузки один чанк и ждёт ответа. Если ответ не приходит в течение некоторого времени, клиент отправляет этот чанк снова, потом еще раз и еще раз, до тех пор, пока сервер не подтвердит успешное принятие. В свое время переход на FileAPI позволил нам в несколько раз сократить число ошибок во время загрузки.

5cf8f27dfa02449caa7d84015f9deac1.jpg

Мобильные устройства
Если говорить о количестве загрузок видео на портал, то картина получается такая:

84fd597ae2e8472184f2beb66ec0bb28.jpg

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

Клиентская часть мобильного загрузчика сначала пытается залить сразу весь файл от начала до конца, используя XMLHttpRequest.send(). В случае обрыва соединения загрузчик пытается восстановить связь с сервером, опрашивая статус загрузки. Если удаётся получить ответ от сервера, загрузчик использует Blob.slice() для того, чтобы получить блок данных, следующий за последним успешно загруженным байтом.

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

Если браузер поддерживает HTML5 Web Worker API, то загрузка осуществляется в отдельном потоке, что помогает сделать нам интерфейс более отзывчивым.

Чтобы показать пользователю прогресс загрузки, мы используем событие XMLHttpRequest.onprogress. В браузерах это событие реализовано как попало: некоторые отправляют тысячи событий каждую секунду, другие не отправляют их вообще. Для первых реализован троттлинг — большая часть событий игнорируется. Для вторых — происходит периодический опрос сервера (раз в 5 секунд).

Бэкенд загрузки видео. Архитектура
В связи с постоянным ростом нагрузки мы непрерывно расширяем и модернизируем инфраструктуру нашего видеосервиса. Сегодня сервис видео — это

  • 240 серверов для хранения пользовательских видео. Дисков там более 7000 — суммарно около 30 петабайт;
  • метаданные и кэш развёрнуты еще на 36 машинах;
  • за трансформацию видеороликов в наш внутренний формат отвечают еще 150 серверов;
  • наконец, ещё 36 машин используются для раздачи и загрузки видео.


Таким образом, наш видеосервис состоит из более чем 400 машин. Железо у нас довольно обычное — что-то типа двухпроцессорных E5–2620 с 64Гб RAM. Архитектура хорошо скалируется, позволяет нам использовать эти, в общем-то, уже далеко не новые процессоры, и тем самым существенно экономить на железе.

Собственно, архитектура бэкенда загрузки видео, построена с учетом следующих основных требований:

  • возобновление загрузки в течение нескольких суток после потери соединения;
  • работоспособность сервиса при программных ошибках и аппаратаных отказах;
  • гарантия сохранности загруженных данных при отказе сервера или потере целого датацентра;
  • высокая производительность.

За загрузку видео в Одноклассниках отвечает подсистема из 6 серверов, распределённых по трем датацентрам. Перед каждой парой серверов загрузки в ДЦ стоит сервер с развернутым на нем LVS (LinuxVirtualServer — это приложения ядра Linux, которое позволяет вешать на один IP любое количество физических серверов, распределяя по ним нагрузку в зависимости от текущей ситуации).

Для балансировки запросов между ДЦ используется DNS-GSLB (Global Server Load Balancing). Это сервера, которые резолвят доменное имя в IP наиболее разгруженного/доступного датацентра. В случае отказа одного из датацентров DNS-GSLB равномерно перераспределит нагрузку по оставшимся.

22ac76010a6c4b79addcdb94789793a1.jpg

Рассмотрим процесс загрузки видео:

  • пользователь инициирует загрузку и сервер выдает ему URL для загрузки: http (s)://vu.mycdn.me/upload.do?…
  • далее DNS-GSLB разрешает доменное имя vu.mycdn.me в IP одного из LVSов (на рисунке в ip1);
  • DNS-GSLB партиционирует пользователей по первым трем октетам их IP адреса. Таким образом одному пользователю, при повторных DNS резолвах выдается IP одного и того же дата центра. Выдача IP адреса другого ДЦ, произойдет только при его отказе или смене IP пользователя, что случается редко;
  • далее LVS, внутри ДЦ, уже перенаправляет запросы к менее загруженному и доступному серверу;
  • на LVS настроен IP affinity, который обеспечивает отправку всех пользовательских запросов к одному серверу.

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

Параллельно процессу загрузки видео на upload-сервер, полученные данные отправляются в распределённое хранилище. Для каждого пользователя на сервере открыта сессия и буфер для приема данных. Размер буфера — 10 МБ. Как только старый буфер заполняется, одновременно происходят две вещи:

  • открывается новый буфер;
  • запускается асинхронная операция сброса старого буфера в промежуточное распределенное хранилище.


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

В случае выхода из ротации одного из серверов внутри ДЦ, LVS передаст все запросы доступному upload server-у внутри ДЦ. Доступный upload server восстанавливает состояния клиентской сессии по данным, имеющимся в распределенном хранилище, и пользователь незамедлительно продолжает загрузку, в худшем случае с retry последних загруженных 10 Мб.

f3621f742c994eb394ffbf6735738f76.jpg

Когда пользователь переключается на другой сервер, может появится «дырка» в последовательности загружаемых байт. В этом случае сервер возвращает специальных код ошибки 416 — «Range is not acceptable error, recoverable» с заголовком «X-Last-Known-Byte». Если клиент поддерживает данный заголовок, то он возобновляет загрузку с места, указанного в этом заголовке, а если нет — идет на один чанк назад.

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

38e568a917b0413d97fe443d47bf9131.jpg

Отказоустойчивость бэкэнда
Три основных сценария для проверки отказоустойчивости нашего сервиса — это:

  • сценарий с выходом датацентра из строя
  • сценарий с перегрузкой по диску
  • сценарий с перегрузкой по трафику

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

Сценарий с перегрузкой по входящему трафику для нас тоже малореален. Каналы, которые мы приобретаем в датацентрах, симметричны, и поскольку наш download трафик превосходит наш upload трафик примерно в 100 раз, то перегрузку по upload мы тоже всерьез не считаем за угрозу. Основная наша защита здесь — рубильники, которые отключают части пользователей загрузку видео. Таких частей (партиций) у нас 256, поэтому мы можем регулировать количество пользователей, которые могут загружать (или наоборот просматривать) видео с шагом меньше, чем 0,4%

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

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

Факап со шпинделями
В этом году нам представился случай оценить степень отказоустойчивости нашего сервиса.

После Парада Победы 9 мая начался резкий рост загрузок видеозаписей парада и салюта. Мы не ожидали трехкратного увеличения пикового трафика, поэтому дисковая подсистема временного хранилища очень быстро оказалась загруженной на 100%. Upload-сервера стали получать ошибки при попытке сбросить чанк во временное хранилище — хранилище не отвечало. У каждой сессий есть буфер, в который принимаются входящие данные, буферы жрут место, поэтому место в оперативке для сессий на upload-серверах тоже быстро закончилась…

Т.е.

  • клиентский загрузчик хотел загрузить чанк
  • в буфере сессии не было места
  • сервер возвращал клиенту возобновляемую ошибку.


Клиентский загрузчик повторял попытки, до тех пор, пока у upload-сервера не появлялся свободный буфер (пока upload-серверу не удавалось сбросить буфер в распределенное хранилище).

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

Итоги были таковы: в целом скорость загрузки на портал просела примерно вдвое, но отказа сервиса не произошло и все пользователи в итоге догрузили свое видео.

Выводы

  • Если у вас в требованиях — работа во многих браузерах, то используйте готовые решения для fallback — FileAPI для загрузки файлов, Atmosphere и Pusher для push-нотификаций и т.п.;
  • для лучшего баланса между производительностью и кроссбраузерностью имеет смысл менять размер чанка в зависимости от клиентского браузера. Дефолтный размер чанка — 2 Мб, но есть проблемные браузеры, для которых этот размер нужно уменьшать до 500 Кб, а то и до 100 Кб;
  • практически любую высоконагруженную систему имеет смысл тестировать на отказоустойчивость несколькими различными сценариями, в том числе, имитируя аварии.


Ну и не забывайте про мобильные устройства — мобильный трафик на крупных порталах давно уже превысил трафик с десктопов.

© Habrahabr.ru