Learning Platform
Глоссарий Troubleshooting
Урок 14.01 · 20 мин
Начальный
SeedsCSVLookup tablesdbt seedReference data

Seeds: CSV как первоклассный citizen

В dbt существует три способа доставить данные в warehouse:

  • Source — данные уже лежат в warehouse, их туда положил кто-то другой (Fivetran, Airbyte, ручной INSERT). dbt просто декларирует, что эти таблицы существуют.
  • Seed — CSV-файл лежит в git-репозитории dbt-проекта. Команда dbt seed читает CSV и заливает его в warehouse как таблицу.
  • Snapshot — отдельный механизм для SCD2 (Slowly Changing Dimensions). Об этом — в следующих трёх уроках.

Этот урок целиком про seeds. Звучит просто: «CSV -> таблица». На практике seed — это критический паттерн для маленьких справочников, и есть жёсткое правило 5 MB, которое отделяет «хороший seed» от «git-репозитория, который тормозит на каждом checkout».


Зачем нужны seeds

Представьте, что вы строите маркетинговый mart, и вам нужна таблица соответствия country_code -> country_name -> region. Откуда её взять?

Варианты:

  1. Хардкод в SQLCASE WHEN country='US' THEN 'United States' WHEN ... на 200 строк. Нечитаемо, дублируется в каждой модели, нельзя пере-использовать.
  2. Загрузить через source — поднять отдельный pipeline Fivetran для CSV из Google Sheets. Overkill для 200 строк.
  3. Положить CSV в репозиторий и сделать seedseeds/country_codes.csv + dbt seed. Таблица появляется в warehouse, на неё можно делать ref('country_codes') как на обычную модель.

Seed — это версионированные данные. CSV лежит в git, поэтому:

  • Code review показывает diff: «инженер добавил Beirut в country_codes — нормально».
  • История изменений — git blame покажет, кто и когда поменял маппинг.
  • Развёртывание атомарно: новая модель + новые данные катятся одним PR.
Три способа доставить данные в dbt
SourceSource: данные уже в warehouse. dbt только декларирует таблицу через YAML (sources:). Сам не загружает. Pipeline в warehouse делает Fivetran/Airbyte/ETL-инструмент. Подходит для больших динамических данных: транзакции, события, логи.
SeedSeed: CSV в seeds/ директории. dbt seed читает CSV и создаёт таблицу в warehouse. Подходит для маленьких справочников менее 5 MB: country codes, mappings, конфиги. Версионируется в git.
SnapshotSnapshot: специальный механизм SCD2. dbt snapshot отслеживает изменения в source-таблице и хранит историю. Подходит для медленно меняющихся измерений: customer addresses, product prices, employee statuses.

Где живут seeds

В стандартном dbt-проекте директория seeds/ лежит в корне:

jaffle_shop/
  dbt_project.yml
  models/
  seeds/
    country_codes.csv
    product_categories.csv
  snapshots/
  tests/
  macros/

Путь настраивается в dbt_project.yml:

seed-paths: ["seeds"]

Можно изменить на ["data"] или указать несколько директорий — но 99% проектов оставляют дефолт.


Анатомия CSV-файла seed

Минимальный seed выглядит так. Файл seeds/country_codes.csv:

country_code,country_name,region
US,United States,Americas
GB,United Kingdom,EMEA
JP,Japan,APAC
DE,Germany,EMEA
BR,Brazil,Americas

Правила, которым следует dbt:

  1. Первая строка — имена колонок. dbt использует их как имена столбцов в таблице.
  2. Разделитель — запятая (по умолчанию). Можно поменять, но об этом — в следующем уроке.
  3. Имя файла = имя таблицы. country_codes.csv создаст таблицу country_codes в warehouse.
  4. Типы данных автоматические. dbt пытается угадать тип по содержимому: строки идут в TEXT/VARCHAR, числа — в INTEGER/DOUBLE. Об этом тоже в следующем уроке (часто угадывает плохо, и нужно явно задавать).

dbt seed — что делает команда

Запустите:

dbt seed

