Перейти к основному содержимому

Контракты моделей

Зачем определять контракт?​

Определение модели dbt так же просто, как написание SQL-запроса select. Ваш запрос естественным образом создает набор данных с именами и типами столбцов на основе выбранных вами столбцов и применяемых трансформаций.

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

Где поддерживаются контракты?​

Соображения​

Есть несколько моментов, которые стоит учитывать при использовании возможностей управления моделями:

  • Такие функции управления моделями, как доступ к моделям (model access), контракты (contracts) и версии (versions), повышают уровень доверия и стабильности в вашем проекте dbt. Однако из‑за добавления дополнительной структуры они могут усложнить откат изменений (например, при необходимости убрать ограничения доступа к модели) и увеличить затраты на сопровождение, если внедрить их слишком рано.
    Перед тем как добавлять функции управления, стоит оценить, готов ли ваш проект dbt извлечь из них пользу. Внедрение управления на этапе, когда модели все еще активно меняются, может усложнить будущие изменения.

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

Предварительные требования​

Контракты моделей поддерживаются в следующих файлах и моделях, а также на следующих платформах:

  • Файл dbt_project.yml
  • Файл properties.yml
  • SQL-модели
  • Модели с одной из следующих материализаций:
    • table
    • view — представления поддерживают имена столбцов и типы данных, но не constraints
    • incremental — с on_schema_change: append_new_columns или on_schema_change: fail
  • Некоторые платформы данных, однако поддерживаемые и принудительно применяемые constraints различаются в зависимости от платформы

Контракты моделей НЕ поддерживаются в следующих случаях:

  • Python-модели
  • SQL-модели с материализацией materialized view или ephemeral
  • Пользовательские материализации (если их автор не добавил поддержку)
  • Модели с рекурсивными CTE в BigQuery
  • Другие типы ресурсов, такие как sources, seeds, snapshots и так далее

Определение контракта​

Допустим, у вас есть модель с запросом примерно такого вида:

models/marts/dim_customers.sql
-- много SQL

final as (

select
customer_id,
customer_name,
-- ... и многое другое ...
from ...

)

select * from final

Чтобы применить контракт модели, установите enforced: true в конфигурации contract.

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

Если ваша модель материализована как table или incremental, и в зависимости от вашей платформы данных, вы можете дополнительно указать constraints, такие как not_null (содержащий нулевые значения).

models/marts/customers.yml
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: customer_id
data_type: int
constraints:
- type: not_null
- name: customer_name
data_type: string
...

При построении модели с определенным контрактом dbt будет делать две вещи по-другому:

  1. dbt выполнит проверку "предварительного полета", чтобы убедиться, что запрос модели вернет набор столбцов с именами и типами данных, соответствующими тем, которые вы определили. Эта проверка не зависит от порядка столбцов, указанных в вашей модели (SQL) или спецификации YAML.
  2. dbt включит имена столбцов, типы данных и ограничения в DDL-запросы, которые он отправляет на платформу данных, которые будут применяться при построении или обновлении таблицы модели, и упорядочит столбцы в соответствии с контрактом, а не вашей моделью dbt.

Поддержка ограничений на платформах​

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

  • Можно определить, соблюдение обеспечивается — Модель не будет построена, если она нарушает ограничение.
  • Можно определить, соблюдение не обеспечивается — Платформа позволяет указать тип ограничения, но модель всё равно может быть построена, даже если её построение нарушает это ограничение. Такое ограничение существует только в качестве метаданных. Этот подход более характерен для облачных хранилищ данных, чем для транзакционных баз данных, где чаще обеспечивается строгое соблюдение правил.
  • Нельзя определить, соблюдение не обеспечивается — Указать этот тип ограничения для платформы невозможно.
Тип ограниченияМожно определитьСоблюдение обеспечивается
not_null✅✅
primary_key✅❌
foreign_key✅❌
unique✅❌
check❌❌

Для каких моделей следует определять контракты?​

 Для каких моделей следует определять контракты?

Чем контракты отличаются от тестов?​

 Чем контракты отличаются от тестов?

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

