Использование метаданных для разблокировки контекста и вывода мониторов качества данных на новый уровень

Барр Мозес и Райан Кернс

В этой серии статей мы рассмотрим, как вы можете создавать собственные мониторы наблюдаемости данных с нуля, сопоставляя их с пятью ключевыми столпами здоровья данных. Часть 1 можно найти здесь.

Вторая часть этой серии была адаптирована из курса О'Рейли Барра Мозеса и Райана Кирнса Управление временем простоя данных: применение наблюдаемости к вашим конвейерам данных, первого в отрасли курса по наблюдаемости данных. Соответствующие упражнения доступны здесь, а адаптированный код, показанный в этой статье, доступен здесь.

По мере того, как во всем мире растет спрос на данные, все более необходимы надежные конвейеры данных. Когда данные прерываются - будь то изменения схемы, нулевые значения, дублирование или что-то еще - инженеры по обработке данных должны знать.

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

  • Данные актуальны?
  • Данные полные?
  • Находятся ли поля в ожидаемых пределах?
  • Нулевая скорость выше или ниже, чем должна быть?
  • Схема изменилась?

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

Чтобы освежить свою память после части 1, определите наблюдаемость данных как способность организации отвечать на эти вопросы и оценивать состояние своей экосистемы данных. Отражая ключевые переменные состояния данных, пять столпов наблюдаемости данных:

  • Свежесть: актуальны ли мои данные? Есть ли временные промежутки, когда мои данные не обновлялись?
  • Распространение: насколько надежны мои данные на полевом уровне? Находятся ли мои данные в ожидаемых пределах?
  • Объем: соответствует ли объем моих данных ожидаемым пороговым значениям?
  • Схема: изменилась ли формальная структура моей системы управления данными?
  • Происхождение: если некоторые из моих данных недоступны, на что повлияет восходящий и нисходящий поток? Как мои источники данных зависят друг от друга?

В этой серии статей мы хотим приоткрыть завесу и исследовать, как выглядит наблюдаемость данных - в коде.

В Части I я рассмотрел первые два столпа, свежесть и распространение, и показал, как небольшой код SQL может реализовать эти концепции. Это то, что мы бы назвали более классическими проблемами обнаружения аномалий - при постоянном потоке данных что-нибудь выглядит не так? Хорошее обнаружение аномалий, безусловно, является частью проблемы наблюдаемости данных, но это еще не все.

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

В этой статье мы рассмотрим два основных принципа наблюдаемости данных, призванные ответить на эти вопросы: схема и происхождение. И снова мы будем использовать легкие инструменты, такие как Jupyter и SQLite, чтобы вы могли легко развернуть нашу среду и попробовать эти упражнения самостоятельно. Давайте начнем.

Наша среда данных

Это руководство основано на Упражнениях 2 и 3 нашего курса O’Reilly Управление временем простоя данных. Вы можете попробовать эти упражнения самостоятельно, используя Jupyter Notebook и SQL. В следующих статьях мы рассмотрим более подробную информацию, включая упражнение 4.

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

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

И снова у нас есть таблица EXOPLANETS:

$ sqlite3 EXOPLANETS.db
sqlite> PRAGMA TABLE_INFO(EXOPLANETS);
0 | _id            | TEXT | 0 | | 0
1 | distance       | REAL | 0 | | 0
2 | g              | REAL | 0 | | 0
3 | orbital_period | REAL | 0 | | 0
4 | avg_temp       | REAL | 0 | | 0
5 | date_added     | TEXT | 0 | | 0

Запись в базе данных EXOPLANETS содержит следующую информацию:

0. _id: UUID, соответствующий планете.

  1. distance: Расстояние от Земли в световых годах.

2. g: Поверхностная сила тяжести как кратная g, постоянной силы тяжести.

3. orbital_period: Длина одного орбитального цикла в днях.

4. avg_temp: Средняя температура поверхности в градусах Кельвина.

5. date_added: Дата, когда наша система обнаружила планету и автоматически добавила ее в наши базы данных.

Обратите внимание, что один или несколько из distance, g, orbital_period и avg_temp могут быть NULL для данной планеты в результате отсутствующих или ошибочных данных.

sqlite> SELECT * FROM EXOPLANETS LIMIT 5;

Обратите внимание, что это упражнение имеет обратную силу - мы смотрим на исторические данные. В среде производственных данных наблюдаемость данных осуществляется в режиме реального времени и применяется на каждом этапе жизненного цикла данных, поэтому реализация будет немного отличаться от того, что сделано здесь.

Похоже, что наши самые старые данные датированы 2020–01–01 (примечание: большинство баз данных не хранят метки времени для отдельных записей, поэтому наш столбец DATE_ADDED отслеживает нас). Наши новейшие данные…

