
Apache Superset — востребованное open-source решение для анализа данных, которое можно быстро установить и встроить в существующий технологический стек компании благодаря большому количеству коннекторов и видов визуализаций. Однако для высоконагруженных систем и сложных сценариев некоторые компании дорабатывают исходную версию — например, внедряют инструменты автоматического кеширования и оптимизируют архитектуру хранения данных для построения графиков. По этому пути в своё время пошли и мы в VK.
Привет, Хабр. Меня зовут Никита Романов, лидирую разработку аналитических инструментов VK. В этой статье расскажу о нашем опыте оптимизации Apache Superset под свои задачи.
Про BI в VK
Мы в VK применяем data-driven подход, то есть принимаем решения на основе анализа данных и фактов. Аналитика позволяет нам отслеживать состояние процессов, оптимизировать продукты и сервисы, управлять рисками и безопасностью, улучшать пользовательский опыт и не только.
Раньше многие команды VK использовали разные BI-инструменты, исходя из собственных потребностей. Но у этого есть обратная сторона — получается, что мы работаем с множеством разных технологий. В таком разнообразии проблематично ориентироваться и разбираться.
Сейчас от множества систем осталось всего три: Superset, Redash, DataLens. Но и на этом не останавливаемся: объединяем их в одну, которая будет работать под капотом нашего решения VK Horizon.

Нам важно, чтобы сбор и аналитика данных приносили пользу и помогали находить точки роста продуктов и внутренних метрик. Поэтому мы замеряем, как команды используют BI-инструменты внутри VK.
Так, 55% целевых сотрудников регулярно (зачастую — еженедельно) используют наши BI-решения, и эта метрика постепенно растёт.
Причём они не только просматривают уже существующие дашборды, но и активно создают новые под свои задачи. Сейчас соотношение creator-viewer за месяц — 1 к 8, но при сохранении трендов оно может уменьшиться до 1 к 6.
Несмотря на то что у нас несколько основных движков, в рамках статьи я сосредоточусь на инсайтах из опыта работы с Superset.
Стабильность дашбордов
Нам важно, чтобы дашборды работали стабильно и оперировали актуальными данными, которым можно доверять. С какими причинами недоступности дашбордов мы сталкиваемся чаще всего:
Источник данных может быть перегружен, если не рассчитан на большое количество запросов

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

В Superset есть Jinja — шаблонный движок для Python, который нужен для динамического формирования SQL-запросов и построения визуализаций. Он позволяет встраивать выражения, условия и значения переменных в шаблоны SQL, что делает работу с большими наборами данных более гибкой. Но нюанс в том, что особые значения фильтра могут сломать SQL с Jinja

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

Качество дашбордов и процессы их подготовки
Данные на дашбордах могут быть необъективными, также их может быть недостаточно, чтобы принять решение.
Теоретически это можно исключить, определив ответственного за данные и дашборды. И чем меньше специалистов задействовано в пайплайне их подготовки, тем меньше потенциальных проблем и недопониманий.
Сейчас каждая команда может выстроить процесс в соответствии со своими потребностями. Например, раньше у некоторых команд было 5 этапов:
заказчик отдаёт ТЗ аналитику
аналитик обращается к дата-инженеру для построения витрин данных
дата-инженер формирует таблицу и отдаёт аналитику
аналитик составляет отчёт и отдаёт результаты заказчику

Это логичный путь. Но у него есть нюанс — time to market (TTM) отчёта в таком случае может составлять недели. Для продукта это критично, поскольку у нас динамическая среда — и при тестировании гипотез и подготовке новых релизов нам важно опираться на актуальные данные.
Чтобы оптимизировать процесс, мы допускаем исключение из этой цепочки дата-инженера: так он может сфокусироваться на основных бизнес-задачах и процессах. Формировать виртуальные таблицы начал аналитик. Согласований и ожиданий стало меньше, то есть ускорился time to market.

Можно упростить процесс ещё сильнее. Например, если заказчик умеет писать SQL, то мы исключим из цепочки не только дата-инженера, но и аналитика. Но такой self-service возможен только в условиях высокого уровня Data Governance, к которому мы ещё идём.

В итоге получается, что семантический слой DWH (Data Warehouse) часто хранится в виртуальных SQL-датасетах BI-системы. То есть TTM больше зависит не от количества участников и ответственных, а от качества данных, дашбордов и оптимизации системы хранения датасетов. Последнюю задачу мы и решали.
Модель данных в Superset
Чтобы пояснить суть наших действий, разберём модель данных в Superset.
Она довольно простая:
дашборды строятся по графикам
графики строятся на основе двух типов датасетов: физических и виртуальных
датасеты формируются на основе данных из источников

