Learning Platform
Глоссарий Troubleshooting
Урок 09.04 · 14 мин
Начальный
store_failuresstore_failures_asdebugfailed rows

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

dbt даёт механизм store_failures: сохраняет провалившие тест строки в отдельную таблицу в warehouse. Это и есть инструмент для дебага data-quality проблем.

Что делает store_failures

По умолчанию dbt просто запускает тест и считает COUNT строк-нарушителей. Если 0 — PASS, если > 0 — FAIL. Сами нарушители теряются.

С store_failures: true dbt дополнительно делает: материализует результат теста в таблицу. То есть скомпилированный SQL теста — это просто SELECT, и dbt оборачивает его в CREATE OR REPLACE TABLE.

Без store_failures vs со store_failures

Без: dbt считает count, либо PASS либо FAIL. Со store_failures: dbt создаёт таблицу с failed-строками, ты можешь их прочитать и понять, что нарушает.

dbt test (default)SELECT count nарушителей
Output: PASS or FAIL Nстроки потеряны
dbt test --store-failuresCREATE TABLE failed_rows
Output: + таблица в warehouseможно SELECT

Включение через CLI

Самый простой способ — флаг --store-failures:

dbt test --select dim_customers --store-failures

После выполнения в warehouse появятся таблицы вида:

<target_schema>_dbt_test__audit.not_null_dim_customers_customer_id
<target_schema>_dbt_test__audit.unique_dim_customers_customer_id

Внутри них — строки, которые провалили тест. Можно их прочитать обычным SELECT.

Включение через config

В YAML на уровне теста:

- name: customer_id
  tests:
    - not_null:
        config:
          store_failures: true

Или для всех тестов в проекте через dbt_project.yml:

tests:
  +store_failures: true

Когда задано через config, флаг --store-failures не нужен — store_failures всегда применяется.

Что в таблице failed_rows

Структура таблицы зависит от теста. Для not_null:

SELECT customer_id
FROM dim_customers
WHERE customer_id IS NULL

Скомпилированное в storage. В таблице — все колонки SELECT, в данном случае один customer_id (NULL).

Для unique:

SELECT customer_id
FROM dim_customers
GROUP BY customer_id
HAVING COUNT(*) > 1

В таблице — список customer_id, которые дублируются.

Для accepted_values:

SELECT field
FROM model
WHERE field NOT IN ('a', 'b', 'c')

В таблице — невалидные значения.

Для relationships:

SELECT customer_id
FROM fact_orders
WHERE customer_id IS NOT NULL
  AND customer_id NOT IN (SELECT id FROM dim_customers)

В таблице — customer_id из fact_orders, которых нет в dim_customers.

Где найти failed_rows таблицы

Стандартное расположение:

<target.schema>_dbt_test__audit.<test_name>

Например:

main_dbt_test__audit.not_null_dim_customers_customer_id

dbt автоматически создаёт схему <schema>_dbt_test__audit (с суффиксом), чтобы не загромождать main schema.

В DuckDB можно посмотреть:

duckdb jaffle_shop.duckdb -c "SELECT * FROM main_dbt_test__audit.not_null_dim_customers_customer_id;"

Дебаг workflow

Типовой сценарий:

  1. dbt test падает с FAIL. Например:

    FAIL 5 unique_dim_customers_customer_id [...]

    5 нарушителей.

  2. Включить store_failures:

    dbt test --select dim_customers --store-failures
  3. Прочитать failed rows:

    SELECT * FROM main_dbt_test__audit.unique_dim_customers_customer_id;

    Получишь 5 customer_id с дублями.

  4. Дальше JOIN на оригинальную модель:

    SELECT *
    FROM dim_customers
    WHERE customer_id IN (
      SELECT customer_id FROM main_dbt_test__audit.unique_dim_customers_customer_id
    );

    Увидишь полные дубликаты — две строки с тем же customer_id, разными атрибутами. Это даёт контекст: например, обнаружишь, что loader записал customer дважды с разным email.

TIP

store_failures особенно полезен для relationships-тестов. Если 1000 customer_id в fact_orders не существуют в dim_customers, ты не угадаешь, какие именно. С store_failures получаешь точный список — и можешь решить, чинить ли источник или менять логику staging.

store_failures_as (dbt 1.9+)

В dbt 1.9 добавили параметр store_failures_as, который контролирует как именно хранить failed-строки. Варианты:

  • table (default до 1.9) — обычная table.
  • view — view с SELECT нарушителей. Всегда свежая, не занимает место.
  • ephemeral — НЕ поддерживается (это значит “не хранить”).
- name: customer_id
  tests:
    - not_null:
        config:
          store_failures: true
          store_failures_as: view

Почему view может быть лучше:

  • Не занимает storage (особенно важно на больших faild_rows).
  • Всегда свежая — отражает текущее состояние модели, не зафиксированное на момент dbt test.
  • Не нужно повторять dbt test для обновления.