sqlite> SELECT DATE_ADDED FROM EXOPLANETS ORDER BY DATE_ADDED DESC LIMIT 1;
2020–07–18

… Похоже, будет в 2020–07–18. Конечно, это та же таблица, которую мы использовали в прошлой статье. Если мы хотим исследовать более зависимые от контекста столпы схемы и происхождения, нам нужно расширить нашу среду.

Теперь, в дополнение к EXOPLANETS, у нас есть таблица с именем EXOPLANETS_EXTENDED, которая является надмножеством нашей предыдущей таблицы. Их полезно рассматривать как одну и ту же таблицу в разные моменты времени. Фактически, EXOPLANETS_EXTENDED имеет данные, относящиеся к 2020–01–01…

sqlite> SELECT DATE_ADDED FROM EXOPLANETS_EXTENDED ORDER BY DATE_ADDED ASC LIMIT 1;
2020–01–01

… Но также содержит данные до 2020–09–06, кроме EXOPLANETS:

sqlite> SELECT DATE_ADDED FROM EXOPLANETS_EXTENDED ORDER BY DATE_ADDED DESC LIMIT 1;
2020–09–06

Визуализация изменений схемы

Между этими таблицами есть еще кое-что:

sqlite> PRAGMA TABLE_INFO(EXOPLANETS_EXTENDED);
0 | _ID            | VARCHAR(16777216) | 1 | | 0
1 | DISTANCE       | FLOAT             | 0 | | 0
2 | G              | FLOAT             | 0 | | 0
3 | ORBITAL_PERIOD | FLOAT             | 0 | | 0
4 | AVG_TEMP       | FLOAT             | 0 | | 0
5 | DATE_ADDED     | TIMESTAMP_NTZ(6)  | 1 | | 0
6 | ECCENTRICITY   | FLOAT             | 0 | | 0
7 | ATMOSPHERE     | VARCHAR(16777216) | 0 | | 0

В дополнение к 6 полям в EXOPLANETS таблица EXOPLANETS_EXTENDED содержит два дополнительных поля:

6. eccentricity: эксцентриситет орбиты планеты относительно звезды-хозяина.

7. atmosphere: доминирующий химический состав атмосферы планеты.

Обратите внимание, что, как и distance, g, orbital_period и avg_temp, и eccentricity, и atmosphere могут быть NULL для данной планеты в результате отсутствия или ошибочных данных. Например, планеты-изгои имеют неопределенный эксцентриситет орбиты, а многие планеты вообще не имеют атмосферы.

Также обратите внимание, что данные не заполняются заново, что означает, что записи данных с начала таблицы (данные, содержащиеся также в таблице EXOPLANETS) не будут иметь информации об эксцентриситете и атмосфере.

sqlite> SELECT
   ...>     DATE_ADDED,
   ...>     ECCENTRICITY,
   ...>     ATMOSPHERE
   ...> FROM
   ...>     EXOPLANETS_EXTENDED
   ...> ORDER BY
   ...>     DATE_ADDED ASC
   ...> LIMIT 10;
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |
2020–01–01 | |

Добавление двух полей является примером изменения схемы - формальный план наших данных был изменен. Изменения схемы происходят, когда вносятся изменения в структуру ваших данных, и ручная отладка может вызывать затруднения. Изменения схемы могут указывать на любое количество вещей о ваших данных, в том числе:

  • Добавление новых конечных точек API
  • Предположительно устаревшие поля, которые еще не ... устарели
  • Сложение или вычитание столбцов, строк или целых таблиц.

В идеальном мире мы хотели бы записать это изменение, поскольку оно представляет собой вектор возможных проблем с нашим конвейером. К сожалению, наша база данных не настроена естественным образом для отслеживания таких изменений. У него нет истории версий.

Мы столкнулись с этой проблемой в Части I при запросе возраста отдельных записей и добавили столбец DATE_ADDED, чтобы справиться. В этом случае мы сделаем нечто подобное, за исключением добавления всей таблицы:

sqlite> PRAGMA TABLE_INFO(EXOPLANETS_COLUMNS);
0 | DATE    | TEXT | 0 | | 0
1 | COLUMNS | TEXT | 0 | | 0

Таблица EXOPLANETS_COLUMNS «изменяет» нашу схему, записывая столбцы в EXOPLANETS_EXTENDED в любую заданную дату. Глядя на самую первую и последнюю записи, мы видим, что столбцы определенно изменились в какой-то момент:

sqlite> SELECT * FROM EXOPLANETS_COLUMNS ORDER BY DATE ASC LIMIT 1;
2020–01–01 | [
              (0, ‘_id’, ‘TEXT’, 0, None, 0),
              (1, ‘distance’, ‘REAL’, 0, None, 0),
              (2, ‘g’, ‘REAL’, 0, None, 0),
              (3, ‘orbital_period’, ‘REAL’, 0, None, 0),
              (4, ‘avg_temp’, ‘REAL’, 0, None, 0),
              (5, ‘date_added’, ‘TEXT’, 0, None, 0)
             ]
