
Умный в гору не пойдёт, умный гору обойдёт. А если это гора пользовательских данных? Тогда тоже обойдёт — но не в смысле «вокруг», а буквально заглянет под каждый камень, чтобы ничего не упустить. Разберём в статье, как быстро и эффективно обойти данные, если требуется, к примеру, перенести их в новое хранилище или сделать анализ содержимого.
Меня зовут Андрей Баталов, я старший программист в VK Музыке. Расскажу об эффективных приёмах, которые помогают нам в переобходах аудиообъектов и других данных всех пользователей ВКонтакте.
Итак, представим ситуацию: все новые аудио, которые попадают в систему VK Музыки от пользователей, проходят флоу обработки. В него добавляется этап, который предоставляет новые функции. Они будут доступны только для загружаемых аудио. Чтобы новые функции появились и у ранее загруженных файлов, нужно обойти их и применить к ним добавившийся этап обработки. Иными словами, требуется запустить некий модуль переобхода, который отправит все старые данные на обработку. Отмечу пару её особенностей: она занимает несколько секунд для одной аудюшки и происходит асинхронно.
Вот стартовая схема этого процесса, по ходу статьи будем её совершенствовать:

Во время работы над задачей мы фокусировались на двух моментах:
нельзя нарушать обработку новых поступающих файлов;
нужно поскорее получить значимый эффект от переобхода.
Пойдём по порядку: вот какие приёмы помогали совершить такой переобход.
Приём 1. Выключатель переобхода
Человеку свойственно ошибаться. Но у ошибок есть приятное качество: большую часть из них можно исправить. Если стало понятно, что в процессе переобхода что‑то упустили, недочёт необходимо устранить. Но это займёт какое‑то время. Чтобы выиграть это время, нужно выключить переобход. Так это выглядит на схеме:

Выключатель должен срабатывать мгновенно и быть в лёгком доступе у ответственного за переобход. Это сэкономит кучу нервов и времени на исправления.
Приём 2. Очередь-посредник
Если для переобхода используются те же мощности, что и для работы основной функциональности, то может произойти замедление или полная остановка обработки текущего потока задач. Лиха беда начало! Есть ещё один приём, чтобы и с этим справиться.
Можно создать очередь‑посредника для задач из переобхода и настроить её так, чтобы она использовала ресурсы процессинга только в рамках выделенной квоты. Кстати, этот приём помогает сбалансировать нагрузку и в других задачах, связанных с использованием общего ресурса. Взглянем на актуализированную схему:

Приём 3. Rate Limiter
Когда при переобходе обнаруживаются ошибки, в нашем первом приёме предлагается использовать выключатель. Но бывает и противоположная ситуация. Переобход работает даже лучше, чем ожидалось: производится поток запросов, который система не в силах обработать. В таком случае хочется цитировать персонажа легендарного «Брата»: бери ношу по себе, чтоб не падать при ходьбе.
Такой механизм, как Rate Limiter, позволяет ограничить поток событий в единицу времени. Он широко применяется в разработке: например, можно ограничить как доступ к API, так и количество сообщений в личку. Чтобы обуздать поток задач из переобхода, он тоже отлично подойдёт. Вот схема с включением Rate Limiter:

Часто реализация Rate Limiter основывается на механизмах скользящего или фиксированного окна. Рассмотрим простой пример на основе фиксированного окна с использованием хранилища Redis. Обратите внимание, это не production‑ready код, а иллюстрация:

О разных способах реализации Rate Limiter можно почитать в статье «Рейт‑лимиты с помощью Python и Redis». Там описаны интересные подходы.
Приём 4. Исключение дублей
Представьте ситуацию: существует система с множеством аудиообъектов, у каждого своё название, владелец, указатель на аудиофайл. Но, приглядевшись, мы видим, что часть из них созданы на основе одного файла: у них разные названия, разные владельцы, но аудиодорожка одна и та же. В таком случае при переобходе не нужно обрабатывать абсолютно все аудиообъекты в системе. Если важна только аудиодорожка, то другую информацию можно игнорировать. Таким образом, в данной ситуации в качестве ключа переобхода разумно использовать не идентификатор аудиообъекта пользователя, а идентификатор аудиофайла в системе:

Процесс станет значительно быстрее, если не нужно будет делать одну работу несколько раз.
Приём 5. Блокировка
Обработка происходит не мгновенно. Пока она не закончилась, может нападать куча запросов с одинаковым контентом. Чтобы этого избежать, нужно отклонять новые повторяющиеся запросы, если задача уже в процессе выполнения. Такой приём можно назвать блокировкой, а механизм, который его предоставляет, — Locker.
Так же, как и Rate Limiter, Locker активно используется в серверных приложениях. Действует просто: перед тем как взять задачу в работу, совершается попытка блокировки. Если она успешная, можно продолжить обработку, а если завершилась неудачей — кто‑то уже взял задачу в работу и текущую обработку нужно прекратить. На схеме это выглядит так:

Иллюстративный пример блокировки с помощью Redis:

Приём 6. Ретроспективная подготовка списка горячих данных
Для инициирования переобхода нужна веская причина, так как процесс трудоёмкий, длительный, а если допустить ошибки, может нарушиться стабильность работы системы. Например, если переобход нужен, чтобы перенести какие‑то данные в новое хранилище, которое будет быстрее их отдавать пользователям, то логично начать с тех, которые чаще всего извлекаются, — то есть с горячих данных. Тогда эффект от внедрения нового хранилища наступит быстрее и больше пользователей заметят ускорение. Выделить горячие данные можно, изучив статистику и логи. Так выглядит обновлённая схема:

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

Это как Rate Limiter наоборот: фильтр будет пройден только после определённого количества попыток. Например, можно настроить порог так, чтобы в обработку пропускались лишь треки, которые послушали больше 1 000 раз в день. По мере обработки горячих данных этот порог нужно снижать, чтобы переходить к менее популярным.
Заключение
В статье удалось собрать семь универсальных приёмов для переобхода большого набора данных, которые мы в VK Музыке успешно применяем на практике. Они помогают изолировать другие работающие элементы системы от влияния переобхода, а также скорее получить эффект от самого процесса переобхода.
Вероятно, многие читатели Хабра сталкивались с задачами переобхода и повторной обработки данных. Поделитесь в комментариях: как вы организовывали этот процесс и какими приёмами пользовались?
Akina
Что-то не вижу я разницы между пунктами 4 и 5. И там, и там имеется два задания, желающие обработать один и тот же файл, и второе задание блокируется. Всей разницы - в пункте 4 блокирование задания 2 выполняется до начала выполнения первого задания, а в пункте 5 уже в процессе выполнения.
Также не очень понятно по пункту 3 и пункту 7. Полное ощущение, что все эти операции вполне способен выполнять диспетчер потоков обработки по пункту 2. Причём в таком случае приоритезация, контроль загрузки и регулирование потока выполняются в одной точке, там, где непосредственно получаются все сведения для такой диспетчеризации.
Новый файл? высокий приоритет. Горячий? нормальный приоритет. Всем остальным - низкий.
Есть ресурсы на обработку, не выбираем производительность? Плюс поток. Зашиваемся, нехватает производительности? минус поток.
andrey270 Автор
Привет!
Первый коммент, класс, попробую ответить.
Если вы не примените прием 4 (исключение дублей) - то приема 5 (блокировка на время выполнения) будет недостаточно. Представим ситуацию, когда есть 2 разных (id отличается) аудиообъекта с одним и тем же (одинаковый id) аудиофайлом. Если вы выбрали ключем переобхода id аудиообъекта, а не id аудиофайла (т.е. пренебрегли приемом 4, и выбрали ключ, который не исключает дубли) – то переобход выполнит обработку 2 раза для одного и того же аудиофайла: первую задачу поставит аудиообъект 1, а вторую – аудиообъект 2. А если бы выбрали действительный ключ переобхода (id аудиофайла) – то обрабатывать пришлось бы всего один раз.
По поводу пунктов 2 (очередь-посредник), 3 (rate limiter) и 7 (пороговая фильтрация). Если механизм очередей, который намерены использовать в системе может брать в очередь задачу с i-го запроса (допустим, с 10-й попытки), может допускать в очередь не больше j задач в минуту/час/день, и может обрабатывать не больше k задач в минуту/час/день – то да, можно оставить все три этих приема на откуп такой системе. Но от этого приемы не перестанут существовать, а просто их реализация будет выполнена в подсистеме очередей.
По поводу управления с помощью приоритетов – тоже отличный вариант, спасибо, что упомянули!
Akina
Вот не понял... через что бы не была поставлена задача, но реальная обработка-то всё равно будет выполняться для аудиофайла. И если пропускать и не контролировать дублирование аудиообъектов, а отсечение дубликатов делать только на стадии формирования очереди заданий на обработку файлов, то эффект будет совершенно тот же. Все дубликаты всё равно будут "схлопнуты". Но за один проход, а не за два, как вы предлагаете.