Добавление снапшотов в ваш DAG
Чтобы посмотреть видеоуроки по теме Snapshots, перейдите в dbt Learn и ознакомьтесь с курсом Snapshots.
Аналитикам часто нужно "оглядываться назад" на предыдущие состояния данных в изменяемых таблицах. Хотя некоторые системы исходных данных построены таким образом, что доступ к историческим данным возможен, это не всегда так. dbt предоставляет механизм, снимки, который фиксирует изменения в изменяемой table с течением времени.
Снимки реализуют тип-2 медленно изменяющихся измерений в изменяемых исходных таблицах. Эти медленно изменяющиеся измерения (или SCD) определяют, как строка в таблице изменяется с течением времени. Представьте, что у вас есть таблица orders, где поле status может быть перезаписано по мере обработки заказа.
| id | status | updated_at |
|---|
| 1 | pending | 2024-01-01 |
Теперь представьте, что заказ переходит из состояния "pending" в "shipped". Эта же запись теперь будет выглядеть так:
| id | status | updated_at |
|---|
| 1 | shipped | 2024-01-02 |
Этот заказ теперь в состоянии "shipped", но мы потеряли информацию о том, когда заказ в последний раз находился в состоянии "pending". Это затрудняет (или делает невозможным) анализ того, сколько времени потребовалось для отправки заказа. dbt может "сделать снимок" этих изменений, чтобы помочь вам понять, как значения в строке изменяются с течением времени. Вот пример таблицы снимков для предыдущего примера:
| id | status | updated_at | dbt_valid_from | dbt_valid_to |
|---|
| 1 | pending | 2024-01-01 | 2024-01-01 | 2024-01-02 |
| 1 | shipped | 2024-01-02 | 2024-01-02 | null |
Стратегия timestamp рекомендуется, потому что она более эффективно обрабатывает добавление и удаление колонок по сравнению со стратегией check. Это связано с тем, что она более устойчива к изменениям схемы, особенно когда со временем в таблице появляются новые колонки или удаляются существующие.
Стратегия timestamp опирается на одно поле updated_at, что позволяет избежать необходимости постоянно обновлять конфигурацию snapshot’а по мере эволюции исходной таблицы.
Почему timestamp — предпочтительная стратегия:
- Требуется отслеживать только одну колонку (
updated_at)
- Автоматически обрабатывает появление новых или удаление существующих колонок в исходной таблице
- Меньше подвержена ошибкам при изменении схемы таблицы со временем (например, при использовании стратегии
check может потребоваться обновлять параметр check_cols)
По умолчанию dbt_valid_to равно NULL для текущих записей. Однако, если вы установите конфигурацию dbt_valid_to_current (доступно в dbt Core v1.9+), dbt_valid_to будет установлено в указанное вами значение (например, 9999-12-31) для текущих записей.
Это позволяет легко фильтровать по диапазону дат.
Уникальный ключ используется dbt для сопоставления строк, поэтому крайне важно убедиться, что этот ключ действительно уникален! Если вы делаете снимок источника, я рекомендую добавить тест на уникальность в ваш источник (пример).
Когда вы запускаете команду dbt snapshot:
- При первом запуске: dbt создаст исходную таблицу снапшота — это будет результат выполнения вашего
select‑запроса с добавленными колонками, включая dbt_valid_from и dbt_valid_to. Для всех записей значение dbt_valid_to будет равно null либо значению, заданному в dbt_valid_to_current (доступно начиная с dbt Core 1.9+), если эта настройка сконфигурирована.
- При последующих запусках: dbt проверит, какие записи изменились, а также появились ли новые записи:
- Колонка
dbt_valid_to будет обновлена для всех существующих записей, которые изменились.
- Обновлённые записи и все новые записи будут вставлены в таблицу снапшота. Для этих записей значение
dbt_valid_to будет равно null либо значению, заданному в dbt_valid_to_current (доступно в dbt Core v1.9+).
Снимки могут быть использованы в последующих моделях так же, как и модели — с помощью функции ref.
Стратегии снимков определяют, как dbt узнает, изменилась ли строка. В dbt встроены две стратегии:
- Timestamp — Использует колонку
updated_at, чтобы определить, изменилась ли строка.
- Check — Сравнивает список колонок между их текущими и историческими значениями, чтобы определить, изменилась ли строка.
Стратегия timestamp использует поле updated_at, чтобы определить, изменилась ли строка. Если настроенная колонка updated_at для строки более новая, чем в последний раз, когда снимок запускался, то dbt аннулирует старую запись и запишет новую. Если временные метки не изменились, то dbt не предпримет никаких действий.
Почему рекомендуется использовать timestamp?
- Требуется отслеживать только одну колонку (
updated_at)
- Автоматически обрабатывает появление новых или удаление существующих колонок в исходной таблице
- Меньше подвержен ошибкам при эволюции схемы таблицы со временем (например, при использовании стратегии
check может потребоваться обновлять конфигурацию check_cols)
Стратегия timestamp требует следующих настроек:
| Конфигурация | Описание | Пример |
|---|
| updated_at | Столбец, который отражает момент последнего обновления строки источника. В зависимости от используемой платформы данных может поддерживать строки дат в формате ISO и целые числа unix epoch. | updated_at |
Пример использования:
Стратегия check полезна для таблиц, которые не имеют надежной колонки updated_at. Эта стратегия работает, сравнивая список колонок между их текущими и историческими значениями. Если какая-либо из этих колонок изменилась, то dbt аннулирует старую запись и запишет новую. Если значения колонок идентичны, то dbt не предпримет никаких действий.
Стратегия check требует следующих конфигураций:
| Конфигурация | Описание | Пример |
|---|
| check_cols | Список колонок для проверки изменений или all для проверки всех колонок | ["name", "email"] |
Стратегия снимков check может быть настроена для отслеживания изменений всех колонок, указав check_cols = 'all'. Лучше явно перечислить колонки, которые вы хотите проверить. Рассмотрите возможность использования surrogate key для конденсации многих колонок в одну.
При использовании стратегии check dbt отслеживает изменения, сравнивая значения в check_cols. По умолчанию dbt использует текущее время выполнения, чтобы заполнять поля dbt_updated_at, dbt_valid_from и dbt_valid_to. При этом вы можете дополнительно указать колонку updated_at:
- Если
updated_at настроена, стратегия check будет использовать эту колонку вместо времени выполнения, аналогично стратегии timestamp.
- Если значение
updated_at равно null, dbt по умолчанию использует текущее время.
Рассмотрим следующий пример, который показывает, как использовать стратегию check с updated_at:
snapshots:
- name: orders_snapshot
relation: ref('stg_orders')
config:
schema: snapshots
unique_key: order_id
strategy: check
check_cols:
- status
- is_cancelled
updated_at: updated_at
В этом примере:
- Если изменяется хотя бы одно из указанных значений в
check_cols, снапшот создаёт новую строку. Если колонка updated_at содержит значение (не равна null), снапшот использует его; в противном случае используется текущее время.
- Если
updated_at не задана, dbt автоматически возвращается к использованию текущего времени для отслеживания изменений.
- Используйте этот подход, если ваша колонка
updated_at не всегда надёжна для отслеживания обновлений записей, но вы всё равно хотите применять её — вместо времени выполнения снапшота — когда изменения строк всё же обнаружены.
Снимки таблицы будут созданы как клон вашего исходного набора данных, плюс некоторые дополнительные мета-поля*.
В dbt Core версии 1.9+ (или доступно раньше в треке релизов «Latest» для dbt):
- Эти имена колонок можно настроить в соответствии с командными или организационными соглашениями с помощью конфига
snapshot_meta_column_names.
- Используйте конфиг
dbt_valid_to_current, чтобы задать пользовательский индикатор значения dbt_valid_to для текущих записей снапшота (например, будущую дату вроде 9999-12-31). По умолчанию это значение равно NULL. Если оно задано, dbt будет использовать указанное значение вместо NULL для dbt_valid_to у текущих записей в таблице снапшота.
- Используйте конфиг
hard_deletes для отслеживания удалённых записей как новых строк с мета-полем dbt_is_deleted при использовании значения hard_deletes='new_record'.
| Field | Значение | Примечания | Пример |
|---|
dbt_valid_from | Временная метка, когда эта строка снапшота была впервые вставлена и стала валидной. | Эту колонку можно использовать для упорядочивания различных «версий» записи. | snapshot_meta_column_names: {dbt_valid_from: start_date} |
dbt_valid_to | Временная метка, когда эта строка стала невалидной. Для текущих записей по умолчанию это NULL или значение, указанное в dbt_valid_to_current. | Самая последняя запись снапшота будет иметь dbt_valid_to, равное NULL или указанному значению. | snapshot_meta_column_names: {dbt_valid_to: end_date} |
dbt_scd_id | Уникальный ключ, сгенерированный для каждой строки снапшота. | Используется внутри dbt. | snapshot_meta_column_names: {dbt_scd_id: scd_id} |
dbt_updated_at | Временная метка updated_at исходной записи на момент вставки этой строки снапшота. | Используется внутри dbt. | snapshot_meta_column_names: {dbt_updated_at: modified_date} |
dbt_is_deleted | Строковое значение, указывающее, была ли запись удалена (True — удалена, False — не удалена). | Добавляется, когда сконфигурирован hard_deletes='new_record'. | snapshot_meta_column_names: {dbt_is_deleted: is_deleted} |
Все эти имена колонок можно настроить с помощью конфига snapshot_meta_column_names. Подробнее см. в этом примере.
*Временные метки, используемые для каждой колонки, немного различаются в зависимости от используемой стратегии:
-
Для стратегии timestamp настроенная колонка updated_at используется для заполнения колонок dbt_valid_from, dbt_valid_to и dbt_updated_at.
Результаты snapshot‑запроса на момент 2024-01-01 11:00
| id | status | updated_at |
|---|
| 1 | pending | 2024-01-01 10:47 |
Результаты снапшота (обратите внимание, что 11:00 нигде не используется):
| id | status | updated_at | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|
| 1 | pending | 2024-01-01 10:47 | 2024-01-01 10:47 | | 2024-01-01 10:47 |
Результаты запроса на момент 2024-01-01 11:30:
| id | status | updated_at |
|---|
| 1 | shipped | 2024-01-01 11:05 |
Результаты snapshot (обратите внимание, что 11:30 нигде не используется):
| id | status | updated_at | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|
| 1 | pending | 2024-01-01 10:47 | 2024-01-01 10:47 | 2024-01-01 11:05 | 2024-01-01 10:47 |
| 1 | shipped | 2024-01-01 11:05 | 2024-01-01 11:05 | | 2024-01-01 11:05 |
Результаты snapshot при hard_deletes='new_record':
| id | status | updated_at | dbt_valid_from | dbt_valid_to | dbt_updated_at | dbt_is_deleted |
|---|
| 1 | pending | 2024-01-01 10:47 | 2024-01-01 10:47 | 2024-01-01 11:05 | 2024-01-01 10:47 | False |
| 1 | shipped | 2024-01-01 11:05 | 2024-01-01 11:05 | 2024-01-01 11:20 | 2024-01-01 11:05 | False |
| 1 | deleted | 2024-01-01 11:20 | 2024-01-01 11:20 | | 2024-01-01 11:20 | True |
-
Для стратегии check текущая временная метка используется для заполнения каждого столбца. Если настроено, стратегия check вместо этого использует столбец updated_at, как и стратегия временных меток.
Результаты snapshot-запроса на момент 2024-01-01 11:00
Snapshot results:
| id | status | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|
| 1 | pending | 2024-01-01 11:00 | | 2024-01-01 11:00 |
Query results at 2024-01-01 11:30:
Snapshot results:
| id | status | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|
| 1 | pending | 2024-01-01 11:00 | 2024-01-01 11:30 | 2024-01-01 11:00 |
| 1 | shipped | 2024-01-01 11:30 | | 2024-01-01 11:30 |
Snapshot results with hard_deletes='new_record':
| id | status | dbt_valid_from | dbt_valid_to | dbt_updated_at | dbt_is_deleted |
|---|
| 1 | pending | 2024-01-01 11:00 | 2024-01-01 11:30 | 2024-01-01 11:00 | False |
| 1 | shipped | 2024-01-01 11:30 | 2024-01-01 11:40 | 2024-01-01 11:30 | False |
| 1 | deleted | 2024-01-01 11:40 | | 2024-01-01 11:40 | True |
Чтобы запустить один снимок, используйте флаг --select, за которым следует имя снимка:
$ dbt snapshot --select order_snapshot
Ознакомьтесь с документацией по синтаксису выбора моделей для получения информации о других операторах и примерах.
Снимки (snapshots) — это пакетный подход к захвату изменений данных. Команда dbt snapshot должна выполняться по расписанию, чтобы гарантировать, что изменения в таблицах действительно фиксируются! Хотя отдельные случаи использования могут различаться, снимки предназначены для выполнения с интервалом от часа до дня. Если вы обнаружите, что делаете снимки чаще, чем это, подумайте, нет ли более подходящего способа фиксировать изменения в ваших исходных таблицах данных.
Когда столбцы вашего исходного запроса изменяются, dbt попытается урегулировать это изменение в целевом снимке table. dbt делает это следующим образом:
- Создает новые столбцы из исходного запроса в целевой таблице
- Расширяет размер строковых типов, где это необходимо (например,
varchar в Redshift)
dbt не будет удалять столбцы в целевой таблице снимка, если они удалены из исходного запроса. Он также не будет изменять тип столбца, за исключением увеличения размера столбцов типа varchar. То есть, если столбец string изменен на столбец date в исходном запросе снимка, dbt не будет пытаться изменить тип столбца в целевой таблице.
Да! Для снапшотов доступны следующие хуки:
По умолчанию dbt ожидает, что файлы snapshot’ов будут находиться в поддиректории snapshots вашего проекта.
Чтобы изменить это поведение, обновите конфигурацию snapshot-paths
в файле dbt_project.yml, например, так:
snapshot-paths: ["snapshots"]
Обратите внимание, что нельзя размещать snapshot’ы и модели в одной и той же директории.
Если вы видите следующую ошибку при попытке выполнить команду snapshot:
Целевая таблица снимка не является таблицей снимков (отсутствуют dbt_scd_id, dbt_valid_from, dbt_valid_to)
Убедитесь, что вы случайно не заставили ваш снимок вести себя как материализация таблицы, установив его конфигурацию materialized в значение table. До версии dbt 1.4 было возможно создать снимок следующим образом:
{% snapshot snappy %}
{{ config(materialized = 'table', ...) }}
...
{% endsnapshot %}
dbt обрабатывал снимки как таблицы (выполняя команды create or replace table ...) без предупреждения, вместо того чтобы фактически создавать снимки данных (SCD2 через команды insert / merge). При обновлении до версий dbt 1.4 и выше, dbt теперь выдает ошибку разбора (вместо того чтобы молча обрабатывать снимки как таблицы), которая гласит:
Снимок должен иметь значение materialized 'snapshot'
Это указывает на необходимость изменить конфигурацию materialized на snapshot. Но когда вы вносите это изменение, вы можете столкнуться с сообщением об ошибке, указывающим на отсутствие таких полей, как dbt_scd_id. Эта ошибка возникает потому, что ранее, когда dbt обрабатывал снимки как таблицы, он не включал необходимые мета-поля снимков в вашу целевую таблицу. Поскольку эти мета-поля отсутствуют, dbt правильно определяет, что вы пытаетесь создать снимок в таблице, которая на самом деле не является снимком.
Когда это происходит, вам нужно начать с нуля — повторно создать снимок ваших исходных данных, как если бы это было в первый раз, удалив ваш "снимок", который не является настоящей таблицей снимков. Затем dbt snapshot создаст новый снимок и вставит мета-поля снимка, как и ожидалось.