sqlite> SELECT * FROM EXOPLANETS_COLUMNS ORDER BY DATE DESC LIMIT 1;
2020–09–06 | [
              (0, ‘_id’, ‘TEXT’, 0, None, 0),
              (1, ‘distance’, ‘REAL’, 0, None, 0),
              (2, ‘g’, ‘REAL’, 0, None, 0),
              (3, ‘orbital_period’, ‘REAL’, 0, None, 0),
              (4, ‘avg_temp’, ‘REAL’, 0, None, 0),
              (5, ‘date_added’, ‘TEXT’, 0, None, 0),
              (6, ‘eccentricity’, ‘REAL’, 0, None, 0),
              (7, ‘atmosphere’, ‘TEXT’, 0, None, 0)
             ]

Теперь вернемся к нашему первоначальному вопросу: когда именно схема изменилась? Поскольку наши списки столбцов индексируются по датам, мы можем найти дату изменения с помощью быстрого SQL-скрипта:

Вот возвращенные данные, которые мы переформатировали для удобочитаемости:

DATE:         2020–07–19
NEW_COLUMNS:  [
               (0, ‘_id’, ‘TEXT’, 0, None, 0),
               (1, ‘distance’, ‘REAL’, 0, None, 0),
               (2, ‘g’, ‘REAL’, 0, None, 0),
               (3, ‘orbital_period’, ‘REAL’, 0, None, 0),
               (4, ‘avg_temp’, ‘REAL’, 0, None, 0),
               (5, ‘date_added’, ‘TEXT’, 0, None, 0),
               (6, ‘eccentricity’, ‘REAL’, 0, None, 0),
               (7, ‘atmosphere’, ‘TEXT’, 0, None, 0)
              ]
PAST_COLUMNS: [
               (0, ‘_id’, ‘TEXT’, 0, None, 0),
               (1, ‘distance’, ‘REAL’, 0, None, 0),
               (2, ‘g’, ‘REAL’, 0, None, 0),
               (3, ‘orbital_period’, ‘REAL’, 0, None, 0),
               (4, ‘avg_temp’, ‘REAL’, 0, None, 0),
               (5, ‘date_added’, ‘TEXT’, 0, None, 0)
              ]

С помощью этого запроса мы возвращаем дату нарушения: 2020–07–19. Подобно свежести и наблюдаемости распределения, достижение наблюдаемости схемы следует шаблону: мы определяем полезные метаданные, которые сигнализируют о состоянии конвейера, отслеживаем их и строим детекторы, чтобы предупреждать нас о потенциальных проблемах. Добавление дополнительной таблицы, такой как EXOPLANETS_COLUMNS, - это один из способов отслеживания схемы, но есть и множество других. Мы рекомендуем вам подумать о том, как можно реализовать детектор изменения схемы в своем собственном конвейере данных!

Визуализация происхождения

Мы описали происхождение как самый целостный из 5 столпов наблюдаемости данных, и не зря. Lineage контекстуализирует инциденты, сообщая нам (1), какие нижестоящие источники могут быть затронуты, и (2) какие вышестоящие источники могут быть основной причиной. Хотя визуализировать происхождение с помощью кода SQL не совсем интуитивно понятно, быстрый пример может проиллюстрировать, как это может быть полезно.

Для этого нам нужно еще раз расширить нашу среду данных.

Представляем: ЖИЛЬЕ

Давайте добавим еще одну таблицу в нашу базу данных. Пока что мы записываем данные об экзопланетах. Вот один забавный вопрос: на скольких из этих планет может быть жизнь?

Таблица HABITABLES берет данные из EXOPLANETS, чтобы помочь нам ответить на этот вопрос:

sqlite> PRAGMA TABLE_INFO(HABITABLES);
0 | _id          | TEXT | 0 | | 0
1 | perihelion   | REAL | 0 | | 0
2 | aphelion     | REAL | 0 | | 0
3 | atmosphere   | TEXT | 0 | | 0
4 | habitability | REAL | 0 | | 0
5 | min_temp     | REAL | 0 | | 0
6 | max_temp     | REAL | 0 | | 0
7 | date_added   | TEXT | 0 | | 0

Запись в HABITABLES содержит следующее:

0. _id: UUID, соответствующий планете.

1. perihelion: Ближайшее расстояние до небесного тела за период обращения по орбите.

2. aphelion: Наибольшее расстояние до небесного тела за период обращения по орбите.

3. atmosphere: доминирующий химический состав атмосферы планеты.

4. habitability: Действительное число от 0 до 1, указывающее, насколько вероятно, что на планете есть жизнь.

