Learning Platform
Глоссарий Troubleshooting
Урок 10.01 · 22 мин
Средний
DocumentationDescriptionsProduction toneNamingPR review

Production-grade descriptions: что писать и как поддерживать живыми

В dbt I мы научились писать description: "..." в YAML и видеть его в dbt docs serve. Но что отличает production-grade documentation от формальной отписки «таблица customer’ов»? Какие descriptions реально помогают аналитикам, а какие — формальная галочка для PR-review?

Этот урок — про производственный tone для descriptions: какую информацию обязательно включать, как поддерживать descriptions живыми (не устаревающими), и как организовать docs review в команде.


Что отличает плохое description от хорошего

Возьмём модель customer_segments в реальном проекте. Junior пишет:

- name: customer_segments
  description: "Сегменты клиентов"

Это бесполезно. Аналитик, открывший docs, узнал только то, что и так понятно из названия. На сколько сегментов? По какому правилу? Как часто обновляется? Что грешат, если segment = NULL?

Senior пишет так:

- name: customer_segments
  description: |
    **Grain**: один клиент (customer_id уникален).

    **Бизнес-смысл**: классификация клиентов по lifetime revenue и
    активности за последние 12 месяцев. Используется для:
    - Email-кампаний (segment = 'VIP' -> высокий бюджет)
    - Churn-risk scoring (segment = 'Inactive' -> priority outreach)
    - Annual planning (CLV прогноз per segment)

    **Правила классификации**:
    | Segment    | Условие                                              |
    |------------|------------------------------------------------------|
    | VIP        | ltv >= $5000 AND orders_last_12m >= 12               |
    | Premium    | ltv >= $1000 AND orders_last_12m >= 6                |
    | Standard   | ltv >= $100                                          |
    | Inactive   | ltv < $100 OR orders_last_12m = 0                    |

    **Обновление**: ежедневно через scheduled job (cron 06:00 UTC).

    **Owner**: [email protected] (Slack: #data-help)

    **Связанные модели**:
    - upstream: `customer_lifetime_value`, `customer_orders_summary`
    - downstream: `email_campaigns_targeting`, `churn_predictions`

    **История**: добавлено в v1 проекта 2025-09. Сегментация была обсуждена с
    маркетингом 2025-08-15 (см. Notion doc «Customer Segmentation Logic v2»).

Это — production-grade. Шесть категорий информации, которые каждый production description должен содержать:

6 категорий production-description

Дополнительно полезно — история (когда добавлено, ссылка на discussion / PR / Notion). Это контекст для будущего читателя: «зачем мы вообще это сделали?»


Описания колонок: тот же стандарт

Колонки тоже заслуживают descriptions. Особенно derived/computed:

- name: customer_segments
  columns:
    - name: customer_id
      description: |
        FK на `dim_customers.customer_id`. Уникальный идентификатор клиента
        из source-системы (Postgres app DB -> raw.customers -> stg_customers).
      data_tests:
        - unique
        - not_null
        - relationships:
            to: ref('dim_customers')
            field: customer_id

    - name: ltv
      description: |
        Lifetime value: суммарная выручка от клиента за всю историю
        (от первого заказа до dbt_valid_to NULL в customers_snapshot).

        **Формула**: `SUM(order_total) - SUM(refund_amount)` по всем completed orders.

        **Edge case**: если у клиента 0 завершённых заказов — ltv = 0
        (через COALESCE, не NULL).

        **Тип**: NUMERIC(12, 2), валюта USD.

    - name: segment
      description: |
        Сегмент клиента. Одно из значений: 'VIP', 'Premium', 'Standard', 'Inactive'.
        См. полную таблицу правил в description модели.
      data_tests:
        - not_null
        - accepted_values:
            values: ['VIP', 'Premium', 'Standard', 'Inactive']

    - name: orders_last_12m
      description: |
        Количество завершённых заказов за последние 12 месяцев от текущей даты.

        **Формула**: `COUNT(o.order_id) WHERE o.order_date >= CURRENT_DATE - INTERVAL '12 month' AND o.status = 'completed'`

        **Использование**: входит в формулу segment. Отдельно может быть полезно
        для активности dashboard.

Что включаем в column description:

  • FK назначение (если внешний ключ).
  • Формулу (для derived/computed колонок). Иначе аналитик догадывается, что значит revenue.
  • Edge cases (что происходит при 0 / NULL / отрицательных).
  • Тип данных (если важно для downstream — особенно для DECIMAL precision).
  • Валюта/единицы (USD vs EUR, hours vs minutes).

Для тривиальных колонок (first_name TEXT) можно короче, но всё равно явно — «имя клиента из source app.customers».


Naming convention для documentation

Согласованные имена помогают навигации:

ЭлементConventionПример
Schema YAML_<context>.yml или _<resource>.yml_models.yml, _sources.yml
Schema YAML per directory_<dir>__models.yml_marts__finance__models.yml
Sources file_sources.ymlодин на проект или per source
Exposures file_exposures.ymlодин на проект
Doc blocks_docs.md или _<topic>__docs.md_metrics__docs.md
Glossary_glossary.mdповторяющиеся термины

Этот convention помогает в IDE: открыл папку — сразу видно YAML и docs.

Альтернатива (старый стиль) — один большой schema.yml в корне models/. На малых проектах ок, на проекте 100+ моделей становится непригодным.

Pro tip: для критических моделей делать models/marts/finance/_models.yml рядом с SQL, а для всех тривиальных staging — models/staging/_stg__models.yml один. Гранулярность зависит от reorganization frequency.


Как поддерживать docs живыми

Бизнес-глоссарий — единый источник терминов

Главная проблема: docs устаревают. Кто-то добавил колонку, забыл обновить YAML — описания нет. Кто-то сделал рефакторинг — формулы в description не пересмотрены — теперь врёт.

Лекарства — процесс, не tools.

1. PR-чеклист

В CONTRIBUTING.md или GitHub PR template:

## Documentation checklist
- [ ] Каждая новая модель имеет description (grain, business meaning, owner)
- [ ] Каждая новая колонка имеет description (формула если derived, edge cases)
- [ ] Обновлены descriptions для изменённых колонок
- [ ] Если новая FK — добавлен `relationships` test
- [ ] Если новый сегмент/категория — обновлён `accepted_values` test
- [ ] Если новый mart — добавлен exposure (если consumer existed)

Reviewer проверяет каждый пункт. Без чеклиста — фокус только на SQL, docs дрейфуют.

2. CI gate: dbt docs generate без warning

В CI добавь job, который проверяет:

- name: Check docs completeness
  run: |
    dbt docs generate --target ci
    python scripts/check_descriptions.py

Скрипт check_descriptions.py парсит target/manifest.json и фейлит, если:

  • Модель в models/marts/ без description.
  • Колонка customer_id / customer_email / revenue (бизнес-критичные) без description.
  • Колонка с типом DECIMAL/NUMERIC без указания precision.
# scripts/check_descriptions.py (упрощённо)
import json, sys

with open('target/manifest.json') as f:
    manifest = json.load(f)

errors = []
for node_id, node in manifest['nodes'].items():
    if node['resource_type'] == 'model' and node['fqn'][1] == 'marts':
        if not node.get('description'):
            errors.append(f"Mart model {node['name']} missing description")
        for col_name, col in node['columns'].items():
            if not col.get('description'):
                errors.append(f"Column {node['name']}.{col_name} missing description")

if errors:
    print('\n'.join(errors))
    sys.exit(1)

Это strict gate: PR без описания не мержится.

3. Glossary через doc blocks

Если у тебя есть термины, повторяющиеся в 5+ моделях (например, revenue, customer_id, churn), оторви их в glossary через doc blocks. Об этом — следующий урок.

Это убирает дублирование: один источник правды для определения revenue, все модели ссылаются. Изменил один раз — обновилось везде.

4. Документация как часть code review

Не разделять «PR с кодом» и «PR с doc». Каждый код-PR содержит docs. Иначе docs накапливаются как технический долг и никогда не пишутся.

Senior должен отказывать в merge при отсутствии docs на новой модели. Это болезненно поначалу, но устанавливает культуру.


Документация для legacy-проекта

Что делать, если ты пришёл в проект с 200 моделей и 80% без descriptions?

Audit
Prioritize
Write
CI enforce
Maintain
1. список без descriptions2. roadmap (marts -> staging)3. после X% — включить gate4. ongoing PR + review

Шаг 1 — Аудит:

# Список моделей без descriptions
dbt parse
python -c "
import json
m = json.load(open('target/manifest.json'))
no_desc = [n['name'] for n in m['nodes'].values()
           if n['resource_type'] == 'model' and not n.get('description')]
print(f'{len(no_desc)} models without description')
for name in no_desc[:20]:
    print(f'  - {name}')
"

Шаг 2 — Приоритизация:

  1. Marts (consumer-facing, аналитики). Сначала.
  2. Staging (важно для onboarding новых dev’ов).
  3. Intermediate (часто эфемерные, можно последними).

Шаг 3 — Написание:

  • Не делать за неделю. Цель: 80% покрытия за квартал.
  • Каждый sprint — 5-10 моделей.
  • Привлечь knowledgeable analysts для проверки бизнес-смысла (они знают лучше).

Шаг 4 — CI gate:

  • После 80% coverage — включить strict gate.
  • Без description — PR не мержится.
  • Это фиксирует coverage от регрессий.

Шаг 5 — Maintain:

  • Каждый PR обновляет docs.
  • Quarterly review: проверить, что formulas в description не разошлись с SQL.
  • Glossary через doc blocks (следующий урок).

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

  1. Description дублирует имя: name: customer_id, description: "ID клиента". Полезной информации ноль. Лучше: description: "FK на dim_customers, originated from raw.customers.id".

  2. Description устарел: модель пересмотрена, но описание не обновлено. Хуже отсутствия — врёт. Решение: PR-чеклист + quarterly review.

  3. Description содержит SQL: description: "SELECT ... FROM ...". Логика SQL и так видна в .sql файле, dublication не нужно. В description — бизнес-описание, не SQL.

  4. Description “TODO”: description: "TODO add description". Не лучше, чем пусто, но создаёт false sense of progress. CI gate должен это ловить (grep TODO).

  5. Слишком длинно: 1000 слов в description. Никто не читает. Лимит: 100-300 слов на model description. Если нужно больше — оторви в _<model>__docs.md через doc block.

  6. Markdown vs plain: description: "**Grain**: ..." рендерится в dbt docs как HTML. Используй markdown — таблицы, lists, bold. Plain text — нечитаемо.

  7. No owner: при сломанных данных аналитик не знает, кому писать. Owner обязательно — почта, Slack channel, или GitHub team.


Попробуй сам

Открой свой dbt-проект. Возьми критическую mart-модель (revenue / churn / customer_metrics). Сейчас description, вероятно, такой:

- name: customer_metrics
  description: "Метрики клиента"

Перепиши его, используя 6 категорий:

  1. Grain: один клиент (customer_id уникален)
  2. Бизнес-смысл: KPI для customer success: LTV, days_since_last_order, NPS
  3. Формулы: таблица с расчётом каждой метрики
  4. Обновление: daily 06:00 UTC через Airflow DAG customer_metrics_daily“
  5. Owner: data-team@..., Slack #data-help
  6. Связи: upstream / downstream

Покажи команде. Сравните old vs new. Это production-grade documentation.


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

  1. Production description содержит 6 категорий: grain, бизнес-смысл, формулы/правила, обновление, owner, связи. Минимум — 5 из 6.
  2. Описания колонок обязательны для derived/computed/FK/categorical. Включают формулу, edge cases, валюту/единицы.
  3. Naming convention: _models.yml, _sources.yml, _docs.md. Один schema per directory для больших проектов.
  4. Поддержка живыми: PR-чеклист, CI gate (грэп по manifest), code review с фокусом на docs, quarterly stale review.
  5. Legacy проект: audit -> prioritize (marts first) -> write 5-10/sprint -> CI gate after 80% coverage -> maintain.
  6. Антипаттерны: дублирование имени, устаревший, SQL вместо бизнеса, TODO без content, no owner, plain text вместо markdown.
  7. Description — часть кода. Без него PR не мержится. Это культура, не tooling.
Проверка знанийKnowledge check
Junior PR: новая mart-модель `customer_churn_risk` с description: 'Churn risk per customer'. Какие 3 вещи senior должен попросить добавить?
ОтветAnswer
Минимум три категории отсутствуют:\n\n1. **Grain** — один клиент? Один (клиент, дата)? Без grain JOIN'ы делают неверно. Добавить: `**Grain**: один клиент (customer_id уникален, обновляется ежедневно)`.\n\n2. **Формула / правила** — что такое "risk"? Score от 0 до 100? Percentage? Boolean? Какие критерии? Добавить: `**Score 0-100**: формула = 0.4 * days_since_last_order_normalized + 0.3 * avg_decline_rate + 0.3 * support_tickets_negative`. Должна быть либо точная формула, либо таблица правил классификации.\n\n3. **Owner + контакт** — кому писать когда дашборд сломался или вопрос. Не безличное "data team", а почта / Slack channel / конкретный человек. Добавить: `**Owner**: [email protected] (Slack: #ml-help)`.\n\nДополнительно полезно (но 3 топ — выше):\n- **Обновление**: ежедневно cron? On-demand? Какая latency.\n- **Downstream**: какой dashboard / ML model читает — для impact analysis.\n- **История**: когда добавлено, ссылка на discussion.\n\nPR не мержится без этих трёх. Это стандарт.
Проверка знанийKnowledge check
Команда пришла в legacy проект с 200 моделей, 80% без descriptions. Стратегия покрытия — переписать всё за неделю? Описать план.
ОтветAnswer
Не за неделю. **80% за квартал** — реалистичная цель.\n\n**План**:\n\n1. **Audit** (день 1): прогон `dbt parse` -> парсим `target/manifest.json` -> список моделей без description. Получаем число (например, 160 из 200).\n\n2. **Prioritize** (день 1-2):\n - Marts (consumer-facing): 50 моделей — **сначала**\n - Staging (важно для onboarding): 40 моделей\n - Intermediate (часто эфемерные): 30 — последними\n\n3. **Sprint planning** (ongoing):\n - Каждый sprint (2 weeks): 5-10 моделей с descriptions\n - Включить knowledgeable analysts для проверки бизнес-смысла\n - Использовать template (grain, business, formula, update, owner, connections)\n\n4. **PR-чеклист**:\n - Любой PR в model без description — обязательно добавить\n - Reviewer enforces в каждом PR\n\n5. **CI gate** (после 80% coverage):\n - Python script проверяет manifest.json\n - Новая модель без description — PR fails CI\n - Это фиксирует прогресс\n\n6. **Quarterly stale review**:\n - Раз в квартал — проверить, что formulas в descriptions не разошлись с SQL\n - Кто-то рефакторил без обновления docs — caught\n\n7. **Glossary через doc blocks**:\n - Повторяющиеся термины (revenue, customer_id, churn) — в `_glossary.md` с doc blocks\n - Все модели ссылаются через `{{ doc('revenue') }}` — один источник правды\n\n**Что НЕ делать**:\n- Не переписывать всё за неделю — burnout, поверхностные descriptions, accuracy низкая\n- Не делать "ai-generated" — генерируют formal, бесполезные descriptions без бизнес-context\n- Не игнорировать stale (старые но technically present) — они врут, хуже отсутствия\n\nЗа квартал — 80% coverage с **качественными** descriptions. Это реалистично и устойчиво.

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

Результат: 0 из 0
Аналитический
Вопрос 1 из 5. Senior пришёл в legacy проект: 200 моделей, 80% без descriptions, marts с одной строкой 'Финансовая таблица', колонки revenue/amount без формул. Стратегия покрытия?

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

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

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

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