# Выбор между инкрементальными моделями и снимками

Используйте [инкрементальные модели](https://docs.getdbt.tech/docs/build/incremental-models-overview.md), [снимки](https://docs.getdbt.tech/docs/build/snapshots.md) или их сочетание в зависимости от того, нужны ли вам текущие значения, исторические версии или и то и другое.

## Сравнение инкрементальных моделей и снимков

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

| Подход                                                                                                         | Когда использовать                                                                                                           | Когда не использовать                                                                                                                        |
| -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| [Только инкрементальные модели](#incremental-only)                                                             | Вам нужна последняя строка для каждого id, а источник уже содержит перечень изменений или надёжную временную метку изменения | На основе этой таблицы нужно отвечать на вопрос «как выглядела эта строка в прошлом месяце?»                                                 |
| [Только снимки](#snapshot-only)                                                                                | Источник перезаписывает строки, вам нужна история, и сканирование источника при каждом запуске допустимо                     | Источник только добавляет строки и никогда их не обновляет, либо таблица настолько велика, что полный запуск снимка обходится слишком дорого |
| [Совместное использование инкрементальных моделей и снимков](#using-incremental-models-and-snapshots-together) | Вам нужна инкрементальная подготовка данных, чтобы снизить стоимость запусков, и снимок для хранения истории                 | Вам нужна только одна из этих задач, либо вы собираетесь создавать снимок итоговой таблицы, к которой обращаются пользователи                |

<br />

## Только инкрементальные модели

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

* Инструмент загрузки или поток, который записывает операции вставки, обновления и удаления (пример см. в разделе [CDC с использованием Snowflake Streams](https://docs.getdbt.tech/best-practices/how-we-handle-real-time-data/2-incremental-patterns.md#cdc-with-snowflake-streams))
* Таблица, в которую только добавляются строки
* Таблица, в которой строки перезаписываются и есть надёжная временная метка `updated_at` (или временная метка загрузки), по которой можно фильтровать данные

В этих примерах рассматриваются два клиента: Alice и Bob. После первого запуска у обоих статус `trial`:

| id | name  | status | updated\_at         |
| -- | ----- | ------ | ------------------- |
| 1  | Alice | trial  | 2026-01-01 00:00:00 |
| 2  | Bob   | trial  | 2026-01-01 00:00:00 |

<br />

На следующий день Alice становится клиентом со статусом `active`. При использовании стратегии `merge` и `id` в качестве `unique_key` следующий запуск обновит её существующую строку:

| id | name  | status | updated\_at         |
| -- | ----- | ------ | ------------------- |
| 1  | Alice | active | 2026-01-02 00:00:00 |
| 2  | Bob   | trial  | 2026-01-01 00:00:00 |

<br />

Текущий статус Alice — `active`. Её предыдущий статус `trial` больше не хранится в этой таблице.

Чтобы настроить такое поведение:

* Используйте `is_incremental()`, чтобы при последующих запусках применять фильтр для новых или обновлённых строк
* Укажите идентификатор клиента в параметре [`unique_key`](https://docs.getdbt.tech/reference/resource-configs/unique_key.md), чтобы dbt мог сопоставлять входящие строки с существующими
* Выберите стратегию, например `merge`, которая обновляет совпадающие строки и вставляет новые

Подробности см. в разделах [Настройка инкрементальных моделей](https://docs.getdbt.tech/docs/build/incremental-models.md) и [Об инкрементальных стратегиях](https://docs.getdbt.tech/docs/build/incremental-strategy.md).

## Только снимки

Выберите этот подход, если источник перезаписывает строки и вам нужна история, чтобы отвечать на вопросы вроде «Когда Alice стала активным клиентом?».

В том же примере запустите создание снимка до и после изменения статуса Alice. При использовании стратегии `timestamp` снимок сохраняет обе версии:

| id | name  | status | updated\_at         | dbt\_valid\_from    | dbt\_valid\_to      |
| -- | ----- | ------ | ------------------- | ------------------- | ------------------- |
| 1  | Alice | trial  | 2026-01-01 00:00:00 | 2026-01-01 00:00:00 | 2026-01-02 00:00:00 |
| 1  | Alice | active | 2026-01-02 00:00:00 | 2026-01-02 00:00:00 | `null`              |
| 2  | Bob   | trial  | 2026-01-01 00:00:00 | 2026-01-01 00:00:00 | `null`              |

<br />

Столбцы `dbt_valid_from` и `dbt_valid_to` показывают, когда действовала каждая версия. По умолчанию для текущей версии значение `dbt_valid_to` равно `null`.

Чтобы настроить снимок, укажите идентификатор клиента в параметре `unique_key` и выберите способ обнаружения изменений:

* Используйте `timestamp` (рекомендуется), если есть надёжный столбец `updated_at`, значение которого увеличивается при каждом изменении строки
* Используйте `check`, если в таблице нет надёжного столбца `updated_at`. dbt отслеживает изменения, сравнивая указанные вами столбцы или все столбцы

Регулярно запускайте `dbt snapshot` или `dbt build` по расписанию. Снимки фиксируют значения, доступные на момент запуска, поэтому они не могут восстановить промежуточные изменения, перезаписанные между запусками. Выбирайте расписание с учётом частоты изменения данных и необходимой детализации их истории.

О настройке и расписании см. в разделах [Добавление снимков в DAG](https://docs.getdbt.tech/docs/build/snapshots.md) и [Как часто следует запускать команду snapshot?](https://docs.getdbt.tech/faqs/Runs/snapshot-frequency.md).

## Совместное использование инкрементальных моделей и снимков

Используйте инкрементальные модели вместе со снимками, если вам нужна инкрементальная staging-модель для подготовки входящих данных перед сохранением их истории.

Следующий пример продолжает историю Alice и Bob. В нём используется источник с именем `raw` и таблицей `customers`, которая хранит одну текущую строку для каждого клиента. Замените эти имена на [источник, определённый в вашем проекте](https://docs.getdbt.tech/docs/build/sources.md).

Источник содержит две временные метки:

* `updated_at`: когда изменились данные клиента.
* `_loaded_at`: когда инструмент загрузки записал эту версию в хранилище. В этом примере предполагается, что значение заполнено и увеличивается с каждым загруженным изменением.

### 1. Обработка входящих изменений

Создайте инкрементальную staging-модель, которая использует `_loaded_at` для поиска недавно загруженных строк и `id` для обновления данных соответствующего клиента.

models/staging/stg\_customers.sql

```sql
{{
  config(
    materialized='incremental',
    unique_key='id',
    incremental_strategy='merge'
  )
}}

select
    id,
    name,
    status,
    updated_at,
    _loaded_at
from {{ source('raw', 'customers') }}
{% if is_incremental() %}
where _loaded_at >= (
    select coalesce(max(_loaded_at), '1970-01-01')
    from {{ this }}
)
{% endif %}
```

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

### 2. Сохранение исторических версий

Создайте снимок staging-модели. Он использует `updated_at` для обнаружения изменений в данных каждого клиента.

snapshots/customers\_snapshot.yml

```yaml
snapshots:
  - name: customers_snapshot
    relation: ref('stg_customers')
    config:
      strategy: timestamp
      unique_key: id
      updated_at: updated_at
```

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

### 3. Выбор текущих значений для отчётности

Создайте модель, которая выбирает текущую версию каждого клиента:

models/marts/dim\_customers\_current.sql

```sql
select
    id,
    name,
    status,
    updated_at
from {{ ref('customers_snapshot') }}
where dbt_valid_to is null
```

Запускайте эти ресурсы в порядке зависимостей: staging-модель, снимок, затем модель для отчётности. Можно использовать `dbt build`, выбрав все три ресурса.

После того как запуск зафиксирует изменение данных Alice, staging-модель и модель для отчётности будут показывать её статус как `active`. Снимок сохранит обе версии — `trial` и `active`, как показано в предыдущих примерах.

Если источник уже содержит даты начала и окончания действия версий (или перечень изменений, который нужно сохранить целиком), снимок может не понадобиться. Загружайте эти изменения инкрементально с помощью `append` или `merge` и выбирайте последнюю строку с помощью SQL-фильтра.

## Что ещё следует учитывать

* Физическое удаление: инструменты загрузки часто помечают строку как удалённую. Снимки могут закрывать старую версию строки или добавлять запись об удалении с помощью [`hard_deletes`](https://docs.getdbt.tech/reference/resource-configs/hard-deletes.md). В инкрементальных моделях удаление необходимо обрабатывать в операции merge (или отдельной инструкцией delete).
* Изменения, поступающие с задержкой: расширьте инкрементальный фильтр, чтобы захватывать данные за период немного раньше последнего запуска, и определите, в каких случаях безопаснее использовать `--full-refresh`. См. раздел [Настройка инкрементальных моделей](https://docs.getdbt.tech/docs/build/incremental-models.md).
* Несколько изменений за один запуск: в инкрементальных моделях перед выполнением merge оставляйте только последнее изменение для каждого id.
* Изменение столбцов источника: используйте [`on_schema_change`](https://docs.getdbt.tech/docs/build/incremental-models.md#what-if-the-columns-of-my-incremental-model-change), чтобы управлять обработкой изменений схемы в инкрементальных моделях. Снимки могут добавлять новые столбцы, но при использовании стратегии `check` может потребоваться обновить [`check_cols`](https://docs.getdbt.tech/docs/build/snapshots.md#check-strategy).
* Тесты: проверяйте, что `unique_key` в источнике или staging-модели уникален и не содержит null. Для снимков также проверяйте уникальность сочетания `(unique_key, dbt_valid_from)`, отсутствие пересечений интервалов `dbt_valid_from` / `dbt_valid_to` и наличие не более одной текущей версии для каждого идентификатора (`dbt_valid_to` равно null). Отслеживайте [свежесть данных источника](https://docs.getdbt.tech/docs/build/sources.md#source-data-freshness), чтобы выявлять задержки загрузки.
* Стоимость: организуйте таблицу по временной метке изменения и используйте фильтры, чтобы ограничить объём данных, сканируемых при каждом запуске. См. раздел [Об инкрементальных стратегиях](https://docs.getdbt.tech/docs/build/incremental-strategy.md).

## Связанная документация

* [Об инкрементальных моделях](https://docs.getdbt.tech/docs/build/incremental-models-overview.md)
* [Настройка инкрементальных моделей](https://docs.getdbt.tech/docs/build/incremental-models.md)
* [Добавление снимков в DAG](https://docs.getdbt.tech/docs/build/snapshots.md)
* [`unique_key`](https://docs.getdbt.tech/reference/resource-configs/unique_key.md)
* [CDC с использованием Snowflake Streams](https://docs.getdbt.tech/best-practices/how-we-handle-real-time-data/2-incremental-patterns.md#cdc-with-snowflake-streams)
* [Блог dbt: стратегии захвата изменений данных в dbt](https://docs.getdbt.tech/blog/change-data-capture)
