Learning Platform
Глоссарий Troubleshooting
Урок 10.03 · 22 мин
Средний
Documentationpersist_docsWarehouse commentsMetadataDuckDB

persist_docs: descriptions в warehouse как column comments

В прошлых уроках мы написали descriptions в YAML и подключили doc blocks. Эти description видны в dbt docs UI. Но что про data consumers, которые не пользуются dbt docs? Например:

  • BI tool (Tableau / Looker / Mode) подключается к warehouse напрямую — видит таблицы и колонки, но не видит документации dbt.
  • Аналитик пишет ad-hoc SQL через DBeaver / DataGrip / Snowflake worksheet — открывает каталог таблиц, видит имя колонки customer_revenue_30d — что это значит?
  • Data scientist подключается через Python (snowflake-connector / sqlalchemy) — SELECT * -> колонки без context.

Решение — persist_docs: dbt сохраняет описания в metadata warehouse как COMMENT ON TABLE / COMMENT ON COLUMN. После dbt run descriptions появляются в каталоге warehouse, и любой инструмент, читающий metadata, их подхватит.

Этот урок — про конфигурацию persist_docs, варианты на разных warehouse, и подводные камни (length limits, special chars).


Концепт: comment в warehouse

В большинстве warehouses (Snowflake, BigQuery, Postgres, DuckDB) есть SQL syntax для добавления comments к objects:

COMMENT ON TABLE customer_metrics IS 'KPI клиентов: revenue, ltv, churn';
COMMENT ON COLUMN customer_metrics.revenue IS 'Sum завершённых заказов (USD)';
COMMENT ON COLUMN customer_metrics.is_churned IS 'TRUE если last_order > 90 days ago';

Эти comments хранятся в information schema warehouse:

-- Snowflake
SELECT column_name, comment
FROM information_schema.columns
WHERE table_name = 'CUSTOMER_METRICS';