Соответственно, датасеты с контекстом чарта формируют SQL-запросы и отправляют их в источники для получения нужных данных.

Во многих BI-системах, в том числе Superset, есть встроенное кеширование. Благодаря этому получить данные, которые уже есть в кеше, будет значительно бы��трее. Например, среднее время выполнения условного запроса к БД — 14 секунд, а среднее время загрузки из кеша — 0,8 секунды.

Есть нюанс: по нашим оценкам, обычно к БД направлены до 85% ежедневных запросов, и только в 15% случаев данные берутся из кеша. Во многом это происходит потому, что данные попадают в кеш, только если к ним кто-то уже обращался за последние сутки. Но у заказчиков могут быть разные контексты и потребности, и возможны ситуации, когда нужных данных может просто не оказаться в кеше. Поэтому мы решили прокачать работу кеша.
Первый этап: кеширование графиков по расписанию
Чтобы исключить случайное попадание данных в кеш, мы решили настроить кеширование графиков по расписанию, то есть делать это принудительно. Оптимально это делать на бэкенде, но так, чтобы не перегружать BI-систему и подключённые базы данных.
При этом мы настроили отправку Alerts&Reports в VK Teams. Благодаря этому аналитики или другие пользователи узнают, что в кеше появились актуальные данные, дашборд обновлён.

Но этого оказалось недостаточно. Настройки кеширования по расписанию фактически сводятся к тому, что нужно указать время, когда выполнять прогрев. Но идеальный сценарий для нас — сначала наполнить витрины данными и только после запускать их прогрев. Мы выстроили логику запуска прогрева данных при помощи корпоративного оркестратора OneFlow через API и сенсор ожидания полных данных в таблицах-источниках. В результате получили такой пайплайн:
get_tables: получаем источники дашборда
sensor: ожидаем сборку источников
warm_up: на основе статистики просмотров чартов прогреваем каждый график в топ-10 популярных значений фильтров

Уже на этом этапе мы получили буст по проценту попадания запросов в кеш и скорости загрузки. В кеш стали обращаться 26% запросов (вместо 15%), а скорость загрузки выросла на 12% (10,6 секунды против 12 секунд).
Метрика | Было | Стало |
Cache Miss | 85% | 74% |
Avg Cache Load | 0,8 с | -//- |
Avg Query Load | 14 с | -//- |
Avg Load | 12 c | 10,6 с |
Даже после этого оставались точки роста, которые можно было дополнительно оптимизировать. Для начала мы решили улучшить метрику Avg Cache Load, то есть среднее время загрузки данных из кеша.
Второй этап: ускоряем доступность данных из кеша
В кеше хранятся снапшоты данных, поэтому в теории ничто не мешает отдавать их без задержек. Но они всё же возникают, хоть и незначительные — те самые 0,8 с.
За это время система проверяет, можно ли конкретному пользователю отдавать запрашиваемый кеш. Она делает вывод на основе table-based access model (модели доступа на основе таблиц). Это можно было оптимизировать.
Основная сложность — нам нужна проверка доступа пользователя к закешированным данным без похода во внешнюю систему, где эти доступы хранятся. Алгоритм проверки доступа должен быть максимально точным и работать быстро.
Изначально мы использовали sqlparse как библиотеку для извлечения таблиц из SQL. Но в рамках оптимизации решили заменить её самописным решением. Многие отчёты у нас строятся на ClickHouse, поэтому нужно оптимизировать процессы для него. В результате разработали VK clickhouse-query-parser — библиотеку на Python. Она возвращает AST от SQL-запроса, на основе которого можно быстро извлечь используемые таблицы. VK clickhouse-query-parser использует бинарник, собранный из оригинального парсера ClickHouse. Поэтому наш парсер никогда не ошибается.
Мы сравнивали своё решение с SQLGlot, чтобы протестировать его. Прогоны показали, что SQLGlot иногда падает или неправильно находит используемые таблицы.

VK clickhouse-query-parser использует бинарный файл, собранный из C++ — это даёт стабильно высокую производительность.