5. min_temp: минимальная температура на поверхности планеты.

6. max_temp: максимальная температура на поверхности планеты.

7. date_added: Дата, когда наша система обнаружила планету и автоматически добавила ее в наши базы данных.

Как и столбцы в EXOPLANETS, значения для perihelion, aphelion, atmosphere, min_temp и max_temp могут быть NULL. Фактически, perihelion и aphelion будут NULL для любого _id в EXOPLANETS, где eccentricity равно NULL, поскольку вы используете эксцентриситет орбиты для вычисления этих показателей. Это объясняет, почему эти два поля всегда NULL в наших старых записях данных:

sqlite> SELECT * FROM HABITABLES LIMIT 5;

Итак, мы знаем, что HABITABLES зависит от значений в EXOPLANETS (или, в равной степени, EXOPLANETS_EXTENDED), и EXOPLANETS_COLUMNS тоже. График зависимости нашей базы данных выглядит так:

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

Исследование аномалии

Когда у нас есть ключевой показатель, такой как обитаемость в HABITABLES, мы можем оценить его работоспособность несколькими способами. Для начала, каково среднее значение habitability для новых данных за определенный день?

Глядя на эти данные, мы видим, что что-то не так. Среднее значение для habitability обычно составляет около 0,5, но позже в записанных данных оно уменьшается вдвое до 0,25.

Это явная аномалия распределения, но что именно происходит? Другими словами, какова основная причина этой аномалии?

Почему бы нам не посмотреть на NULL показатель пригодности для жизни, как мы это делали в Части I?

К счастью, здесь нет ничего необычного:

Но это не выглядит многообещающим как причина нашей проблемы. Что, если мы посмотрим на другой показатель работоспособности распределения, частоту нулевых значений?

Что-то здесь явно не так:

Исторически сложилось так, что habitability практически никогда не был нулевым, но в более поздние сроки он вырастет в среднем почти до 40%. Это дает заметный эффект понижения среднего значения поля.

Мы можем адаптировать один из детекторов распределения, которые мы построили в Части I, чтобы получить первую дату заметных нулевых ставок в поле habitability:

Я запустил этот запрос через командную строку:

$ sqlite3 EXOPLANETS.db < queries/lineage/habitability-zero-rate-detector.sql
DATE_ADDED | HABITABILITY_ZERO_RATE | PREV_HABITABILITY_ZERO_RATE
2020–07–19 | 0.369047619047619      | 0.0

2020–07–19 гг. Стал первой датой, когда нулевая ставка начала показывать аномальные результаты. Напомним, что это тот же день, что и обнаружение изменения схемы в EXOPLANETS_EXTENDED. EXOPLANETS_EXTENDED находится выше по течению от HABITABLES, поэтому вполне возможно, что эти два инцидента связаны.

Таким образом, информация о происхождении может помочь нам определить основную причину инцидентов и быстрее приступить к их устранению. Сравните два следующих объяснения этого инцидента в HABITABLES:

  1. В 2020–07–19 годах нулевая ставка столбца пригодности для жизни в таблице HABITABLES подскочила с 0% до 37%.
  2. В 2020–07–19 мы начали отслеживать два дополнительных поля, eccentricity и atmosphere, в таблице EXOPLANETS. Это оказало неблагоприятное воздействие на нижележащую таблицу HABITABLES, часто задавая для полей min_temp и max_temp экстремальные значения, когда eccentricity не было NULL. В свою очередь, это вызвало всплеск нулевой скорости поля 84_, который мы обнаружили как аномальное уменьшение среднего значения.

В объяснении (1) используется только факт наличия аномалии. Объяснение (2) использует происхождение в терминах зависимостей между таблицами и полями, чтобы поместить инцидент в контекст и определить основную причину. Между прочим, все в (2) на самом деле правильно, и мы рекомендуем вам повозиться с окружающей средой, чтобы самому понять, что происходит. Хотя это всего лишь простые примеры, инженер, оснащенный (2), будет быстрее понять и решить основную проблему, и все это связано с надлежащей наблюдаемостью.

Что дальше?

Отслеживание изменений схемы и происхождения может дать вам беспрецедентную картину состояния и использования ваших данных, предоставляя важную контекстную информацию о том, кто, что, где, почему и как использовались ваши данные. Фактически, схема и происхождение являются двумя наиболее важными столпами наблюдаемости данных, когда дело доходит до понимания последующих (а часто и реальных) последствий простоя данных.

Обобщить:

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

Я надеюсь, что эта вторая часть «Наблюдаемости данных в контексте» была полезной. Спасибо за прочтение.

До Части III желаем вам без простоев из-за данных!

Хотите узнать больше о наблюдаемости данных? Обратитесь к Райану и команде Монте-Карло.