Привет! Меня зовут Наташа Красильникова, я старший аналитик команды Operations Analytics в Skyeng. Мы работаем с сотней сервисов компании на уровне данных, и когда пришло время мигрировать наше многолетнее легаси на новый DWH… что ж, мы пережили целый спектр острых эмоций. И, конечно, выработали ноу-хау, чтобы сделать переход менее болезненным. В статье расскажу, что придумали и как применяли.
Как жили до миграции
Наша компания достаточно крупная: несколько тысяч сотрудников, включая более 50 аналитиков. Обычно в больших компаниях сложная и структурированная Data Governance (система управления данными), есть выделенные команды для хранения данных, системного анализа.
У нас же был иной путь: долгое время мы обходились множеством разрозненных технических мониторингов того, как живут данные. Например, были мониторинги по состоянию таблиц и их размеру. Но все это существовало локально и приносило много головной боли.
В нашей команде мониторингами занимался один аналитик — по собственной инициативе и чтобы облегчить себе работу. Когда возникла потребность переезда на новое хранилище, задумались, как сделать это полноценным мета-анализом и решить несколько насущных проблем.
Вот главные из них:
Проблема первая — мы не понимали, какими аналитическими материалами у нас пользуются, а какие простаивают без дела. Из-за этого страдала приоритизация задач и было непонятно, как составить роадмап перевоза всех наших отчетов. Заказчики просто приходят и говорят: «Ребята, задача ну очень важная». Свято верили, так как проверить это на цифрах не могли.
Проблема вторая — мы плохо понимали зависимости данных в наших пайплайнах. Если добавлялся новый сервис и нужно было долить данные в отчет, то приходилось тратить уйму времени. Мы садились и прокликивали вручную пайплайн, чтобы понять, какие цепочки ведут к отчету и как изменится конечная бизнес-метрика. Это большой риск получить неконсистентные данные и дергающийся глаз заказчика, у которого за ночь метрика упала на 50%. Все в огне, нужно срочно выделять ресурс аналитика, чтобы разрулить.
Третья проблема — еще на этапе подготовки к миграции нужно было выгрузить все интеграции с сервисами, которые используем. На нашей команде интеграций десятки, пайплайнов несколько сотен. Никто не был рад предстоящей монотонной работе по выискиванию этих коннектов и ручному заполнению гигантского гугл-дока.
Поехали разбираться с ключевыми действиями.
Шаг первый — анализ метаданных из BI-сервиса
Для старта мы решили разобраться с проблемой отслеживания того, какими отчетами ребята в компании пользуются, а какими нет. И чтобы больше без разрозненных мониторингов. Для этого я воспользовалась открытой базой данных с метаданными Tableau.
Я собрала дашборд в Redash (опенсорсный браузерный BI), в котором показала: кто, когда, какими отчетами пользуется. Вывели топ департаментов, топ заказчиков, топ отчетов. Заодно устроили шуточное награждение заказчиков за любовь к аналитике.
Дальше я сделала интерактивный дашборд: заходишь и крутишь данные под себя.
Можно было, например, посмотреть топ по определенному департаменту:
Или по нужному дашборду:
Шаг второй — разбираемся с источниками Ad hoc запросов
Чтобы провести полную миграцию данных на новый DWH, нам нужно было проанализировать все Ad hoc отчеты, почистить те, которыми уже никто не пользуется, и главное — понять, из чего эти Ad hoc состоят.
У нас существовал локальный отчет, который вел аналитик для себя. Я расширила его на всю команду, чтобы решить вопрос глобально, а также оценить ресурс, требуемый для миграции всех данных. Нужно было представить артефакт в удобном и понятном виде.
Забегая немного вперед — именно эта визуализация стала незаменимым инструментом, когда в активной фазе миграции команда стала пересобирать витрины. Отчетом начали пользоваться все аналитики в команде и даже ребята из других команд.
Визуализация выглядела так:
Можем посмотреть все зависимости от родительского элемента к дочерним. Это помогает оценить изменение бизнесовой метрики, когда доливаем новый сервис.
Можно построить и наоборот — от потомка:
И посмотреть, кто участвует в его создании. Это нам понадобилось при переезде в новое хранилище: смотришь на конечную витрину и понимаешь, что она зависит от 150 таких-то табличек.
Там же, в Redash, живут наши Ad hoc запросы. Когда я их выгрузила, то оказалось, что у нас на команде 793 (!) отчета.
Шаг третий — автоматизируем сбор списка подключений
Еще в момент подготовки к масштабной миграции, руководитель всей горизонтали аналитики пришел в команды с запросов: нужно составить список всех табличек, которые мы забираем из сервисов.
В нашей команде 14 человек, мы забираем данные из десятков сервисов и интегрируем много локальных документов с бизнесовыми маппингами, таргетами, а иногда и просто какими-то дополнительными данными. Делать это вручную означало спустить на монотонный ручной труд как минимум половину спринта. Меня такая перспектива не устраивала, поэтому я придумала простой алгоритм, как можно автоматизировать задачу.
Нужно было просканировать код всех наших табличек на предмет определенной маски. В любом SQL запросе будет секция from, для соединения почти везде мы используем left join. На старом хранилище у нас было всего три слоя данных, так что в маску были также добавлены названия этих слоёв.
Здесь у меня уже не хватило бы скиллов реализовать все на Python, поэтому отдала реализацию другому аналитику. Сформулировали ТЗ и написали мини-версию раскладывания кода на составляющие части, чтобы он выделял то, что нам нужно и складывал в эксель-табличку в том формате, в котором требовала от нас команда миграции. Для этого хватило грамотно составленного регулярного выражения и нескольких проверок корнер-кейсов. Например, когда данные забирались по API или из Google Docs.
Когда собрали алгоритм, то автоматизировали кучу часов ручного труда.
Код получился следующий:
from os import listdir
import re
import csv
from tqdm import tqdm
result = []
for file_name in tqdm(listdir()):
if file_name[-3:] == '.py' and file_name[1:] != '__':
with open(file_name, 'r') as content:
text = content.read()
with_pattern = r"(?:,|WITH)\s+(\w+)\s+AS\s+\(.+?\)"
from_pattern = r"[^#](?:select)(?:.+?from\s+)(\"[a-zA-Z0-9_\-\.\"]*?\" |\w+\.*\w+\.*\w+\.*)(?=[\, \n])"
join_pattern = r"[^#](?:select)(?:.+?join\s+)(\"[a-zA-Z0-9_\-\.\"]*?\" |\w+\.*\w+\.*\w+\.*)(?=[\, \n])"
hook_pattern = r"(?:FetchDBSourceOperator\(.+DbHook\.)(.+?)(?: |,)(?:.+?\))"
user_pattern = r"(?:(?:(?:Entity|Report)BuilderTemplate|SkyengDAG)\()(?:.*?User\.)(\w+)(?:\,)"
with_expressions = re.findall(with_pattern, text, re.DOTALL)
from_expressions = re.findall(from_pattern, text, re.DOTALL | re.IGNORECASE)
join_expressions = re.findall(join_pattern, text, re.DOTALL | re.IGNORECASE)
hook_expressions = re.findall(hook_pattern, text, re.DOTALL | re.IGNORECASE)
user_expressions = re.findall(user_pattern, text, re.DOTALL | re.IGNORECASE)
sources = [i for i in from_expressions + join_expressions if i not in with_expressions]
result.append({'name': file_name,
'user': ', '.join(user_expressions),
'hooks': ', '.join(hook_expressions),
'sources': ', '.join(sources)})
field_names = ['name', 'user', 'hooks', 'sources']
with open('resources.csv', 'w', newline='') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=field_names, dialect='excel')
writer.writeheader()
writer.writerows(result)
Что по итогам
Когда возникла потребность миграции, то мы сами быстро раскидали приоритеты благодаря анализу BI-сервиса. Это ускорило переговоры с заказчиком, потому что теперь можем прийти и показать: «Вот ваши самые нагруженные отчеты, их перевозим в первую очередь».
Отчет источников наших Ad hoc запросов стал регулярным помощником для задач команды. Если происходит поломка данных, то смотрим, от каких табличек есть зависимости. Легко понимаем, из чего состоит пайплайн.
Парсер подключений, который был нужен для миграции на новый DWH, разом избавил 30+ аналитиков от монотонного просматривания сотен табличек с кодом и ручного заполнения Excel-таблички. Сделали ребят немножечко счастливее.
syusifov
гнать вас всех надо было сразу