В некоторых случаях вы можете заменить тест данных его эквивалентным ограничением. Это имеет преимущество гарантии проверки во время построения и, вероятно, требует меньше вычислительных ресурсов (стоимости) на вашей платформе данных. Предпосылки для замены теста данных ограничением:

  • Убедитесь, что ваша платформа данных может поддерживать и применять необходимое вам ограничение. Большинство платформ применяют только not_null.
  • Материализация вашей модели как table или incremental (не view или ephemeral).
  • Определение полного контракта для этой модели, указав name и data_type каждого столбца.

Почему тесты не являются частью контракта? В аналогии с программными API, структура ответа API является контрактом. Качество и надежность ("время безотказной работы") также являются очень важными атрибутами качества API, но они не являются частью контракта как такового. Когда контракт изменяется в обратной несовместимой манере, это является критическим изменением, требующим увеличения основной версии.

Нужно ли определять каждый столбец для контракта?​

 Нужно ли определять каждый столбец для контракта?

Да. Контракты dbt распространяются на все столбцы, определённые в модели, и требуют явно объявлять ожидания для каждого из этих столбцов. Явное объявление контракта — не случайность — именно в этом и заключается замысел этой функциональности.

При этом мы понимаем, что для моделей с большим количеством столбцов это может означать очень много YAML. Обсуждение возможных подходов к созданию и обновлению определений контрактов моделей см. в dbt-labs/dbt#11764.

 Как обрабатываются изменения, нарушающие совместимость?

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

К критическим изменениям относятся:

  • Удаление существующего столбца
  • Изменение типа данных существующего столбца
  • Удаление или изменение одного из constraints на существующем столбце (dbt версии 1.6 или выше)
  • Удаление модели с контрактом путем ее удаления, переименования или отключения (dbt v1.9 или выше).
    • модели с версией вызовут ошибку. модели без версии вызовут предупреждение.

Подробнее см. в справочнике по контрактам.

 Можно ли использовать контракты моделей с вложенными полями?

Да, но только в BigQuery, поскольку вложенные поля внутри столбцов типа struct имеют строгую типизацию. Контракты моделей полезны, когда модель содержит вложенные данные JSON или данные событий, в которых изменение типа вложенного поля может нарушить работу нижестоящих моделей.

Вложенные поля (структуры) — это столбцы внутри других столбцов. Перечислите каждое внутреннее поле в YAML, используя имя с точечной нотацией, например b.id или b.double_nested.another.even_more. Для каждого внутреннего поля необходимо указать name и data_type. При необходимости можно также объявить сам столбец типа struct (в этом примере — b или b.double_nested), чтобы добавить описание или ограничение для структуры.

models/nested_fields.yml
models:
- name: double_nested_fields
config:
contract:
enforced: true
materialized: table
columns:
- name: a
data_type: string
- name: b.id
data_type: integer
- name: b.name
data_type: string
- name: b.double_nested.id
data_type: integer
- name: b.double_nested.another.again
data_type: string
- name: b.double_nested.another.even_more
data_type: integer
models/double_nested_fields.sql
select
'string' as a,
struct(
1 as id,
'name' as name,
struct(2 as id, struct('test' as again, 2 as even_more) as another) as double_nested
) as b

Если значения name и data_type каждого вложенного поля соответствуют SQL, модель создаётся как обычно. Если они не совпадают, проверка контракта завершается ошибкой и модель не создаётся. Например, если в SQL поле even_more имеет строковый тип, а в контракте указан тип integer, dbt возвращает:

This model has an enforced contract that failed.
Please ensure the name, data_type, and number of columns in your contract match the columns in your model's definition.

| column_name | definition_type | contract_type | mismatch_reason |
| --------------------------------- | --------------- | ------------- | ------------------ |
| b.double_nested.another.even_more | STRING | INT64 | data type mismatch |

Массивы структур также поддерживаются. Объявите родительский столбец с data_type: array, затем перечислите каждое внутреннее поле:

- name: my_array
data_type: array
- name: my_array.id
data_type: integer
- name: my_array.name
data_type: string

О том, как добавить ограничения, например not_null, для вложенных полей, см. в разделе Ограничения.

Нашли ошибку?

0
Loading