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

Как показано в приведенном выше примере для Lightroom для настольных ПК, когда вы вводите «f» в поле поиска Lightroom, автозаполнение предлагает варианты завершения, в том числе:

  • «Флаг» — имя фасета, начинающееся с буквы «f».
  • Значения фасета, содержащие «f», такие как местоположение, тип объектива и значение диафрагмы.

Автозаполнение особенно полезно на мобильных устройствах, как показано ниже, поскольку оно снижает усилия, необходимые для выполнения поиска на небольшом устройстве, где ввод может быть затруднен. Как для мобильных, так и для настольных пользователей это огромная экономия времени. В этом посте мы расскажем, как команда Adobe Sensei & Search объединилась с командой Lightroom для реализации автозаполнения в Lightroom, и прольем свет на проблемы масштабирования, связанные с индексацией всех аспектов актива для такого популярного приложения.

Разбираемся с автозаполнением в Lightroom

В целом существует два основных типа автозаполнения:

  • Автозаполнение на основе пользовательских запросов. Результаты не персонализированы и основаны на наиболее частых запросах пользователей продукта и количестве поисковых запросов. Результаты не обязательно адаптированы для конкретного пользователя.
  • Автозаполнение на основе метаданных.Результаты персонализированы и основаны на контенте конкретного пользователя и метаданных объекта. Результаты подбираются специально для каждого пользователя, и каждый пользователь может получить разные результаты для одних и тех же запросов. Такой тип автозаполнения используется в Adobe Lightroom.

В Lightroom пользователи могут ввести три категории вещей и получить следующие завершения:

  • Значения метаданных
  • Значения фасета
  • Имена фасетов

Далее давайте рассмотрим пример распространенного варианта использования автозаполнения в Lightroom:

Аспект местоположения

Как показано ниже, просто начните вводить «местоположение» в поле поиска, и автозаполнение Lightroom предложит места, где были сделаны ваши фотографии, что позволит вам легко найти все фотографии, связанные с местом или недавней поездкой.

Проблемы, связанные с автозаполнением в Adobe Lightroom

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

Объем данных, которые Lightroom генерирует в процессе производства, огромен. Каждое событие может быть чем угодно: от нового объекта (фотографии или медиафайла, который загружает пользователь), до изменения метаданных объекта или просто применения фильтра к существующей фотографии. Каждое из этих действий создает событие, которое отправляется в хранилище данных HBase. У Lightroom много клиентов, и у каждого клиента может быть от нескольких тысяч до миллионов активов в случае профессиональных клиентов. Таким образом, вы получаете представление о масштабе, в котором необходимо выполнять это агрегирование. Масштаб увеличится еще больше, как только вы поймете, как мы распределяем эту информацию, как описано в следующем разделе.

Конвейер пакетного приема

Конвейер Storm обрабатывает все данные Lightroom, поступающие в результате изменений метаданных ресурсов (вставка/удаление), и заполняет индекс поиска Elasticsearch. Apache Storm — это распределенная система вычислений в реальном времени, которая упрощает надежную обработку неограниченных потоков данных.

Storm также записывает эти данные в хранилище данных HBase, называемое хранилищем контента. Apache HBase — это крупномасштабное распределенное хранилище данных «ключ-значение», и данные, находящиеся в нем, действуют как «источник правды» для автозаполнения Lightroom и отправная точка конвейера пакетного приема.

Затем задание Spark извлекает данные из хранилища контента, вычисляет агрегированные показатели, прежде чем окончательно записать их в индекс Autocomplete Elasticsearch. Apache Spark предоставляет возможности как пакетной, так и потоковой обработки и идеально подходит для масштабируемой распределенной аналитики. Он используется для задач от приема пакетных данных для аналитических конвейеров до эффективного крупномасштабного объединения функций для приложений машинного обучения.

Каждое событие актива содержит всю возможную информацию об активе и хранится в хранилище данных HBase. Пользователи могут выполнять поиск в своих коллекциях ресурсов в Lightroom, используя базовые метаданные и фасетные текстовые метаданные, как описано ниже.

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

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

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

Проблемы масштабирования

Основным хранилищем данных является HBase, хранилище с распределенным значением ключа, поэтому оно подходит для чтения с произвольным доступом и помогает быстро извлекать определенные записи на основе определенного ключа строки. Он предлагает гарантии высокой пропускной способности. Несмотря на это, для нашего варианта использования нам пришлось сделать большое количество чтений. Чтобы прочитать неизмененные данные из таблицы базового содержимого (иногда это может быть в диапазоне 100 000 или более). После определенного момента (определенного количества записей) мы заметили значительное падение производительности при извлечении HBase. После 500 000 ключей фильтрация HBase с использованием ключей строк стала чрезвычайно медленной, и нам пришлось оценить количество ключей строк, которые мы могли бы эффективно отфильтровать за один раз.

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

Запись на S3

Мы рассмотрели идею замены хранилища контента HBase всеми данными в корзине Amazon S3 и сохранения данных, разделенных на уровне пользователя, в формате паркета. S3 — это единая система хранения плоских BLOB-объектов, в которой нет понятия структуры каталогов файлов, поэтому мы столкнулись с ограничениями скорости S3 при записи из задания Spark. В настоящее время S3 поддерживает 3500 запросов PUT/POST/DELETE в секунду.

Другая проблема заключается в том, что при попадании в S3 с высокой скоростью некоторые участки этого большого двоичного объекта хранилища могут стать «горячими», в то время как другие останутся холодными (нетронутыми), из-за чего мы начали видеть удаление записей. Это происходит потому, что данные не записывается единообразно и зависит от того, как распределены данные (активы) для различных пользователей. Таким образом, эти горячие точки приводили к потере данных. Поэтому мы решили переосмыслить способ разделения данных и вместо этого записать их в HDFS.

Перекос данных

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

Обновления в паркете

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

Паркет – столбчатый формат хранения, к преимуществам которого относятся:

  • Быстрое чтение за счет сжатия и поддержки обрезки разделов
  • Быстрая фильтрация: проекция столбца и проталкивание предиката

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

Регулирование Elasticsearch

Регулировать операции записи в кластер Elasticsearch из задания Spark сложно. При индексации тегов мы столкнулись с проблемой массовых отказов от кластера. Крайне важно найти способ ограничить эти записи, чтобы защитить кластер. Это можно сделать двумя способами:

  • Контроль количества одновременных операций записи (запущенных исполнителей Spark и количества потоков задач на каждый исполняющий элемент).
  • Управление размером пакета каждой записи, что помогает контролировать количество документов, записываемых в данный момент времени в каждом запросе.

Основные выводы из внедрения автозаполнения в Lightroom

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

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

Кроме того, автозаполнение Lightroom теперь также позволяет вам напрямую находить фотографии определенных людей из вашей коллекции фотографий, предлагая варианты имен людей в вашей коллекции.

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

Для получения дополнительных примеров работы, которую команда Adobe Sensei & Search выполняет для расширения интеллектуальных функций в нашем наборе продуктов, посетите Центр Adobe Sensei в нашем техническом блоге и ознакомьтесь с Adobe Sensei в Twitter. для получения последних новостей и обновлений.