С переходом на собственный парсер мы смогли выкрутить на максимум среднюю скорость загрузки данных из кеша: значение сократилось с 0,8 до 0,1 секунды.
Метрика | Было | Стало |
Cache Miss | 85% | 74% |
Avg Cache Load | 0,8 с | 0.1 с |
Avg Query Load | 14 с | -//- |
Avg Load | 12 c | 10,6 с |
Осталось найти способ улучшить метрику Avg Query Load, то есть ускорить среднее время выполнения запросов. Важно было сделать это за счёт оптимизации системы хранения, а не переписывания запросов пользователей.
Третий этап: повышение производительности выполнения запросов
Несмотря на то что есть кеш, нам всё равно часто нужно обращаться к базе данных. У нас много дашбордов — 769 основных, а всего более 6 000 — и комбинаций контекстов для каждого, поэтому всё уместить в кеше Redis просто невозможно.
Нам нужно было решение, которое позволит прогревать много дашбордов, но при этом не перегрузит источник. И для которого не нужно хранить терабайты данных в Redis.
Мы проанализировали инструменты и выбрали VK ClickHouse API. Это наша библиотека-драйвер над ClickHouse, в которой есть упрощённая реализация MapReduce. В ней первый запрос выполняется на этапе map, а результат сохраняется во временную таблицу. Второй запрос, уже к полученной материализации, исполняется быстро благодаря предварительной обработке. Он сводит данные на этапе reduce.
К тому же у VK ClickHouse API есть много полезных фич:
контроль коннектов к кластеру — позволяет не перегрузить источник
сенсоры ожидания сборки таблиц-источников — защищают от использования неполных данных
высокая отказоустойчивость над ClickHouse — при сбое одной реплики он переключается на другую этого же шарда

Чтобы драйвер было удобно использовать, мы вынесли его в интерфейс Superset как опцию, которую в любой момент можно включить или выключить.
Покажем на примере, насколько эффективно работает VK ClickHouse API.
Допустим, у нас есть запрос, который выполняется 5 минут. Или не выполняется вообще и завершается по таймауту.

Но после включения CH API он выполняется меньше чем за минуту.

Материализация собирается и складывается в ClickHouse. Затем начинается стадия reduce: при создании визуализации (Chart) источником будут предварительно подготовленные данные — запрос идёт к ним. Запросы для построения графика (Chart SQL) идут напрямую к готовым экстрактам. В интерфейсе мы помечаем, что сборка идёт на основе экстракта.

Запрос визуализации также становится проще и короче. Мы используем FROM $table, чтобы указать, что применяем материализацию.

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

Подготовка экстрактов датасетов помогает нам сократить среднее время выполнения запросов с 14 до 8 секунд. Также увеличилась и средняя скорость загрузки дашбордов на 43% — она составила 5,9 секунды вместо 12.
Метрика | Было | Стало |
Cache Miss | 85% | 74% |
Avg Cache Load | 0,8 с | 0,1 с |
Avg Query Load | 14 с | 8 с |
Avg Load | 12 c | 5,9 с |
Схема работы экстрактов в VK Superset
Мы получили такой общий пайплайн:
Пользователь, открывая дашборд, часто попадает сразу в кеш: дефолтный контекст почти всегда есть в Redis Cache
При изменении внешнего фильтра используем высокодоступную материализацию в ClickHouse. Это позволяет отработать запрос за несколько секунд
Для динамических SQL-датасетов (с Jinja) нужно обращаться в ClickHouse-источник
Если нужны самые свежие данные — нужно настроить их репликацию в ClickHouse из распределённой системы хранения и обработки больших объёмов структурированных данных.
При этом каждый этап можно ставить на расписание в оркестраторе OneFlow, что позволяет отслеживать работу всей схемы.

Важно, что использование VK ClickHouse API в нашей реализации — опция. Пользователи по желанию могут выбирать между двумя вариантами пайплайна:
BI — cache — ClickHouse
BI — cache — VK CH API — ClickHouse
Краткое послесловие
Apache Superset — универсальный инструмент благодаря большому набору базовых функций и возможности гибкой кастомизации. На мой взгляд, этот опыт показывает: чтобы повысить производительность решения, не обязательно значительно менять культуру разработки витрин данных и ограничивать построение дашбордов только аналитиками. Оптимизировать можно и работу самой системы. Мы добились двойного буста по скорости подготовки и отображения данных и продолжаем оптимизировать Superset.
Сталкивались ли вы с медленной загрузкой дашбордов? Если да, как боролись с этим?
