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

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

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

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

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

Цифры решают всё

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

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

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

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

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

Web-версия

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

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

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

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

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

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

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

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

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

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

Мобильные устройства

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

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

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

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

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

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

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

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

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

  • пользователь инициирует загрузку, и сервер выдает ему 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-сервер.

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

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

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

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

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

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

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

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

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

Факап со шпинделями

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

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

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

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

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

📎📎📎📎📎📎📎📎📎📎