-- BigQuery
SELECT column_name, description
FROM `project.dataset.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = 'customer_metrics';

-- DuckDB
SELECT column_name, comment
FROM information_schema.columns
WHERE table_name = 'customer_metrics';

BI-tool / IDE подключается к warehouse, читает information schema, показывает description рядом с column name. Аналитик видит контекст без открытия dbt docs.


Включение persist_docs

В dbt_project.yml (или per-model через config):

models:
  +persist_docs:
    relation: true   # сохранять description на уровне TABLE
    columns: true    # сохранять description на уровне COLUMN

После этого при dbt run dbt дополнительно запускает COMMENT ON ... для каждой материализованной модели и каждой описанной колонки.

Можно настраивать per-model:

- name: customer_metrics
  config:
    persist_docs:
      relation: true
      columns: true

Или per-folder:

# dbt_project.yml
models:
  my_project:
    marts:
      +persist_docs:
        relation: true
        columns: true
    staging:
      +persist_docs:
        relation: true
        columns: false   # на staging — только table-level, колонки тривиальные

Это полезный pattern: на marts (consumer-facing) — full persist, на staging — только relation level. Меньше нагрузки на metadata, фокус на consumer-relevant.


Когда persist_docs нужен / не нужен

Каталог данных — куда попадает metadata из persist_docs
Persist docs decision

Поддержка по warehouses

Snowflake

Поддержка: полная — relation + columns на tables, views, snapshots, seeds.

+persist_docs:
  relation: true
  columns: true

Snowflake принимает up to 256 characters для column comments (без значимых length issues для большинства описаний). Для table-level — больший лимит.

Снимок:

DESCRIBE TABLE analytics.customer_metrics;
-- видит column descriptions

SHOW COLUMNS IN TABLE analytics.customer_metrics;
-- видит column descriptions

BigQuery

Поддержка: полная — relation + columns. Через OPTIONS(description = '...') syntax.

+persist_docs:
  relation: true
  columns: true

BigQuery принимает до 1024 characters для column descriptions. Markdown rendered не в BigQuery UI — plain text only.

Redshift

Поддержка: частичная:

  • relation: yes (COMMENT ON TABLE)
  • columns: yes (COMMENT ON COLUMN)
  • materialized views: support only since recent versions

Postgres

Поддержка: полная. Standard SQL syntax.

COMMENT ON TABLE customer_metrics IS '...';
COMMENT ON COLUMN customer_metrics.revenue IS '...';

DuckDB

Поддержка: partial / evolving.

DuckDB 1.0+ поддерживает COMMENT ON TABLE и COMMENT ON COLUMN. dbt-duckdb 1.10+ implements persist_docs.

COMMENT ON TABLE analytics.customer_metrics IS '...';
COMMENT ON COLUMN analytics.customer_metrics.revenue IS '...';

-- Чтение
SELECT column_name, comment
FROM information_schema.columns
WHERE table_name = 'customer_metrics';

Ограничения DuckDB:

  • Views — comment не sticky на view-level в некоторых версиях.
  • При :memory: режиме comments исчезают вместе с процессом.

В большинстве middle-проектов dbt-duckdb persist_docs работает. На production DuckDB рекомендую тестировать на your specific version.


Полный пример: customer_metrics с persist_docs

YAML:

# models/marts/_marts__models.yml
version: 2

models:
  - name: customer_metrics
    config:
      persist_docs:
        relation: true
        columns: true
    description: |
      KPI клиентов: revenue, ltv, churn, segment.
      Grain: один клиент. Update daily 06:00 UTC.
      Owner: [email protected].
    columns:
      - name: customer_id
        description: 'PK. FK на dim_customers. From raw.customers.id.'
        data_tests: [unique, not_null]

      - name: revenue
        description: 'Lifetime revenue (USD). SUM(order_total) FILTER completed.'

      - name: ltv
        description: 'Same as revenue. Alias kept for backwards compat.'

      - name: is_churned
        description: 'TRUE if last_order_date < CURRENT_DATE - 90 day.'

      - name: segment
        description: 'One of: VIP, Premium, Standard, Inactive.'

После dbt run:

-- В DuckDB / Snowflake / Postgres
SELECT column_name, comment
FROM information_schema.columns
WHERE table_name = 'customer_metrics';

-- Результат:
-- customer_id    | PK. FK на dim_customers. From raw.customers.id.
-- revenue        | Lifetime revenue (USD). SUM(order_total) FILTER completed.
-- ltv            | Same as revenue. Alias kept for backwards compat.
-- is_churned     | TRUE if last_order_date < CURRENT_DATE - 90 day.
-- segment        | One of: VIP, Premium, Standard, Inactive.

В Tableau при подключении к warehouse — descriptions появляются в каталоге колонок. В DataGrip / DBeaver — в schema browser. В DataHub / Atlan — автоматически захватывается.


Gotchas: что идёт не так

1. Markdown в descriptions ломает rendering

В dbt docs description с **bold** рендерится bold. В warehouse — это raw text **bold**, asterisks видны. Аналитик в DBeaver видит:

**Revenue** — суммарная сумма за период. Формула: `SUM(order_total)`.

Markdown не render. Asterisks и backticks выглядят как мусор.

Решение — два разных descriptions:

  • В YAML — markdown для dbt docs UI
  • Через meta.persist_doc_override или manually plain-text version

Или один tone: plain-text короткие descriptions, markdown минимизирован. Это compromise.

# Хорошо для both dbt docs + warehouse comments
description: 'Lifetime revenue (USD). SUM(order_total) FILTER WHERE status = completed.'

# Плохо для warehouse (markdown will leak)
description: |
  **Lifetime revenue (USD)**.

  Формула:

SUM(order_total) FILTER WHERE status = ‘completed’

Production-grade подход: краткие plain-text descriptions для basic info + длинные markdown в doc blocks. Persist использует description (короткое), doc block выдаёт длинное в dbt docs.

2. Length limits

Snowflake: 256 chars on column. Если description длинный — truncate silently.

Workaround — короткая plain-text version:

- name: very_complex_metric
  description: |
    {% if env_var('DBT_DOCS_LONG', '0') == '1' %}
    Long markdown description with formulas, tables, edge cases...
    {% else %}
    Short plain-text version up to 256 chars for warehouse persist.
    {% endif %}

Это hack — обычно команды просто пишут короткое description и длинное в doc block.

3. Special characters

Кавычки / apostrophes в descriptions могут break SQL escape:

description: "Customer's lifetime value"  # apostrophe в строке

dbt автоматически escapes для COMMENT statement. Но если descriptions содержат не-printable / control characters — могут быть issues. Solution — sanitize при написании.

4. Views vs tables

COMMENT ON VIEW поддерживается не во всех warehouses. На DuckDB views support inconsistent. Если модель материализована как view, persist_docs может не работать.

Workaround — +materialized: table (если data size позволяет) или +materialized: incremental.

5. persist_docs не работает для seeds в некоторых warehouses

Snowflake/BigQuery: yes. DuckDB: partial. Если seeds — простые lookup tables, обычно не критично.

6. Сохранение при column rename

Если ты переименовал колонку (revenue -> lifetime_revenue) и сделал dbt run, warehouse имеет:

  • Старая колонка revenue всё ещё имеет старый comment (until next full refresh).
  • Новая колонка lifetime_revenue имеет новый comment.

При --full-refresh или incremental rebuild — старый comment стирается. Но на view-only refresh может задержаться.


CI: проверка persist_docs

# scripts/check_persist_docs.py
import duckdb  # или snowflake-connector / sqlalchemy

con = duckdb.connect('./analytics.duckdb')

# Проверка, что critical mart models имеют comments
critical_marts = ['customer_metrics', 'revenue_daily', 'churn_predictions']

for model in critical_marts:
    result = con.execute(f"""
        SELECT COUNT(*) FROM information_schema.columns
        WHERE table_name = '{model}'
          AND (comment IS NULL OR comment = '')
    """).fetchone()
    missing = result[0]
    if missing > 0:
        print(f"ERROR: {model} has {missing} columns without comment")
        exit(1)

print("All critical marts have full column comments")

Этот CI gate ловит регрессии: PR добавил колонку, забыл description — на dbt run persist_docs пытается сохранить пустую строку — CI catches.


Антипаттерны

  1. persist_docs без plain-text version: markdown leaks в warehouse, аналитики видят asterisks. Решение — короткие plain-text для persist + длинные markdown в doc blocks для dbt docs.

  2. persist_docs на больших ephemeral / view-only моделей: COMMENT не sticky, на каждом run перезаписывается. Лишняя нагрузка на metadata.

  3. persist_docs на тривиальных staging: 100 моделей в staging × 20 колонок × COMMENT statement = 2000 extra DDL per run. Удваивает runtime. Решение — persist_docs только на marts.

  4. Заполнить comments через scripts, не через dbt: команды иногда пишут вручную COMMENT ON ... после dbt run. Это дрейфует: на next dbt run dbt перепишет comment из YAML, кастомный пропадёт. Единый source of truth — YAML.

  5. No verification что comments persisted: написали description в YAML, не проверили в warehouse. После рефакторинга materialization (view -> table) comments могут пропасть. Solution — periodic check.


Попробуй сам

В твоём dbt-проекте:

  1. Включить persist_docs для marts:

    models:
      my_project:
        marts:
          +persist_docs:
            relation: true
            columns: true
  2. dbt run на одной mart-модели.

  3. Проверить в DuckDB:

    SELECT column_name, comment
    FROM information_schema.columns
    WHERE table_name = 'my_mart_model';
  4. Открыть в DataGrip / DBeaver — должны видеть column descriptions в schema browser.

  5. Изменить description в YAML, прогнать dbt run снова — comment в warehouse обновляется.

  6. Попробовать markdown в description (например **bold**) — увидеть, что в warehouse asterisks показываются как text. Решить — оставить markdown или сократить до plain text.


Ключевые выводы

  1. persist_docs сохраняет descriptions из YAML в warehouse metadata через COMMENT ON TABLE/COLUMN. После dbt run BI tools / DataGrip / DataHub видят descriptions.
  2. Конфигурация: relation: true/false, columns: true/false. Можно per-folder, per-model.
  3. Включай для consumer-facing marts, выключай для staging (overhead, тривиальные descriptions).
  4. Snowflake/BigQuery/Postgres — full support. DuckDB — partial (views не везде). Redshift — partial.
  5. Gotcha: markdown leaks в warehouse. Лучшая практика — короткие plain-text descriptions для persist, длинные markdown в doc blocks для dbt docs UI.
  6. Length limits: Snowflake 256 chars on column. Длинные descriptions truncate. Solution — short plain text + long markdown.
  7. CI verification: Python script проверяет, что critical mart колонки имеют comments в information_schema.
  8. Антипаттерны: markdown без plain version, persist на views/ephemeral, manual comments через scripts (дрейфует от dbt YAML), без verification.
Проверка знанийKnowledge check
Команда включила persist_docs для всех моделей (marts + staging + intermediate). После 2 месяцев нотика: dbt run стал на 30% медленнее. В чём проблема?
ОтветAnswer
Включение `persist_docs` на все модели генерит **COMMENT statement per relation + per column** на каждом run.\n\nЕсли:\n- 100 моделей в staging × 20 колонок = 2000 COMMENT statements\n- 30 моделей в intermediate × 15 колонок = 450 COMMENT statements\n- 50 моделей в marts × 20 колонок = 1000 COMMENT statements\n\nИтого ~3500 extra DDL statements на каждом run. На Snowflake/BigQuery каждый DDL = latency + cost. Это удваивает runtime в badly designed warehouses.\n\n**Решение** — selective persist_docs:\n\n```yaml\n# dbt_project.yml\nmodels:\n my_project:\n marts:\n +persist_docs:\n relation: true\n columns: true # full persist для consumer-facing\n intermediate:\n +persist_docs:\n relation: true\n columns: false # only table-level, columns тривиальные\n staging:\n +persist_docs:\n relation: false\n columns: false # vetoeshe — staging меняется часто, BI не подключается\n```\n\nЭто фокусирует persist_docs на consumer-facing layer (marts), где descriptions реально полезны. Staging — internal layer, BI не подключается напрямую.\n\n**Дополнительные оптимизации**:\n- На incremental models — persist на каждом incremental run не нужен (description не меняется). Можно `alter_column_types: false` patterns или manual rerun только при schema changes.\n- Использовать `dbt run --select state:modified+` (Slim CI) — не re-comment unchanged models.\n\nЦель: persist_docs работает там, где нужен (marts), не нагружает там, где не нужен (staging).
Проверка знанийKnowledge check
Senior замечает: в DataGrip колонка customer_metrics.revenue показывается с описанием **Revenue** — суммарная сумма SUM(order_total). Asterisks и backticks видны как обычный текст. Что делать?
ОтветAnswer
Это classic markdown leak. В dbt docs UI `**Revenue**` рендерится как bold. В warehouse COMMENT — raw text, asterisks/backticks показываются.\n\n**Решения по приоритету**:\n\n**1. Plain-text description, длинное в doc block**:\n\n```yaml\n- name: revenue\n description: 'Lifetime revenue in USD. SUM(order_total) where status=completed.'\n # длинное в doc block:\n # description: '{{ doc("revenue") }}'\n```\n\nКороткое plain-text -> читается в warehouse fluently. Длинное в doc block -> видно в dbt docs.\n\n**2. Sanitize markdown при persist** (custom macro):\n\n```sql\n{%- macro sanitize_description(desc) -%}\n {{ desc | regex_replace('asterisks_pattern', '...') }}\n{%- endmacro -%}\n```\n\nИспользовать через hook или override persist_docs macro. Это hack — не рекомендую (overhead, fragile).\n\n**3. Два source of truth через meta**:\n\n```yaml\n- name: revenue\n description: |\n Lifetime revenue (USD). SUM completed orders.\n meta:\n persist_description: 'Lifetime revenue (USD). SUM(order_total) where status=completed.'\n```\n\nКастомный macro reads meta.persist_description если present, иначе description. Рабочее но кастомное.\n\n**Production-grade подход (most teams)**: одна description — **plain-text короткая**, markdown минимальный. Длинные с markdown — в doc blocks.\n\n**Правило**: description (показывается в both dbt docs + warehouse) оптимизирована для warehouse readability. doc block — для dbt docs UI.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Команда подключает Tableau к Snowflake. Аналитики жалуются: 'видим колонки в каталоге, но не знаем, что они значат — формул нет'. Решение?

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

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

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

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