Поверь своим глазам и тому что видишь на Дашборде
Мы в Wheely в значительной степени опираемся на данные при принятии операционных и стратегических решений. От выплаты еженедельных бонусов партнерам до экспансии в другие города и страны.
Каждый менеджер или Product Owner досконально знает свою область и любые отклонения могут вызвать вопросы. Поэтому к достоверности дашбордов и метрик предъявляются повышенные требования. А мы в команде Аналитики стремимся идентифицировать и исправить проблемы раньше, чем они попадут в отчетность.
Как известно, легче предотвратить, и поэтому я решил подойти к проблеме системно и проактивно. И, конечно, первым делом создал канал в Slack, в который настроил доставку уведомлений о любых ошибках в наших пайплайнах.
Уверенность в актуальности витрин данных
Прежде всего мы хотим убедиться в том, что пользовательские витрины содержат актуальные на момент времени данные:
К 10 утра каждого дня у нас рассчитаны витрины за полные прошлые сутки
Чтение из источников идет в ногу со временем и отставание не превышает 8 часов
Все источники продолжают слать лог изменений в DWH
Выходит, задача QA формулируется следующим образом:
Покажи мне все витрины данных, в которых время актуальности отстает от ожидаемого
Реализация для Хранилища:
В конфигурационном файле .yml добавим параметр freshness:
freshness:
warn_after: {count: 4, period: hour}
error_after: {count: 8, period: hour}
loaded_at_field: "__etl_loaded_at"
Для каждого теста будет выполнен простой шаблонизированный SQL-запрос:
select
max({{ loaded_at_field }}) as max_loaded_at,
{{ current_timestamp() }} as snapshotted_at
from {{ source }}
where {{ filter }}
Собранные метрики можно визуализировать в сводный отчет:
Мониторинг метрик расчета Витрин Данных
Неминуемо при проектировании сложных витрин, кубов, будут возникать проблемные места:
Баги и просчеты в формулах расчета метрик
Неожиданные данные (edge cases), которые могут нарушать заложенную логику
Бутылочное горлышко (bottleneck) в операциях расчетов
Они могут привести к серьезным последствиям:
Ошибки: Таймаут, Out of Memory, Disk Full
Замедление всего пайплайна загрузок и расчетов и нарушение SLA
Для контроля можно собирать следующие метрики:
Время, затраченное на формирование витрины + его динамика (скачки времени расчета)
Потребление ресурсов CPU
Потребление ресурсов диска и сети - IO, network
Лидеры этого рейтинга становятся первыми кандидатами на оптимизацию и рефакторинг.
Задача формулируется следующим образом:
Покажи мне те витрины, формирование которых требует излишне много ресурсов
Реализация для Хранилища:
Снять метрики расчетов витрин
Отрисовать дашборд
Настроить алерты
+pre-hook: "{{ logging.log_model_start_event() }}"
+post-hook: "{{ logging.log_model_end_event() }}"
Валидация схемы данных в основе тестирования
Современные Хранилища предполагают структуру, строгую типизацию, поколоночное хранение и компрессию данных. Структура данных суть схема - набор атрибутов, их типов, ограничений, например, PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE.
Большинство DWH не предполагает валидацию ограничений на этапе записи. Это нужно скорее в рамках подсказок оптимизатору запросов. Т.е. Это всего лишь предположение о данных, а фактическая проверка остается на откуп инженерам и аналитикам.
Какие базовые ожидания можем иметь относительно данных:
Есть ли в данных пропуски (NULL) там, где их быть не должно?
Какова атомарность моих данных (UNIQUE ID строки)?
Как таблицы соотносятся между собой (PRIMARY - FOREIGN KEYS)?
Есть ли записи, выходящие из списка допустимых значений(ACCEPTED VALUES)?
Задача QA формулируется следующим образом:
Покажи мне те витрины и источники, данные в которых нарушают наши ожидания
Реализация для Хранилища:
В конфигурационном файле .yml добавим параметр tests:
- name: dim_cars
description: Wheely partners cars.
columns:
- name: car_id
tests:
- not_null
- unique
- name: status
tests:
- not_null
- accepted_values:
values: ['deleted', 'unknown', 'active', 'end_of_life', 'pending', 'rejected'
, 'blocked', 'expired_docs', 'partner_blocked', 'new_partner']
Для каждого теста будет выполнен простой шаблонизированный SQL-запрос
-- NOT NULL test
select count(*) as validation_errors
from "wheely"."dbt_test"."dim_cars"
where car_id is null
-- UNIQUE test
select count(*) as validation_errors
from (
select
car_id
from "wheely"."dbt_test"."dim_cars"
where car_id is not null
group by car_id
having count(*) > 1
) validation_errors
-- ACCEPTED VALUES test
with all_values as (
select distinct
status as value_field
from "wheely"."dbt_test"."dim_cars"
),
validation_errors as (
select
value_field
from all_values
where value_field not in (
'deleted','unknown','active','end_of_life','pending','rejected','blocked','expired_docs','partner_blocked','new_partner'
)
)
select count(*) as validation_errors
from validation_errors
Бизнес-логика тоже подлежит проверке
Использую термин бизнес-логика в широком смысле - это любая сколь угодно сложная логика расчетов и взаимосвязей, которую мы закладываем в витрины данных. По сути это и есть бизнес-требования, лежащие в основе формирования Витрин.
Несколько простых примеров:
Сумма заказа не может быть отрицательной
Время подачи машины по заказу строго больше времени бронирования
Пользовательская сессия заканчивается только одним заказом, либо прерывается
Комиссия не превышает заданного %
Резкие скачки сумм (и вверх и вниз), неправдоподобные графики и значения метрик чаще всего становятся объектом пристального внимания на дашбордах и в отчетах.
Задача QA формулируется следующим образом:
Покажи мне те витрины данных, в которых нарушены бизнес-правила.
Реализация для Хранилища:
В терминах SQL выразить ту ситуацию, которая описывает нарушение правил
Сформировать тест на базе SQL-запроса
Тест считается пройденным (PASSED) если запрос возвращает 0 записей, и проваленным (FAILED) если записей >= 1
Continuous Integration на страже мастер-ветки DWH
Хорошо, идём дальше. Над DWH мы работаем совместно всей командой. Это подразумевает скоординированность и согласованность действий. Однако нередки случаи ошибок, просчеты, невнимательности на этапе разработки, которые приводят к ошибкам в PROD-окружении после PR Merge:
Доработка в одной части может послужить причиной ошибки в другой части
DEV-окружение инженера может отличаться от PROD-окружения
Запуск неоптимального кода на всех данных может привести к ошибке (например, Out of Memory)
Решение давно придумано - это использование практик тестирования в рамках Continuous Integration (CI). И его можно и нужно применять к данным!
Задача формулируется следующим образом:
Минимизировать вероятность появления ошибок в master-ветке и PROD-окружении DWH после релизов.
Реализация для Хранилища:
Подготовить окружение для CI (например, актуальная копия PROD-окружения, содержащая только 7 последних дней)
Выполнить полный пересчет всех витрин и метрик без ошибок прежде чем дать возможность влить feature-ветку в master
Кросс-сверка состояния DWH и Источников
От Хранилища Данных мы ожидаем отображение актуального состояния (а также всей истории) источников данных:
Наличие в DWH всех записей, которые присутствуют в источнике
Точное соответствие значений атрибутов (статус, временные метки, метрики) один-к-одному
Мы хотели бы застраховаться от ситуаций, когда часть данных просто не попадает в Хранилище в силу технических причин, а также от потери изменений состояния записей (например, финализация статуса заказа или корректировка его стоимости).
Задача формулируется следующим образом:
Убедиться в том, что Хранилище находится в консистентном (согласованном) с источниками состоянии.
Эта задача имеет одну из самых сложных реализаций и может стать темой отдельной статьи, судите сами:
Определить набор атрибутов, подлежащих верификации: идентификаторы, внешние ключи, измерения (страна, город, статус), метрики (стоимость, продолжительность, комиссия).
Выгрузить все строки из источника, актуальные на текущий момент
Загрузить строки в DWH и подготовить логику сверок
Настроить визуализацию и уведомления
Визуальное представление с возможностью drill-down до уровня атомарных записей:
Собирая всё в единый пазл
Идеи и принципы, изложенные в этой статье прозрачны и ясны. Сегодня пришло время применить лучшие практики разработки и тестирования в областях, связанных с обработкой данных:
Регулярный мониторинг, сбор и анализ метрик
Continuous Integration and Testing
Настройка уведомлений и алертов для команды
Проактивная работа над устранением инцидентов и причин ошибок
Управление ожиданиями пользователей в случае возникновения проблем (У нас всё под контролем)
В этой статье я показал подход к контролю качества данных, который мы выстраиваем в команде Wheely. Надеюсь, вы почерпнули для себя много нового и полезного.
Обширный набор тем, связанных с обработкой, хранением, тестированием данных изучается в рамках курса «Data Engineer» в OTUS, запуск которого состоится уже совсем скоро.
Как преподаватель курса я приглашаю вас 4 ноября в 20:00 на День Открытых Дверей курса «Data Engineer». Приходите на вебинары в OTUS знакомиться со мной и другими экспертами, будем ждать.
Что почитать еще
Напоследок я оставлю вам несколько ссылок на смежную тематику для дальнейшего изучения:
Data Build Tool или что общего между Хранилищем Данных и Смузи - обзор DBT на русском языке
The farm-to-table testing framework - комплексный подход к тестированию качества данных
Tests - Related reference docs - раздел документации DBT, посвященный тестированию
How to get started with data testing - тред на dbt discourse с обсуждением по теме
Data testing: why you need it - взгляд на преимущества тестирования данных
Manual Work is a Bug - несколько слов о принципах автоматизации и DRY