Запуск Netflix на телевизорах и приставках. Лишние 40 миллисекунд
Приложение Netflix работает на сотнях умных телевизоров, стиков и телевизионных приставок. Я один из инженеров, которые помогают производителям запустить наше приложение на их устройствах. В этой статье обсудим особенно сложный вопрос, который помешал выходу одной телеприставки на европейский рынок.
Таинственная проблема
В конце 2017 года меня позвали на созвон, чтобы обсудить проблему с приложением Netflix на новой телеприставке. Это было новое устройство Android TV с поддержкой 4K, на базе Android Open Source Project (AOSP) версии 5.0, Lollipop. Я уже несколько лет работал в Netflix и помог выпустить несколько девайсов, но это был моё первое устройство Android TV.
На связи были все четыре стороны: крупная европейская компания платного ТВ, запускающая устройство (оператор), подрядчик, интегрирующий прошивку (интегратор), поставщик системы-на-чипе (поставщик чипов) и я (Netflix).
Интегратор и Netflix уже завершили строгий процесс сертификации Netflix, но во время внутреннего испытания у оператора руководитель компании сообщил о серьёзной проблеме: воспроизведение Netflix лагало, то есть видео воспроизводилось очень короткое время, затем пауза, затем снова, затем пауза. Это происходило не всегда, но стабильно начинало лагать через нескольких дней после включения приставки. Они показали видео, оно выглядело ужасно.
Интегратор нашёл способ воспроизвести проблему: несколько раз запустить Netflix, начать воспроизведение, а затем вернуться в UI. Они предоставили скрипт для автоматизации процесса. Иногда это занимало целых пять минут, но скрипт всегда надёжно воспроизводил баг.
Тем временем инженер из компании-поставщика чипов диагностировал основную причину: приложение Netflix для Android TV под названием Ninja не успевало доставлять аудиоданные. Лаги вызваны опустошением буфера в аппаратном звуковом конвейере. Воспроизведение останавливалось, когда декодер ждал от Ninja часть аудиопотока, а затем оно возобновлялось, когда поступали новые данные. Интегратор, поставщик чипов и оператор — все думали, что проблема понятна. И все они смотрели на меня: Netflix, у вас есть ошибка в вашем приложении, и вы должны её исправить. Я слышал напряжение в голосе представителя оператора. Выпуск устройства задерживался и выходил за рамки бюджета, и они ожидали от меня результатов.
Расследование
Я был настроен скептически. Это же самое приложение Ninja работает на миллионах устройств Android TV, включая умные телевизоры и другие телевизионные приставки. Если в Ninja ошибка, то почему она происходит только на этом устройстве?
Я начал с того, что сам воспроизвёл проблему с помощью скрипта от интегратора. Я связался с коллегой из компании-производителя микросхем и спросил, видел ли он что-нибудь подобное (не видел). Затем начал изучать исходный код Ninja. Нужно было найти точный код, который отвечает за доставку аудиоданных. Я во многом разобрался, но начал теряться в коде, который отвечает за воспроизведение, и мне нужна была помощь.
Я поднялся наверх и нашёл инженера, который написал аудио- и видеоконвейер Ninja, он познакомил меня с кодом. После этого я сам ещё некоторое время изучал его, чтобы окончательно понять главные части и добавить свои логи. Приложение Netflix сложное, но в упрощённом виде оно получает данные с сервера Netflix, в течение нескольких секунд буферизует видео- и аудиоданные на устройстве, а затем доставляет видео- и аудиокадры по одному на аппаратные декодеры.
Рис. 1. Упрощённый конвейер воспроизведения
Давайте на минутку поговорим об аудио/видеоконвейере в приложении Netflix. До «буфера декодера» он абсолютно одинаковый на каждой приставке и телевизоре, но перемещение данных A/V в буфер декодера устройства — это процедура, специфичная для конкретного устройства. Она выполняется в собственном потоке. Задача этой процедуры состоит в том, чтобы поддерживать буфер декодера полным, вызывая следующий кадр аудио- или видеоданных через API от Netflix. В Ninja эта работа выполняется потоком Android. Существует простой конечный автомат и некоторая логика для обработки различных состояний воспроизведения, но при нормальном воспроизведении поток копирует один кадр данных в API воспроизведения Android, а затем сообщает планировщику потока подождать 15 мс перед следующим вызовом обработчика. При создании потока Android можно запросить повторные запуски потоков, словно в цикле, но именно планировщик потоков Android вызывает обработчик, а не ваше собственное приложение.
На максимальных 60 FPS устройство должно отображать новый кадр каждые 16,66 мс, поэтому проверки через 15 мс хватает в любом случае. Поскольку интегратор определил, что проблема в аудиопотоке, я сосредоточился на конкретном обработчике, который доставлял аудиосэмплы в аудиослужбу Android.
Нужно было понять, откуда берутся лаги, то есть задержка. Я предположил, что виновата какая-то функция, вызванная обработчиком, поэтому разбросал сообщения журнала по всему обработчику и собирался легко найти код, ставший причиной лагов. Вскоре стало ясно, что в обработчике нет ничего плохого, и он несколько миллисекунд работает даже тогда, когда воспроизведение лагает.
Ага, озарение
В конце концов я сосредоточился на трёх числах: скорость передачи данных, время вызова обработчика и время передачи управления от обработчика обратно Android. Я написал скрипт для анализа выходных данных журнала и сгенерировал график ниже, на котором виден ответ.
Рис. 2. Визуализация пропускной способности аудиопотока и тайминги обработчика
Оранжевая линия — это скорость перемещения данных из буфера потоковой передачи в аудиосистему Android (байт в миллисекунду). На этой диаграмме три различных сценария:
Чтобы понять причину, давайте внимательнее изучим жёлтые и серые линии.
Жёлтая линия показывает время, проведённое в самой процедуре обработчика, вычисленное по меткам времени, записанным в начале и по завершению процедуры. Как в нормальной, так и в лагающей области время в обработчике одинаковое: около 2 мс. Всплески показывают случаи, когда время оказалось медленнее из-за выполнения других задач на устройстве.
Истинная первопричина
Серая линия — время между вызовами обработчика — говорит о другом. При обычном воспроизведении обработчик вызывается примерно каждые 15 мс. В случае лагов справа обработчик вызывается примерно каждые 55 мс Между вызовами возникают лишние 40 мс, и в такой ситуации он никак не может угнаться за воспроизведением. Но почему?
В этот момент меня спас другой инженер из компании-поставщика чипов, который обнаружил баг, уже исправленный в следующей версии Android (Marshmallow). Оказывается, планировщик потоков Android изменяет поведение потоков в зависимости от того, работает приложение на переднем плане или в фоновом режиме. Потокам в фоновом режиме назначается дополнительная задержка 40 мс (40000000 нс).
Ошибка глубоко в ядре Android означала, что это дополнительное значение таймера сохранялось при перемещении потока на передний план. Обычно поток обработчика звука создавался, когда приложение на переднем плане, но иногда немного раньше, когда Ninja всё ещё находилась в фоновом режиме. Если такое случалось, воспроизведение начинало лагать.
Полученные уроки
Это не последняя ошибка, которую мы исправили на платформе Android, но её было труднее всех отследить. Она находилась вне приложения Netflix и даже вне конвейера воспроизведения, а все исходные данные указывали на ошибку в самом приложении Netflix.
История иллюстрирует аспект моей работы, которую я люблю: невозможно предсказать все проблемы, которые наши партнёры сбросят на меня. И я знаю, что для их решения нужно понимать множество систем, работать с отличными коллегами и постоянно подталкивать себя к изучению нового. То, что я делаю, оказывает непосредственное влияние на реальных людей и на их удовольствие от отличного продукта. Когда люди наслаждаются просмотром Netflix в своей гостиной, я знаю, что являюсь частью команды, которая сделала это возможным.
Как устроена локализация в Netflix — перевод
Привет, Хабр! Представляю вашему вниманию перевод материала «Localization Technologies at Netflix», написанного командой Netflix про внутренние процессы локализации и программы, разработанные специально для этого.
Программа локализации в Netflix базируется на трех принципах: безупречная лингвистика, гармоничная атмосфера в коллективе и передовые технологии.
Мы не боимся экспериментировать и пробовать новые процессы и инструменты, выступать против общепринятых в локализации норм — благодаря этому мы продвинулись так далеко! Работать в Netflix — значит быть первопроходцем.
В этой статье мы рассказываем о двух технологиях, которые приведут нас к МИРОВОМУ господству… Подробнее под катом.
Netflix Global String Repository
Netflix достиг успеха не потому, что мы делаем качественный контент, а потому, как мы подаем этот контент. Большая часть успеха — это интуитивно понятный, простой в использовании и локализованный пользовательский интерфейс (UI). Netflix доступен на разных платформах: веб-версия, Apple iOS, Google Android, Sony PlayStation, Microsoft Xbox, в телевизорах Sony, Panasonic и так далее. У каждой из этих платформ свои требования к интернализации, что представляет собой серьезный вызов для нашей команды.
Вот примеры, когда требуется локализация UI:
После того, как перевод выполнен, приложение собирают, тестируют и размещают на платформе. Для некоторых устройств требуется получить подтверждение от третьей стороны (например, от Apple). Все это провоцирует нежелательную задержку сроков. Особенно неприятными являются случаи экстренного внесения правок.
Но что, если сделать процесс локализации открытым для всех стейкхолдеров — и для команды разработки, и для локализаторов? Что, если нам не нужно будет больше пересобирать билды каждый раз, когда вносятся правки в текст?
Для решения этих проблем мы разработали глобальное хранилище строк UI, которое называется Global String Repository; здесь хранятся локализованные строки, которые подставляются в среду для выполнения кода. Мы интегрировали Global String Repository в техпроцесс локализации, таким образом, они взаимно дополняют друг друга.
Global String Repository разделяет пакеты локализации и пространство имен (заполнители). В пакете локализации хранятся все данные по строкам на всех языках. Заполнители — это плейс-холдеры для пакетов, над которыми работает команда. Во время разработки используются стандартные заполнители. Рабочий процесс выглядит так:
Мы открываем Global String Repository через API Netflix, таким образом к нему применяются те же самые масштабирование и требования, что и к метаданным других API. Для приложений, которые интегрируются во время исполнения, это критически важная часть. У нас 60 миллионов пользователей, которые запускают Netflix на разных устройствах, поэтому Global String Repository является приоритетной задачей.
Как и у Netflix, у Global String Repository микросервисная архитектура. Микросервис — это веб-приложение на Java (выполненное в Apache Cassandra и ElasticSearch), которое размещено в трех регионах AWS. Мы собираем статистические данные для каждого запроса к API.
Интерфейс Global String Repository разработан на Node.js, Bootstrap и Backbone и размещен в AWS.
На стороне пользователя Global String Repository использует REST API для получения данных и предлагает клиент Java со встроенным кэшированием.
Несмотря на то, что мы проделали большой путь и активно развиваем Global String Repository, нам есть к чему стремиться. Вот над чем мы работаем сейчас:
Hydra
Netflix — глобальный сервис, который поддерживает множество локалей в мириадах различных комбинаций на разных устройствах/UI; ручное тестирование в таком случае не подходит. Раньше команда локализаторов и разработчиков UI тестировала все вручную на разных устройствах — от консолей до iOS и Android; так мы проверяли все строки на соответствие контексту и UI (например, нет ли «обрезания» текста).
Но философия Netflix такова — мы стремимся к совершенству. Такой подход позволяет нам переосмысливать то, что мы делаем. Так родилась Hydra.
Задача Hydra — создание каталога всех возможных вариантов уникального экрана, который будет показывать именно тот экран, который требуется (поиск осуществляется по фильтрам, например, можно выбрать устройство и локали). Например, будучи специалистом по немецкой локализации, вы можете настроить фильтрацию таким образом, что увидите весь путь, который проходят незарегистрированные пользователи на PS3, веб-сайте и Android. Эти же экраны можно посмотреть в том темпе, в котором пользователь будет открывать их на своем устройстве.
Работа с экранами в Hydra
Инструмент Hydra не работает с экранами напрямую; он служит для их каталогизации и отображения. Чтобы взять отображение экрана из каталога Hydra, мы используем нашу модель автоматизации UI. С помощью Jenkins CI тесты, управляемые данными, работают параллельно во всех поддерживаемых локалях: так создаются скриншоты, которые публикуются в Hydra с соответствующими метаданными (имя страницы, область функций, платформа UI и один критический фрагмент метаданных, — уникальное экранное определение).
Уникальное экранное определение нужно для того, чтобы составить полный каталог экранов без ложных совпадений. Это позволяет сравнить большее количество экранов в долгосрочной перспективе, так как изображение каждого экрана сравнивается с самим собой. Определение уникального экрана отличается от UI к UI; для браузера это сочетание имени страницы, браузера, разрешения, локальной среды и среды разработки.
Технология
Hydra — это полностековое веб-приложение, размещенное в AWS. Back-end на Java выполняет две основных функции: обрабатывает входящие скриншоты и предоставляет данные для бэк-энда через REST API.
Когда автоматизация UI отправляет экран в Hydra, сам файл изображения записывается на S3, что обеспечивает его бесконечное хранение (плюс-минус), а метаданные гораздо меньшего размера записываются в базу данных RDS, чтобы впоследствии запрашивать их через REST API. Конечные точки REST (REST endpoints) обеспечивают отображение параметров строки запроса на запросы MySQL.
В этом запросе содержатся параметры для выбора необходимых данных из Базы:
select distinct feature where uigroup = ‘TVUI’ AND area = ‘signupwizard’ AND locale = ‘da-DK’
Фронт-энд JavaScript, который использует knockout.js, позволяет пользователям выбирать фильтры и просматривать экраны, соответствующие этим фильтрам. Содержимое фильтров, а также экраны, которые соответствуют выбранным фильтрам, обеспечиваются вызовами конечных точек REST, упомянутых выше.
Масштабирование приложения
После установки Hydra и запуска автоматизации добавить новые локали так же легко, как добавить одну строку в существующий файл свойств, который отправляется в Data Provider фреймворка testNG. Экраны с новой локалью будут отображаться со следующими работающими сборками Jenkins.
Что дальше?
Нам нужно внедрить функцию, которая будет оповещать о том, что экран изменился. На данный момент, если строка меняется, нет ничего, что автоматически бы оповещало об этом. Hydra может превратиться в более или менее рабочую очередь, и тогда эксперты по локализации смогут войти в систему и увидеть только конкретный набор экранов, которые изменились.
Еще одна фича — иметь возможность сопоставлять отдельные строки ключей с тем, какие экраны нужно отображать. Это позволит переводчику изменить строку, а затем выполнить поиск по ключу и увидеть экраны, на которые повлияло это изменение; так переводчик увидит, как эта строка изменяется в контексте заранее.
Мы не боимся решать сложные задачи. Netflix станет глобальным сервисом, и наша команда по локализации будет расширяться. Подобные вызовы позволяют нам привлечь самых талантливых людей, и мы создаем команду, способную сделать то, что считается невозможным.
Как мы писали код Netflix
Как именно в Netflix реализован код до этапа работы в облаке? Части этой истории мы рассказывали и прежде, но сейчас настало время добавить в неё больше деталей. В данном посте мы опишем инструменты и методы, позволившие нам пройти путь от исходного кода до развёрнутого сервиса, который позволяет наслаждаться фильмами и сериалами более чем 75 миллионам подписчиков со всего мира.
Схема выше – отсылка к предыдущему посту, представляющему Spinnaker, нашу глобальную непрерывную платформу передачи данных. Но до попадания в Spinnaker строке кода нужно пройти несколько этапов:
Организационная культура, облако и микросервисы
Прежде чем углубиться в описание процесса создания кода Netflix, необходимо обозначить ключевые факторы, которые влияют на принимаемые решения: наша организационная культура, облако и микросервисы.
Культура Netflix расширяет возможности инженеров в плане использования любого, по их мнению, подходящего инструментария ради решения поставленных задач. По нашему опыту, для того, чтобы какое-либо решение получило всеобщее признание, оно должно быть аргументированным, полезным и уменьшать когнитивную нагрузку на большинство инженеров Netflix. Команды свободны в выборе пути решения задач, но за это расплачиваются дополнительной ответственностью по поддержке этих решений. Предложения центральных команд Netflix начинают считаться частью «проторенной дорожки» (paved road). Сейчас именно она находится в центре нашего внимания и поддерживается нашими специалистами.
Кроме того, в 2008 году Netflix начала переносить потоковую передачу данных на AWS и переводить монолитную ЦОД на основе Java-приложений на облачные Java-микросервисы. Их архитектура позволяет командам Netflix не быть сильно связанными друг с другом, что позволяет создавать и продвигать решения задач в комфортном для них темпе.
Разработка
Думаю, никого не удивит то, что перед развёртыванием сервиса, либо приложения, его сначала нужно разработать. Мы создали Nebula – набор плагинов build system для Gradle, чтобы справиться с основными сложностями в ходе разработки. Gradle является первоклассным инструментом для разработки, тестирования и подготовки Java-приложений к развёртыванию, потому что охватывает основные потребности нашего кода. Он был выбран потому как на нём было легко писать поддающиеся тестированию плагины, уменьшая при этом итоговый файл. Таким образом, Nebula, при помощи Gradle с его набором доступных плагинов для управления взаимосвязями, выпуском, развёртыванием и многими другими инструментами, обеспечивает надёжную автоматизированную функциональность.
Простое приложение на Java. Файл build.gradle
Представленный выше файл build.gradle является примером сборки обычного Java-приложения в Netflix. В теле этого проекта мы видим команды Java и четыре плагина Gradle, три из которых являются или частью Nebula, или же внутренними настройками, связанными с ее плагинами. Плагин ‘nebula’ – часть Gradle, обеспечивающая связи и настройки, необходимые для интеграции с нашей инфраструктурой. Плагин ‘nebula.dependency-lock’ позволяет проекту создавать файл a.lock, который строит вариативные зависимости с возможностью повторения. Плагин ‘netflix.ospackage-tomcat’ и блок ospackage будут рассмотрены ниже.
При помощи Nebula мы пытаемся обеспечить многократно используемую и совместимую функциональность сборки, преследуя задачу уменьшить шаблонные обороты в каждом файле приложения. В следующем посте мы подробнее обсудим Nebula и её различные функции, код к которым открыт. А пока вы можете зайти на web-сайт Nebula.
Интеграция
После локального испытания кода при помощи Nebula мы можем перейти к интеграции и развёртыванию. Для начала мы должны запустить обновлённый исходный код в хранилище Git, так как при необходимости команды могут свободно найти его рабочий процесс.
После подтверждения изменения срабатывает функция Jenkins. Наше использование Jenkins для непрерывной интеграции развивалось годами. В нашем ЦОД мы начали всего с одного массивного master Jenkins, но это вылилось в использование 25 masters Jenkins в AWS. В Netflix он используется для большого количества автоматизированных задач, которые сложнее простых непрерывных интеграций.
Задача Jenkins – вызвать Nebula для создания, тестирования и подготовки к развёртыванию кода приложения. Если репозиторий становится библиотекой, Nebula опубликует *.jar в нашем репозитории. Если репозиторий является приложением, то Nebula запустит плагин ospackage. Плагин ospackage (“operating system package”) представит созданный артефакт приложения в виде Debian или RPM пакете, содержание которого определяется при помощи простого DSL, основанного на Gradle. Затем Nebula опубликует Debian-файл в репозиторий пакетов, где он станет доступным для следующего этапа: «выпечки».
«Выпечка»
Примечание: Под «выпечкой» и «пекарней» подразумевается аналогия с процессом выпечки в реальной жизни: рецепт, последовательность, соблюдение необходимых условий. Автору оригинала очень нравится данный оборот и он будет использовать его в дальнейшем достаточно часто. К сожалению, русскоязычного аналога по вкладываемому в эти слова смыслу я не нашел.
Наша стратегия развертывания сосредоточена вокруг неизменности паттерна сервера. Для снижения вероятности дрейфа конфигурации и для уверенности в том, что повторения развертываний происходят из одного источника, файлы крайне не рекомендуется модифицировать вручную. Каждое развертывание на Netflix начинается с создания нового AMI (Amazon Machine Image). Для формирования файлов AMI прямо из источника мы создали «Пекарню»
«Пекарня» создаёт API, что невероятно облегчает создание AMI. Затем API службы Пекарни по расписанию «готовят» задания для рабочих узлов, которые используют Animator для создания изображений. Для вызова функции «выпекания» пользователь должен объявить установку нужного пакета и базовый образ, на который этот пакет и будет установлен. Он то (базовый AMI) и обеспечивает работу настроенной с общими конвекциями, услугами и инструментами среду Linux, необходимой для полной интеграции с более крупной частью экосистемы Netflix.
Когда Jenkins успешно выполнил свою часть работы, он, как правило, вызывает «Spinnaker pipeline». Они могут быть вызваны действиями «Jenkins» или фиксацией Git. «Spinnaker» прочтёт пакет операционной системы, сгенерированные «Nebula», и обратится к API «Пекарни», чтобы начать работу.
Применение
После завершения «выпекания» Spinnaker способен развернуть полученный в итоге AMI до десятков, сотен и даже тысяч экземпляров. Тот же самый AMI можно использовать в нескольких средах пока в это же время Spinnaker выявляет среду выполнения для конкретного экземпляра, что позволяет приложениям самостоятельно настраивать время своего выполнения. Успешное «выпекание» запустит следующий этап работы «Spinnaker pipeline» — резвертывание в тестовой среде.
Тут команды начинают деплой, используя при этом целую «батарею» автоматизированных тестов интеграции. С этого момента специфика «pipeline» делает процесс развёртки зависимым от людей с возможностью тонкой настройки. Команды используют «Spinnaker» для управления несколькими областями развертываний, красными / черными внедрениями и многим другим. Достаточно будет сказать о том, что «Spinnaker pipelines» обеспечивают команды гибким инструментарием для контроля развёртки кода.
Предстоящий путь
В целом эти инструменты дают высокий уровень производительности и автоматизации. Например, потребуется всего 16 минут для того, чтобы «Janitor Monkey», наш облачный сервис устойчивости и технического обслуживания, прошел путь от окна регистрации до мульти-региональной развертки.
Spinnaker «выпекает» и развёртывает pipeline, вызванную Jenkins.
Это говорит о том, что мы всегда ищем способы повысить опытность разработчиков и постоянно бросаем сами себе вызовы – делать лучше, быстрее и проще.
Нас часто спрашивают, как мы справляемся с бинарными зависимостями в Netflix. Nebula предоставляет нам инструменты, ориентированные на облегчение зависимостей Java. Для примера возьмём плагин dependency-lock, который помогает приложениям просчитывать всю их графику бинарных зависимостей и создает вариативный файл a.lock. Плагин «resolution rules» для Nebula позволяет создавать правила зависимостей для всего проекта, которые влияют на все его части. Эти инструменты помогают сделать управление двоичными зависимостями проще, но все равно не позволяют снизить показатели до приемлемого уровня.
Также мы работаем над снижением времени «выпекания». Не так давно 16 минут на развертывание казались мечтой, но с повышением быстродействия других частей системы эта мечта превратилась в потенциальное препятствие. Для примера возьмем развёртывание Simian Army: процесс «выпечки» занял 7 минут, что составляет 44% от всего процесса «выпекания» и развертывания. Мы обнаружили, что ведущими (по времени) в процессе выпечки являлись установка пакетов (включая разрешение зависимостей) и сам процесс копирования AWS.
По мере роста и развития Netflix повышается спрос на его сборку и инструментарий развертки, так как нужно обеспечивать первоклассную поддержку JavaScript/Node.js, Python, Ruby и Go без виртуальной машины. Сейчас для данных языков мы можем порекомендовать плагин Nebula ospackage, который позволяет получить Debian-пакет для последующей «выпечки», оставляя сборку и тестирование инженерам и инструментарию используемой платформы. Пока это решение подходит для нас, но мы пытаемся расширять наш инструментарий для снижения зависимости от конкретных языков.
Контейнеры представляют собой интересную возможность решения двух последних задач. Сейчас мы исследуем, каким образом контейнеры могут помочь улучшить наши процессы сборки, «выпекания» и развёртывания. Если мы сможем создать локальную контейнерную среду, которая в точности имитирует наши облачные среды, потенциально мы смогли бы уменьшить время «выпекания», необходимое для циклов разработки и тестирования, тем самым повысив производительность труда разработчиков и ускорив процесс развития продукта в целом. Контейнер, который можно было бы развернуть локально «из коробки», снижает когнитивную нагрузку и позволяет нашим инженерам сфокусироваться на решении поставленных задач и инновациях, вместо того, чтобы проверять, не появилась ли какая-нибудь ошибка из-за различия сред.




