За несколько лет работы в дата-аналитике мне довелось перелопатить тонны SQL-кода сотен витрин данных (под витриной данных я понимаю представление на SQL и кеш-таблицу, хранящую результат его исполнения). Порой бизнес-логика настолько сложна, что витрины состоят из целой цепочки последовательно выполняющихся запросов-представлений.
Вопрос отслеживания взаимосвязей витрин, их «происхождения» часто встает, когда нужно изменить логику или создать новую витрину по аналогии. Я уже описывал, как мы с коллегами облегчили поиск «предков» и «потомков» рассматриваемой витрины, создав простую визуализацию запроса, который находил связи объектов в системной таблице нашего аналитического хранилища.
Это решение приятно тем, что а) связи выстраиваются автоматически (неручная документация); б) можно выбрать любую конкретную таблицу и увидеть компактные метаданные о ней. Однако минусы так же очевидны, как и плюсы:
- нет графического представления связей, только табличное;
- возможно видеть только первый уровень «потомков» и «предков», более далекие уровни недоступны.
Постановка задачи
Попробуем сформулировать требования смелее и полнее, чем в прошлой «итерации»:
- Пара «представление + кеш-таблица» рассматривается как единый объект — витрина данных. Имя представления = префикс v_ + имя кеш-таблицы.
- Для исследуемой витрины должны выводиться все ее «предки» всех уровней до самого начала и все «потомки» до конца. Кроме того, нужна возможность регулировать количество выводимых уровней.
- Связи должны быть представлены в виде графа, в котором узлы — это таблицы и представления, а стрелки между ними — направленные связи.
- Должна быть возможность посмотреть метаданные любого объекта (схемы, описания и т. д.).
- Отображение должно быть интерактивным, то есть обеспечивать возможность изменения масштаба, перемещения узлов для удобства, вывода свойств выделенного объекта.
Решение
Требования у нас суровые, но нам не нужно изобретать велосипед. Ведь существуют специальные графовые СУБД, заточенные как раз под нашу задачу! Популярный их представитель — Neo4j, его-то мы и будем использовать для отрисовки нашего линеажного графа.
Предположим, наши витрины лежат в ClickHouse, стало быть, нам нужно «соединить» эти две СУБД. Именно поэтому решение состоит из следующих шагов:
- Разворачиваем Neo4j.
- Создаем таблицу всех связей между витринами, используя уже известный нам механизм обращения к system.tables.
- Экспортируем эту структуру из ClickHouse в Neo4j.
- Работаем с линеажным графом в Neo4j через интерфейс в обычном браузере. В этой СУБД используется свой язык запросов — Cypher. С его помощью можно выбрать исследуемую витрину, задать количество уровней и т. д.
Разберем эти шаги подробнее.
Установка Neo4j
Для тестовой итерации ставить Neo4j будем на локальную Linux-машину. Позже можно перенести на удаленную, чтобы доступ к линеажу имела вся команда.
Для развертывания Neo4j воспользуемся Docker. Выполняем команды:
sudo systemctl restart docker
docker run -d
--name neo4j
-p 7474:7474
-p 7687:7687
-e NEO4J_AUTH=neo4j/your_password
neo4j:latest
Если не вылезло ошибок, то открываем браузер по адресу http://localhost:7474 и видим приглашение ко входу в СУБД:
Вводим логин и пароль, указанные при установке, и видим интерфейс Neo4j:
Верхнее текстовое поле neo4j$ предназначено для ввода cypher-запросов.
Но база пока пуста. Перейдем к загрузке в нее структуры наших таблиц.
Передача структуры таблиц из ClickHouse в Neo4j
Скрипт, формирующий структуру связей между объектами в ClickHouse и отправляющий эти данные в Neo4j, можно скачать из репозитория. Основной его функционал описывается двумя классами:
1. ClickHouseLineageExtractor — извлечение из ClickHouse данных об объектах и их связях. Класс включает функции:
- __init__ — создание клиента для обращения к ClickHouse (библиотека clickhouse_driver);
- get_tables — извлечение метаданных обо всех таблицах и представлениях всех несистемных баз;
- get_columns — извлечение метаданных о полях этих объектов;
- get_dependencies — формирование зависимостей (связей) между объектами.
2. Neo4jLineageLoader — загрузка данных в Neo4j. Класс включает в себя методы:
- __init__ — создание клиента для обращения к Neo4j (библиотека neo4j);
- run_query — запуск запросов в Neo4j;
- load_schemas — загрузка схем, таблиц;
- load_tables — загрузка списка таблиц с их свойствами;
- load_columns — загрузка узлов колонок (да, у полей в нашем графе будут собственные узлы, привязанные к узлам соответствующих таблиц);
- load_dependencies — загрузка связей.
Этот скрипт можно запускать разово при необходимости либо поставить на расписание через любой оркестратор или шедулер. Любые изменения структуры таблиц в ClickHouse сразу отразятся в его system.tables, а значит, их подхватит скрипт. Далее данные в Neo4j будут перезаписаны обновленными.
Результат
После того как Neo4j благополучно установился, а скрипт экспорта структуры отработал, в браузере по адресу http://localhost:7474, залогинившись, мы можем запускать cypher-запросы. Например, запрос
MATCH path = (ancestor:Table)-[:FEEDS_INTO*]->(t:Table {name: 'accounts'})
RETURN path
покажет в виде графа всех «предков» витрины accounts:
Узлы можно помечать, передвигать, приближать и удалять. При выборе узла, соответствующего витрине или представлению, справа покажется перечень его свойств, различные метаданные, экспортированные нами из ClickHouse.
Вот некоторые другие запросы на языке Cypher:
// Ближайшие «родители» (1-й уровень)
MATCH (parent:Table)-[:FEEDS_INTO]->(t:Table {name: 'report_accounting_sa_money'})
RETURN parent, t
// Все «потомки» таблицы (lineage вниз)
MATCH path = (t:Table {name: 'my_table'})-[:FEEDS_INTO*]->(descendant:Table)
RETURN path
// Полный lineage (вверх и вниз)
MATCH path = (a:Table)-[:FEEDS_INTO*]-(t:Table {name: 'money'})
RETURN path
// Все таблицы схемы
MATCH (t:Table)-[:BELONGS_TO]->(s:Schema {name: 'datamart'})
RETURN t
// Таблицы без «родителей» (источники данных)
MATCH (t:Table)
WHERE NOT ()-[:FEEDS_INTO]->(t)
AND t.schema = 'datamart'
RETURN t
// Колонки таблицы
MATCH (t:Table {name: 'money'})-[:HAS_COLUMN]->(c:Column)
RETURN c ORDER BY c.position
Графовое представление линеажа позволяет не только быстро находить информацию об источниках и потребителях витрины, но и обнаруживать потенциальные изъяны в структуре, например:
- рекурсивные петли, когда витрина является одним из источников себя самой;
- лишние звенья и узлы, потерявшие актуальность;
- не описанные таблицы неизвестного происхождения (да, бывает и такое) и ненадежные источники и т. д.
Резюме
Линеаж витрин данных в виде наглядного интерактивного графа можно реализовать путем экспорта структуры объектов и связей между ними из аналитического хранилища в графовую СУБД. Мы рассмотрели скрипт, который выгружает модель данных из ClickHouse и загружает в Neo4j.
Работать с графом в Neo4j можно, запуская типовые cypher-запросы и управляя полученным графом в пользовательском интерфейсе прямо в браузере.
Такая визуализация значительно упрощает и ускоряет исследование модели данных.