Перейти к основному содержимому

Настройка базы данных, схемы и алиаса моделей dbt

Продвинутый уровень
Menu

    Введение​

    Это руководство объясняет, как настраивать соглашения об именовании схем, баз данных и алиасов в dbt, чтобы они соответствовали требованиям управления и дизайна вашего хранилища данных.
    Когда мы разрабатываем модели dbt и выполняем определённые команды (например, dbt run или dbt build), объекты (такие как таблицы и представления) создаются в хранилище данных в соответствии с этими соглашениями об именовании.

    Немного о терминологии

    Разные хранилища данных используют разные названия для логических баз данных. Информация в этом документе относится к «databases» в Snowflake, Redshift и Postgres; «projects» в BigQuery; и «catalogs» в Databricks Unity Catalog.

    Ниже описано поведение dbt «из коробки» по умолчанию:

    • База данных, в которой создаётся объект, определяется базой данных, настроенной на уровне окружения в dbt или в файле profiles.yml в dbt v1.

    • Схема зависит от того, определили ли вы пользовательскую схему для модели:

      • Если вы не определили пользовательскую схему, dbt создаёт объект в схеме по умолчанию. В dbt это обычно dbt_username для разработки и схема по умолчанию для окружений развёртывания. В dbt v1 используется схема, указанная в файле profiles.yml.
      • Если вы определили пользовательскую схему, dbt объединяет упомянутую выше схему с пользовательской.
      • Например, если настроенная схема — dbt_myschema, а пользовательская — marketing, объекты будут созданы в схеме dbt_myschema_marketing.
      • Обратите внимание: для автоматизированных заданий CI имя схемы формируется на основе номера задания и номера PR: dbt_cloud_pr_<job_id>_<pr_id>.
    • Имя объекта зависит от того, задан ли для модели псевдоним.

      • Без псевдонима dbt использует имя модели без расширения .sql или .py. Например, fct_orders_complete.sql в пользовательской схеме marketing создаст объект prod.marketing.fct_orders_complete, если целевая база данных — prod.
      • При наличии псевдонима dbt использует его как имя объекта. Например, при alias: fct_orders та же модель создаст объект prod.marketing.fct_orders.

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

    Значения по умолчанию позволяют разработчикам работать в изолированных схемах (sandbox) и не перезаписывать работу друг друга — даже если они работают с одними и теми же таблицами.

    Как настроить это поведение​

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

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

    Или, в зависимости от архитектуры вашего хранилища, вы можете захотеть минимизировать количество схем в dev-окружении (и избежать разрастания схем за счёт отказа от создания комбинаций всех пользовательских схем и кастомных схем).

    В качестве альтернативы, вы можете захотеть, чтобы dev-схемы назывались по имени feature-ветки, а не по имени разработчика.

    По этой причине dbt предоставляет три макроса для настройки того, где именно объекты создаются в хранилище данных:

    Переопределяя один или несколько из этих макросов, мы можем настроить размещение объектов dbt в хранилище данных и привести его в соответствие с любыми существующими требованиями.

    Ключевая концепция

    Модели, запускаемые из разных контекстов, должны приводить к созданию уникальных объектов в хранилище данных.
    Например, разработчик Suzie работает над улучшениями fct_player_stats, а Darren разрабатывает тот же самый объект.

    Чтобы предотвратить перезапись работы друг друга, и Suzie, и Darren должны иметь собственные уникальные версии fct_player_stats в dev-окружении.

    Кроме того, staging-версия fct_player_stats должна существовать в уникальном месте, отличном от dev-версий и production-версии.

    При кастомизации этих макросов мы часто используем следующее:

    • В dbt мы рекомендуем использовать переменные окружения, чтобы указать, в каком окружении выполняется вызов dbt (dev/stg/prod).
      • Их можно задать на уровне окружения, и все задания автоматически унаследуют значения по умолчанию. Мы добавим логику Jinja (if/else/endif), чтобы определить, выполняется ли запуск в dev, prod, CI или другом окружении.
    • В качестве альтернативы переменным окружения можно использовать target.name. Дополнительные сведения см. в разделе О переменных target.
    Имя целевого окружения в переменных окружения для пользовательской схемы.Имя целевого окружения в переменных окружения для пользовательской схемы.

    Чтобы имя базы данных/схемы/объекта зависело от текущей ветки, можно использовать встроенную переменную окружения DBT_CLOUD_GIT_BRANCH из числа специальных переменных окружения dbt.

    Примеры использования​

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

    примечание

    Обратите внимание, что следующие примеры не являются исчерпывающими и не покрывают все возможные варианты. Они предназначены как шаблоны для разработки собственных правил.

    1. Кастомные схемы без конкатенации target-схемы в production​

    Наиболее распространённый сценарий — использование кастомной схемы без конкатенации с именем схемы по умолчанию в production.

    Для этого можно создать новый файл generate_schema_name.sql в папке macros со следующим кодом:

    macros/generate_schema_name.sql
    {% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}

    {{ default_schema }}

    {%- elif env_var('DBT_ENV_TYPE','DEV') == 'PROD' -%}

    {{ custom_schema_name | trim }}

    {%- else -%}

    {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- endif -%}

    {%- endmacro %}

    Данный подход сгенерирует следующие результаты для модели my_model с кастомной схемой marketing, предотвращая пересечения объектов между запусками dbt из разных контекстов.

    ContextTarget databaseTarget schemaResulting object
    Developer 1devdbt_dev1dev.dbt_dev1_marketing.my_model
    Developer 2devdbt_dev2dev.dbt_dev2_marketing.my_model
    CI PR 123cidbt_pr_123ci.dbt_pr_123_marketing.my_model
    CI PR 234cidbt_pr_234ci.dbt_pr_234_marketing.my_model
    Productionprodanalyticsprod.marketing.my_model
    примечание

    Мы добавили логику проверки, выполняется ли текущий запуск dbt в production. Это важно, и мы объясняем почему в разделе What not to do.

    2. Статические схемы: добавление идентификаторов разработчиков к таблицам​

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

    В этом случае можно:

    • Создать новый файл generate_schema_name.sql в папке macros со следующим кодом.
    • Изменить generate_schema_name() так, чтобы использовалась одна схема для всех разработчиков, даже если задана кастомная схема.
    • Обновить generate_alias_name(), чтобы в dev-окружении добавлять алиас разработчика и кастомную схему в начало имени таблицы.
      • Этот метод не идеален, так как приводит к длинным именам таблиц, но позволяет разработчикам видеть, в какой схеме модель будет создана в production.
    macros/generate_schema_name.sql
    {% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}

    {{ default_schema }}

    {%- elif env_var('DBT_ENV_TYPE','DEV') != 'CI' -%}

    {{ custom_schema_name | trim }}

    {%- else -%}

    {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- endif -%}

    {%- endmacro %}
    macros/generate_alias_name.sql
    {% macro generate_alias_name(custom_alias_name=none, node=none) -%}

    {%- if env_var('DBT_ENV_TYPE','DEV') == 'DEV' -%}

    {%- if custom_alias_name -%}

    {{ target.schema }}__{{ custom_alias_name | trim }}

    {%- elif node.version -%}

    {{ target.schema }}__{{ node.name ~ "_v" ~ (node.version | replace(".", "_")) }}

    {%- else -%}

    {{ target.schema }}__{{ node.name }}

    {%- endif -%}

    {%- else -%}

    {%- if custom_alias_name -%}

    {{ custom_alias_name | trim }}

    {%- elif node.version -%}

    {{ return(node.name ~ "_v" ~ (node.version | replace(".", "_"))) }}

    {%- else -%}

    {{ node.name }}

    {%- endif -%}

    {%- endif -%}

    {%- endmacro %}

    Данный подход сгенерирует следующие результаты для модели my_model с кастомной схемой marketing, предотвращая пересечения объектов между запусками dbt из разных контекстов.

    ContextTarget databaseTarget schemaResulting object
    Developer 1devdbt_dev1dev.marketing.dbt_dev1_my_model
    Developer 2devdbt_dev2dev.marketing.dbt_dev2_my_model
    CI PR 123cidbt_pr_123ci.dbt_pr_123_marketing.my_model
    CI PR 234cidbt_pr_234ci.dbt_pr_234_marketing.my_model
    Productionprodanalyticsprod.marketing.my_model

    3. Использование имени ветки в качестве префикса схемы​

    Для команд, которые предпочитают изолировать работу по feature-веткам, можно воспользоваться специальной переменной окружения DBT_CLOUD_GIT_BRANCH. Обратите внимание, что разработчики будут писать в одну и ту же схему, если они находятся в одной и той же feature-ветке.

    примечание

    Переменная DBT_CLOUD_GIT_BRANCH доступна только в Studio IDE и недоступна в dbt platform CLI.

    Мы также встречали организации, которые предпочитают организовывать dev-базы данных по имени ветки. Для этого требуется реализовать аналогичную логику в generate_database_name(), а не в макросе generate_schema_name(). По умолчанию dbt не создаёт базы данных автоматически.

    Подробнее см. в разделе Tips and tricks.

    macros/generate_schema_name.sql
    {% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}
    {%- if env_var('DBT_ENV_TYPE','DEV') == 'DEV' -%}

    {#- we replace characters not allowed in the schema names by "_" -#}
    {%- set re = modules.re -%}
    {%- set cleaned_branch = re.sub("\W", "_", env_var('DBT_CLOUD_GIT_BRANCH')) -%}

    {%- if custom_schema_name is none -%}

    {{ cleaned_branch }}

    {%- else -%}

    {{ cleaned_branch }}_{{ custom_schema_name | trim }}

    {%- endif -%}

    {%- else -%}

    {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- endif -%}

    {%- endmacro %}

    Данный подход сгенерирует следующие результаты для модели my_model с кастомной схемой marketing, предотвращая пересечения объектов между запусками dbt из разных контекстов.

    ContextBranchTarget databaseTarget schemaResulting object
    Developer 1featureABCdevdbt_dev1dev.featureABC_marketing.my_model
    Developer 2featureABCdevdbt_dev2dev.featureABC_marketing.my_model
    Developer 1feature123devdbt_dev1dev.feature123_marketing.my_model
    CI PR 123cidbt_pr_123ci.dbt_pr_123_marketing.my_model
    CI PR 234cidbt_pr_234ci.dbt_pr_234_marketing.my_model
    Productionprodanalyticsprod.marketing.my_model

    Когда developer 1 и developer 2 находятся на одной и той же ветке, они будут генерировать один и тот же объект в хранилище данных. Это не является проблемой, так как нахождение на одной ветке означает, что код модели будет одинаковым для обоих разработчиков.

    4. Использование статической схемы для CI​

    Некоторые организации предпочитают писать результаты CI-задач в одну статическую схему, добавляя идентификатор PR в начало имени таблицы. Важно отметить, что это приводит к длинным именам таблиц.

    Для этого можно создать новый файл generate_schema_name.sql в папке macros со следующим кодом:

    macros/generate_schema_name.sql
    {% macro generate_schema_name(custom_schema_name=none, node=none) -%}

    {%- set default_schema = target.schema -%}

    {# If the CI Job does not exist in its own environment, use the target.name variable inside the job instead #}
    {# {%- if target.name == 'CI' -%} #}

    {%- if env_var('DBT_ENV_TYPE','DEV') == 'CI' -%}

    ci_schema

    {%- elif custom_schema_name is none -%}

    {{ default_schema }}

    {%- else -%}

    {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- endif -%}

    {%- endmacro %}
    macros/generate_alias_name.sql
    {% macro generate_alias_name(custom_alias_name=none, node=none) -%}

    {# If the CI Job does not exist in its own environment, use the target.name variable inside the job instead #}
    {# {%- if target.name == 'CI' -%} #}
    {%- if env_var('DBT_ENV_TYPE','DEV') == 'CI' -%}

    {%- if custom_alias_name -%}

    {{ target.schema }}__{{ node.config.schema }}__{{ custom_alias_name | trim }}

    {%- elif node.version -%}

    {{ target.schema }}__{{ node.config.schema }}__{{ node.name ~ "_v" ~ (node.version | replace(".", "_")) }}

    {%- else -%}

    {{ target.schema }}__{{ node.config.schema }}__{{ node.name }}

    {%- endif -%}

    {%- else -%}

    {%- if custom_alias_name -%}

    {{ custom_alias_name | trim }}

    {%- elif node.version -%}

    {{ return(node.name ~ "_v" ~ (node.version | replace(".", "_"))) }}

    {%- else -%}

    {{ node.name }}

    {%- endif -%}

    {%- endif -%}

    {%- endmacro %}

    Данный подход сгенерирует следующие результаты для модели my_model с кастомной схемой marketing, предотвращая пересечения объектов между запусками dbt из разных контекстов.

    ContextTarget databaseTarget schemaResulting object
    Developer 1devdbt_dev1dev.dbt_dev1_marketing.my_model
    Developer 2devdbt_dev2dev.dbt_dev2_marketing.my_model
    CI PR 123cidbt_pr_123ci.ci_schema.dbt_pr_123_marketing_my_model
    CI PR 234cidbt_pr_234ci.ci_schema.dbt_pr_234_marketing_my_model
    Productionprodanalyticsprod.marketing.my_model

    Чего делать не стоит​

    В этом разделе описано, каких действий следует избегать при настройке схем и алиасов из‑за возможных проблем.

    Обновление generate_schema_name() так, чтобы всегда использовать кастомную схему​

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

    Проблема​

    При изменении стандартного макроса generate_schema_name() это может привести к созданию следующей версии:

    macros/generate_schema_name.sql
    {% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}

    {{ default_schema }}

    {%- else -%}
    # The following is incorrect as it omits {{ default_schema }} before {{ custom_schema_name | trim }}.
    {{ custom_schema_name | trim }}

    {%- endif -%}

    {%- endmacro %}

    Хотя это может давать ожидаемый результат в production, где используется выделенная база данных, такое решение приведёт к конфликтам везде, где несколько людей используют одну и ту же базу данных.

    Рассмотрим пример модели my_model с кастомной схемой marketing.

    ContextTarget databaseTarget schemaResulting object
    Productionprodanalyticsprod.marketing.my_model
    Developer 1devdbt_dev1dev.marketing.my_model
    Developer 2devdbt_dev2dev.marketing.my_model
    CI PR 123cidbt_pr_123ci.marketing.my_model
    CI PR 234cidbt_pr_234ci.marketing.my_model

    Мы видим, что и developer 1, и developer 2 получают один и тот же объект my_model. Это означает, что при одновременной работе над моделью невозможно понять, чья версия сейчас находится в хранилище данных.

    Аналогично, разные PR будут приводить к созданию одного и того же объекта в хранилище данных. Если несколько PR открыты одновременно и изменяют одни и те же модели, с высокой вероятностью возникнут проблемы, замедляющие разработку и продвижение кода.

    Решение​

    Как описано в предыдущем примере, необходимо обновить макрос так, чтобы он проверял, выполняется ли dbt в production. Только в production следует убирать конкатенацию и использовать одну лишь кастомную схему.

    Советы и рекомендации​

    В этом разделе приведены полезные советы по корректной настройке макросов generate_database_name() и generate_alias_name().

    Создание несуществующих баз данных из dbt​

    dbt автоматически пытается создать схему, если она не существует и если в неё нужно создать объект, но он не пытается автоматически создавать базу данных, если она не существует.

    Поэтому, если ваша конфигурация generate_database_name() указывает на разные базы данных, которые могут отсутствовать, dbt завершится ошибкой при выполнении простого dbt build.

    Тем не менее, это можно реализовать, создав макросы, которые будут проверять существование базы данных и создавать её при необходимости. Такие макросы можно вызывать либо в шаге dbt run-operation ... в джобах, либо как on-run-start hook.

    Определение контекста через переменные окружения, а не через target.name​

    Мы предпочитаем использовать переменные окружения вместо target.name для определения контекста вызова dbt. Подробнее см. в разделе О переменных target.

    • target.name нельзя задать на уровне окружения. Поэтому в каждом задании внутри окружения необходимо явно переопределять значение target.name. Если в задании не задано подходящее значение target.name, база данных, схема или псевдоним могут определяться некорректно. В отличие от этого, значения переменных окружения наследуются заданиями из соответствующего окружения. При необходимости значения переменных окружения также можно переопределить в заданиях.
    Настройка псевдонима схемы с помощью переменной окружения.Настройка псевдонима схемы с помощью переменной окружения.
    • При использовании target.name каждый разработчик должен указать одно и то же значение (обычно ‘dev’) в поле имени target в своих пользовательских учётных данных для проекта. Если у разработчика не задано подходящее имя target, его база данных, схема или псевдоним могут определяться некорректно.
    Учётные данные пользователя.Учётные данные пользователя.

    Всегда требуйте указания пользовательских схем​

    Некоторые пользователи предпочитают принудительно задавать кастомные схемы для всех объектов в проекте. Это позволяет избежать записи в нежелательные «дефолтные» локации. Вы можете добавить соответствующую логику в макрос generate_schema_name(), чтобы вызывать ошибку компиляции, если кастомная схема для объекта не определена.

    macros/generate_schema_name.sql
     {% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}
    {%- set node_custom_schema = node.config.get('schema') -%}

    {%- if custom_schema_name is none and node_custom_schema is none and node.resource_type == 'model' -%}

    {{ exceptions.raise_compiler_error("Error: No Custom Schema Defined for the model " ~ node.name ) }}

    {%- elif custom_schema_name is none -%}

    {{ default_schema }}

    {%- elif env_var('DBT_ENV_TYPE','DEV') == 'PROD' -%}

    {{ custom_schema_name | trim }}

    {%- else -%}

    {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- endif -%}

    {%- endmacro %}

    Нашли ошибку?

    0