Почему table:

  • Историческая запись — могу посмотреть, что было нарушено вчера.
  • View будет пересчитываться при каждом SELECT — на больших моделях может тормозить.

Параметры количества failed rows

В compiled SQL теста стандартно нет LIMIT — dbt считает все нарушители. Но в store_failures можно ограничить:

- name: customer_id
  tests:
    - not_null:
        config:
          store_failures: true
          # store_failures обычно сохраняет все, но в config теста можно
          # добавить limit через custom test или generic с фильтром

В практике достаточно сохранять всё — даже миллион failed rows занимает мало места. Если нарушителей миллион, у тебя проблема не в storage, а в данных.

Production использование

В prod-проектах store_failures часто всегда включен через dbt_project.yml:

tests:
  +store_failures: true
  +store_failures_as: view  # в 1.9+

Преимущества:

  • Любой провал теста сразу даёт контекст для дебага.
  • В CI можно сделать parsing failed_rows таблиц и отправлять в Slack/PagerDuty.
  • При расследовании инцидентов не нужно повторно запускать dbt test с флагом.

Минусы:

  • Каждый тест с store_failures чуть дольше выполняется (нужно создать таблицу).
  • На warehouse, где storage стоит денег (Snowflake), это +N таблиц. На DuckDB бесплатно.

DuckDB-специфика для store_failures

В DuckDB store_failures работает быстро:

  1. Таблица failed_rows создаётся через CREATE OR REPLACE TABLE — атомарно, single-writer.
  2. Storage в одном файле .duckdb — нет per-table cost.
  3. store_failures_as: view тоже supported.
  4. Audit-схема <schema>_dbt_test__audit создаётся автоматически.

На DuckDB можно безопасно делать +store_failures: true для всего проекта — overhead минимальный.

Команды для дебага

# Включить через CLI
dbt test --store-failures

# Selective test с store_failures
dbt test --select dim_customers --store-failures

# Посмотреть схему с audit таблицами
duckdb jaffle_shop.duckdb -c "SHOW TABLES FROM main_dbt_test__audit;"

# Прочитать failed rows
duckdb jaffle_shop.duckdb -c "SELECT * FROM main_dbt_test__audit.not_null_dim_customers_customer_id LIMIT 10;"

Попробуй сам

Создай модель с заведомо плохими данными:

-- models/staging/stg_test_failures.sql
SELECT 1 AS id, 'a' AS status
UNION ALL
SELECT 1 AS id, 'b' AS status  -- дубль id
UNION ALL
SELECT NULL AS id, 'c' AS status  -- NULL id

YAML:

models:
  - name: stg_test_failures
    columns:
      - name: id
        tests:
          - not_null
          - unique

Запусти:

dbt run --select stg_test_failures
dbt test --select stg_test_failures --store-failures

Увидишь FAIL на оба теста. Проверь:

duckdb jaffle_shop.duckdb -c "SELECT * FROM main_dbt_test__audit.not_null_stg_test_failures_id;"
duckdb jaffle_shop.duckdb -c "SELECT * FROM main_dbt_test__audit.unique_stg_test_failures_id;"

Получишь конкретные нарушители: NULL для not_null, id=1 (с count 2) для unique.

Что мы поняли

store_failures — механизм сохранения failed-строк тестов в отдельную таблицу/view. Включается через CLI флаг --store-failures или через config в YAML/dbt_project.yml. Создаёт audit-схему <schema>_dbt_test__audit с таблицами по имени теста. В dbt 1.9+ есть store_failures_as для выбора между table и view. На production удобно включать глобально — overhead минимальный, контекст для дебага огромный.

На этом мы закончили модуль про базовые тесты. В следующем модуле — singular, custom generic и unit tests, которые покрывают сценарии вне четырёх встроенных.

store_failures и store_failures_as: production debugging workflow
Проверка знанийKnowledge check
Тест unique на orders.order_id провалился — FAIL 17. Ты добавил --store-failures. Где найти таблицу с нарушителями и какой SELECT покажет полные строки-дубликаты?
ОтветAnswer
Таблица будет в схеме <target.schema>_dbt_test__audit, имя — unique_<model>_<column>: например main_dbt_test__audit.unique_orders_order_id. Внутри — 17 значений order_id с дублями. Чтобы увидеть полные дубли: SELECT * FROM orders WHERE order_id IN (SELECT order_id FROM main_dbt_test__audit.unique_orders_order_id). Получишь 34+ строки (минимум по 2 на каждый дублирующийся id) с разными значениями других колонок — это даст контекст для понимания причины (loader записал дважды, race condition, плохой transform).

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Что делает флаг --store-failures при запуске dbt test?

Закончили урок?

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

Войдите чтобы оценить урок

Прогресс модуля
0 из 4