Что произойдёт под капотом:

  1. dbt находит все CSV в seed-paths.
  2. Для каждого файла:
    • Парсит CSV (использует csv.DictReader Python).
    • Угадывает типы колонок (или читает из column_types config).
    • Дропает существующую таблицу (DROP TABLE IF EXISTS ...).
    • Создаёт пустую таблицу нужной схемы.
    • INSERT’ит строки батчами (по умолчанию батч = min(10000, # rows)).
  3. Возвращает run-result: успешные/упавшие seeds.

Типичный вывод:

$ dbt seed
14:21:03  Running with dbt=1.10.2
14:21:03  Registered adapter: duckdb=1.10.1
14:21:04  Found 2 seeds
14:21:04
14:21:04  Concurrency: 4 threads (target='dev')
14:21:04
14:21:04  1 of 2 START seed file main.country_codes ............... [RUN]
14:21:04  2 of 2 START seed file main.product_categories .......... [RUN]
14:21:04  1 of 2 OK loaded seed file main.country_codes ........... [INSERT 195 in 0.12s]
14:21:04  2 of 2 OK loaded seed file main.product_categories ...... [INSERT 24 in 0.05s]
14:21:04  Finished running 2 seeds in 0:00:01
14:21:04  Completed successfully

После этого таблицы country_codes и product_categories существуют в DuckDB. Проверим:

-- В DuckDB (или через dbt show)
SELECT * FROM country_codes LIMIT 3;
country_codecountry_nameregion
USUnited StatesAmericas
GBUnited KingdomEMEA
JPJapanAPAC

Использование seed в модели через ref()

После dbt seed таблица существует в warehouse. Чтобы использовать её в модели, делаете ref(), точно так же как на обычную модель:

-- models/marts/customers.sql
SELECT
    c.customer_id,
    c.country_code,
    cc.country_name,
    cc.region
FROM {{ ref('stg_jaffle__customers') }} c
LEFT JOIN {{ ref('country_codes') }} cc
    ON c.country_code = cc.country_code

Важно: seed — node в DAG, у него есть depends-on. Если запустить dbt run --select customers, dbt не запустит seed автоматически (это не source, это нода). Чтобы прогнать seed перед моделью, используйте dbt build или явный +:

dbt build --select +customers   # запустит seed -> staging -> customers + тесты
dbt seed --select country_codes # только этот seed
dbt run --select country_codes+ # seed + все downstream
NOTE

dbt build — самая удобная команда для CI/CD: она запускает seeds -> snapshots -> models -> tests в правильном порядке по DAG. Если кто-то изменил CSV, новые данные попадут в warehouse одной командой.


Когда брать seed, а когда — нет

Главный признак — размер и динамика данных.

Когда seed, когда source

Правила, которые работают:

  1. менее 5 MB или менее 50 000 строк -> seed это нормально.
  2. Меняется реже раза в день -> seed нормально. Если чаще — нужен внешний pipeline.
  3. Хочу видеть изменения в PR -> seed (потому что diff в git).
  4. Не хочу видеть изменения в PR -> source (потому что это «грязные» внешние данные).

Жёсткое правило 5 MB

Почему именно 5 MB? Это не магическое число dbt, это практика git и code review.

CSV — это plain text. Файл на 50 000 строк (типичная маленькая lookup-таблица) — 2–5 MB. Файл на 500 000 строк — 50 MB. Что происходит с репозиторием при 50 MB seed:

  1. git clone тормозит. Не на 5 секунд, а на 30–60 на медленной сети. Каждый новый разработчик/CI-раннер.
  2. git status тормозит. Git считает hash каждого файла; крупные текстовые файлы дольше хешируются.
  3. Code review бесполезен. GitHub не показывает diff > 1 MB по умолчанию. Reviewer не увидит, что строка 32 451 поменялась с «Tashkent» на «Toshkent».
  4. History раздувается. Каждое изменение CSV хранится в .git/ целиком. 10 правок по 50 MB = 500 MB в .git/objects/.

Если данные больше 5 MB — это не lookup-таблица, а датасет. Загружайте через source.

WARNING

Если уже добавили большой CSV и закоммитили — git rm его не уменьшает репозиторий. Файл остаётся в history. Чистка через git filter-repo или BFG Repo-Cleaner — это операция, которая ломает hashes commit’ов у всей команды. Лучше не доводить.


Что не делает seed

dbt seed — это примитивный механизм. Он не умеет:

  • Append. Каждый запуск делает DROP + CREATE + INSERT, то есть полностью пере-загружает таблицу. Если нужен инкремент — это не seed, это incremental model.
  • JOIN с другими seed. Seed читает CSV как есть, никакой SQL-обработки.
  • Парсить сложные форматы. CSV-only. JSON/Parquet/XLSX не поддерживается из коробки (можно загрузить через read_csv / read_parquet source-конфиги в DuckDB, но это уже source).
  • Транзакционно атомарно. На большинстве warehouse DROP TABLE коммитится сразу, поэтому в окне между DROP и INSERT таблица не существует. Downstream запросы упадут с «relation not found». Для production это редко проблема (seed редко запускается), но знать стоит.

Попробуй сам

Создайте файл seeds/country_codes.csv в вашем dbt-проекте:

country_code,country_name,region
US,United States,Americas
GB,United Kingdom,EMEA
JP,Japan,APAC
RU,Russia,EMEA
CN,China,APAC

Запустите:

dbt seed
dbt show --inline "SELECT * FROM {{ ref('country_codes') }}"

Теперь измените одну строку в CSV — например, «Russia» -> «Russian Federation». Запустите dbt seed снова. Заметьте: dbt не делает «UPDATE country_name SET …». Он делает полный DROP + CREATE + INSERT. Если у вас downstream-модели читают эту таблицу — они увидят новые данные после следующего dbt run.

Бонус-задача: сделайте простую модель models/marts/customers_enriched.sql, которая JOIN-ит staging-таблицу клиентов с country_codes через ref('country_codes'). Запустите dbt build --select +customers_enriched — обратите внимание, что dbt запустит seed автоматически перед моделью.


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

  1. Seed — это CSV в git-репозитории, который dbt загружает в warehouse как обычную таблицу. Доступен через ref('seed_name') так же, как модель.
  2. Используется для маленьких lookup-таблиц менее 5 MB: country codes, mappings, configs. Версионируется в git, изменения видны в PR.
  3. Не для больших датасетов. > 5 MB ломает git workflow: clone тормозит, diff не показывается, history раздувается.
  4. dbt seed делает полный DROP + CREATE + INSERT каждый раз. Нет append, нет incremental.
  5. dbt build — правильная команда: запускает seeds -> snapshots -> models -> tests в порядке DAG.
  6. Seed — это нода в DAG. dbt run сам по себе не запустит seed; нужно dbt seed, dbt build, или явное включение в селектор.
Почему Git ломается на больших файлах
Проверка знанийKnowledge check
В dbt-проекте лежит файл `seeds/exchange_rates.csv` на 80 000 строк (примерно 4 MB). Это правильное место для такого файла?
ОтветAnswer
Граница 5 MB / 50k строк не жёсткая, а ориентир для здоровья git-репозитория. 4 MB / 80k строк — это пограничная зона, и важнее **частота изменений**, чем размер. Если CSV редактируется раз в полгода (например, исторические курсы валют, которые не меняются задним числом) — это нормальный seed. Если же CSV пере-генерируется каждый день из внешнего API — это **не данные для git**, и нужен source через Fivetran / Airbyte / ad-hoc INSERT. Признак, что seed не подходит: вы пишете скрипт "сходи в API -> сгенерируй CSV -> сделай git commit". Это очевидно неправильный pipeline; данные должны попадать в warehouse напрямую.
Проверка знанийKnowledge check
Команда инженера запустила `dbt run --select my_model`, где `my_model` зависит от seed `country_codes`. dbt пишет 'relation main.country_codes does not exist'. Что произошло?
ОтветAnswer
Seed — это нода в DAG, но **не source**. `dbt run` запускает только models, не seeds. Если seed никогда не был запущен (или таблица была удалена), `dbt run` упадёт. Решения: \n1. `dbt seed` один раз, потом `dbt run`. \n2. `dbt build --select +my_model` — `build` идёт по всему DAG включая seeds. \n3. `dbt run --select +my_model` НЕ запускает seeds (только models в селекции). \n4. В CI используйте `dbt build`, а не `dbt run` — это идиоматический паттерн.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. В dbt-проекте лежит файл `seeds/exchange_rates.csv` размером 80 MB. Команда заметила, что `git clone` стал тормозить, а GitHub не показывает diff в PR. Какое правильное решение?

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

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

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

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