Как мы улучшили наш процессор изображений с помощью программирования, основанного на страхе

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

Только не снова!

19:15: это снова происходит. Поступают сообщения об отсутствующих обновлениях изображений объявлений. Я вижу исключения сети Finagle и признаки тупика в одном из журналов нашей серверной службы Thrift. Это последняя из серии проблем с процессором изображений. Поздняя ночная отладка, усугубленная моей неспособностью решить проблему, привела к опасному росту моего стресса за последние несколько месяцев.

Я обращаюсь к нашей команде DevOps, чтобы помочь решить эту проблему. Мы просматриваем наш канал Slack, посвященный конкретным инцидентам, # war-room. Кто-то отслеживает корень сбоя. Запросы в нашу службу обработки изображений - службу, отвечающую за загрузку, изменение размера и загрузку изображений списков в S3 - истекли ... снова. Тайм-ауты нашего балансировщика нагрузки, клиента и сервера не совпадают. Количество проблем с обработкой изображений, которые мы исправили за последние несколько месяцев, утомило нас, но позволило нам найти более радикальные решения.

Исходный конвейер данных листинга

Наш конвейер листинга извлекает и обрабатывает листинги с изображениями из API-интерфейсов, предоставляемых сторонними поставщиками данных, называемых Службами множественных листингов (MLS). Он включает четыре основных шага, представленных на диаграмме ниже нашего конвейера листингов:

  1. Преобразование списков из схем данных MLS в наши собственные.
  2. Геолокационные объявления по адресу. Мы используем комбинацию данных переписи и Google Maps для привязки каждого адреса к широте и долготе.
  3. Копирование и изменение размера изображений для представления в наших продуктах «Поиск», «Страницы списков» и «Коллекции».
  4. Объединение нескольких списков одного и того же дома в единое представление.
    Если дом продавался несколько раз за несколько лет, мы хотим объединить временные данные в один объект (не включен в диаграмму ниже).

Первоначальная версия конвейера использовала синхронный API, предоставляемый нашей службой обработки изображений для шага 3. Этот API обработки изображений принимал запросы, содержащие объект списка с URL-адресами изображений. Для каждого запроса служба загружала изображения, изменяла их размер и загружала в S3.

Проблемы масштабируемости

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

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

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

  1. Пришлось дождаться завершения обработки изображения.
  2. Перегрузил процессор изображений и привел к его отказу.
  3. Не удалось восстановить изображения, потерянные в результате временных сбоев процессора изображений.

Надежная обработка изображений

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

  1. Ускоренная обработка объявлений: конвейер объявлений может запросить обработку изображений и продолжить ее последующие этапы.
  2. Стабильная загрузка запросов: обработчики изображений могли извлекать из очереди со скоростью, с которой они могли справиться.
  3. Горизонтальная масштабируемость: при резервном копировании очереди мы могли бы увеличить количество процессоров изображений, и они сразу же начали бы использовать из очереди запросов.
  4. Повторная обработка: процессор изображений может восстанавливаться после временных сбоев путем повторной постановки запросов в очередь.

Мы решили разместить наши очереди сообщений в нашей недавно созданной внутренней библиотеке публикации-подписки (PubSub), поддерживаемой SNS и SQS Amazon. В этой новой системе элементы очереди запросов и ответов заменяют запросы и ответы синхронной обработки изображений, отделяя обработку изображений от конвейера списков.

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

Наш первый новый компонент, обработчик запросов изображений PubSub, обрабатывает изображения из очереди запросов и помещает результаты обработанных изображений в очередь вывода. Наш второй новый компонент, обработчик ответов изображений PubSub, потребляет данные из очереди вывода и сохраняет обработанные результаты в коллекции изображений MongoDB для конкретных регионов. [1]

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

Внедрение и результаты

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

Перегрузка

9:00. Я иду на работу с кофеином и готов к работе. Я открываю свой компьютер и вижу значок уведомления Slack из моих кошмаров, одно новое сообщение от нашего менеджера по продукту: «Эй, что-то происходит с изображениями? Все новые показывают заполнитель «скоро».

Ой ой. Проверяю панель SQS - очередь запросов забита несколькими сотнями тысяч изображений. Я паникую на минуту, а затем пингую #devops:

Я начинаю свои скрытые (читай: Google Calculator) вычисления с целью: осушить очередь за час. При 100 000 запросов изображений листинга, ~ 20 изображений в листинге и 1 изображении в секунду на поток 60 потоков, работающих почти полный рабочий день, должны пройти через все изображения менее чем за час. Я запускаю четыре дополнительных компьютера c4.2xlarge EC2 (позднее в сообщении Slack добавлялось, что я запускал на две машины больше, чем первоначально запрашивал) и развертываю задание обработчика запросов на каждой. Я жду. Проходит час безумной проверки и перепроверки графиков Amazon SQS Cloudwatch, и очередь возвращается в нормальное состояние. Я выключил машины.

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

Тестирование

Хотя асинхронная обработка имеет некоторые преимущества, у нее есть обратная сторона. Наша новая система делает разработку менее удобной, потому что мы больше не можем запускать все локально. Для тестирования изменений, влияющих на обработку изображений, необходимо запустить два задания и убедиться, что в них используются правильные пространства имен SNS и SQS. [2]

Отладка

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

Плюсы

  • Мы можем отследить сбои до процессора изображений и избежать множества ошибок, вводящих в заблуждение.

Минусы

  • Мы не можем запрашивать или просматривать элементы в наших очередях запросов или ответов SQS, пока эти элементы не поступят в процессор изображений.
  • Для устранения сбоев может потребоваться перекрестная ссылка на журналы между процессорами изображений и их клиентами для восстановления временной шкалы событий.
  • Мы должны гарантировать, что настоящие сбои обработки изображений (например, невозможность загрузить изображение) не будут повторно помещены в очередь навсегда [3].

Последующие улучшения

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

Оглядываясь назад, можно сказать, что настройка предупреждений Amazon CloudWatch была очевидна. Эти предупреждения отправляют электронное письмо членам нашей команды, когда количество элементов в очереди превышает 100 000, это магическое число, которое мы обнаружили, чтобы сигнализировать о потенциальных проблемах путем проб и ошибок.

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

Выводы

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

Я поддерживаю оспариваемое решение, которое мы приняли, чтобы ограничить объем этого проекта. Переход с Mongo или обработки движущихся изображений на Python замедлил бы проект и повысил бы вероятность неудачи. Хотя новые функции и технологии волнуют меня так же, как и всех остальных, сохранение относительно узкого диапазона наших изменений позволило нам сконцентрироваться и глубоко понять требования и риски, связанные с ними.

Этот проект научил меня развитию, основанному на страхе, или тому, как позволить страху конструктивно управлять моими усилиями по развитию. До того, как приступить к работе над процессором изображений, я постоянно опасался, что его изменение или развертывание вызовет сбой. Иногда я даже получал синдром самозванца, задаваясь вопросом, означает ли тот факт, что я так боялся касаться операций ImageMagick в процессоре, что я не был «настоящим программистом».

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

Обязательный разъем для компаса

Если вам понравился этот пост в блоге и вы хотите поработать над вещами, подобными тому, что я описал, подайте заявку на работу в группу данных Compass!

Благодарности

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

Сноски

[1] Кстати, когда мы впервые представили эту часть проекта инженерам из других команд, некоторые возражали, что использование базы данных SQL упростит эти соединения. Это, несомненно, правда, но мы все равно проигнорировали это. Использование для этого преимуществ соединений SQL потребовало бы переноса наших коллекций списков на SQL в дополнение к этим новым коллекциям изображений. Это нарушает неформальное правило, о котором я упоминал ранее: максимально разделять инфраструктурные изменения (некоторые могут назвать это устаревшей версией кода хуже - лучше).

[2] Для поддержки модульного и сквозного тестирования компонентов, использующих PubSub, мы реализовали внутрипроцессные и локальные реализации PubSub, которые позволяют клиентам использовать PubSub без внешней зависимости от AWS.

[3] Это усложняется в случае временных сбоев, когда запросы необходимо повторно ставить в очередь после того, как поставщик данных исправляет некоторые изображения. Мы остановились на простом, но надежном решении, которое позволяет разработчикам вручную запускать конвейер приема, который включает загрузку и повторную обработку изображений для одного или нескольких списков. В старой системе, где отказы каскадировались, инцидент требовал запуска этого режима для сотен или даже тысяч списков. В новой системе, где сбои затрагивают определенные списки и не передаются каскадом, нам редко приходится прибегать к этому ручному решению.