Data Platform/Systems/dbt
dbt (data build tool) is a tool to build data models with SQL. You write SQL files, dbt understands dependencies between them, and creates/updates tables/views in the data platform.
This page is for users who want to:
- Develop dbt models in dbt-jobs
- Run those models from stat hosts
- Use those tables in scheduled production jobs (through Airflow)
Prerequisite knowledge
Before starting, you should know:
- Basic
gitand GitLab flow (clone, branch, MR) - How to use stat machines.
- Basic Spark SQL (dbt runs through SparkSQL)
dbt vs Airflow DAGs
- Airflow is a scheduler/orchestrator: it decides when and in what order jobs run in production.
- dbt is a SQL transformation framework: it helps define SQL models + dependencies.
- In production, both are needed:
- dbt defines transformations.
- Airflow schedules and executes them.
Install and run on stat hosts
First, log in to a stat host. The following commands should be entered in a terminal, one line at a time.
Clone and enter the git repository where dbt models are defined:
git clone https://gitlab.wikimedia.org/repos/data-engineering/dbt-jobs.git
cd dbt-jobs
Install environment and dependencies:
make dbt-install
source conda-analytics-activate dbt-jobs
Note: make dbt-install is only needed the first time the project is setup. In future sessions, only source conda-analytics-activate dbt-jobs is needed to enable dbt commands.
Now you can run dbt commands, for example:
dbt run -s my_model_name
dbt test -s my_model_name
How dbt-jobs is organized by teams
dbt-jobs is a shared dbt repository used by multiple teams/projects.
Each team/project can have its own folder structure, usually under models/.
Most people will mainly work in models/, because that is where model SQL files live. For more information about folders in the repository, check the README.md or dbt documentation.
About folder conventions:
- dbt recommends using
staging,intermediate, andmartsfolders, but anyone can organize differently if that works better. Folders don't change how dbt works, they are a way of structuring models. It's important to note that multiple models cannot have the same name even if they are in different folders. - Models can have configurations, like tags, or destination schemas. They can be defined at model level, or they can be configured automatically for entire folders in
dbt_project.ymle.g:
movement-insights-contributors-metrics:
+schema: wmf_contributors
+tags: ["monthly"]
Schema behavior:
- Production (Airflow): models write to the schema configured in
dbt_project.yml - Manual runs on stat hosts: models write under
username_schemaname
How to iterate and develop a new model
- Create (or choose) a folder for your team/project in
models/. - Configure that folder in
dbt_project.ymlwith at least:+schema+tags- (optionally) materialization and other settings
- Add your SQL file inside that folder.
- The file name becomes the table name.
- Write your query.
- Run dbt:
dbt run -s my_new_model
Referencing tables correctly: source() vs ref()
In dbt, avoid hardcoding table names in FROM ....
When writing something like:
SELECT * FROM ...
the object should usually be referenced with either source() or ref():
ref('model_name')- Use for other dbt models in this same repository
- dbt tracks dependencies and build order automatically
source('source_name', 'table_name')- Use for tables created outside this repo/dbt project
- Source metadata is defined in YAML (
models/sources.ymlor team-specific source files)
Example:
SELECT *
FROM {{ ref('int_webrequest_requestctl_daily') }}
SELECT *
FROM {{ source('wmf', 'mediawiki_history') }}
Querying new tables (adding sources)
If you need to query an external table not yet declared as a source:
- Add it to a source YAML file (for example
models/sources.ymlor a team-local source file):
version: 2
sources:
- name: my_external_schema
schema: my_external_schema
tables:
- name: my_external_table
- Use it in SQL:
SELECT *
FROM {{ source('my_external_schema', 'my_external_table') }}
- Run dbt:
dbt run -s my_model
This keeps lineage, docs, and dependency behavior consistent.
dbt coding style guidelines (linter)
A linter is a tool that checks code style and common quality issues before merge. In this repo, the linter is SQLFluff (configured for dbt + Spark SQL).
Why this matters:
- Keeps SQL style consistent across teams
- Improves readability and long-term maintainability
- Helps preventing common mistakes
You can sqlfluff manually from the stat hosts or from local, both to detect errors and to fix most of the formatting issues automatically.
Commands:
sqlfluff lint
sqlfluff fix
Recommended habit:
- Run
sqlfluff lintbefore opening/updating your MR - Use
sqlfluff fixfor automatic fixes, then review changes
Documentation, dbt-docs
Dbt comes with a built-in mechanism to generate project documentation.
To generate the documentation and serve it, perform the following steps:
- Ssh into a stats machine (e.g. stat1008)), set up the dbt conda environment and change into the checked out dbt-jobs repository (see above)
- To generate the documentation locally, run the following command in the dbt-jobs repository root folder:
dbt docs generate - To serve the documentation via a local webserver run the dbt docs serve command, best with a dedicated non in use port (e.g. 8765):
dbt docs serve --port 8765 - In a new terminal on your local machine create a tunnel to the stats machine with the specified port:
ssh -N stat1008.eqiad.wmnet -L 8765:127.0.0.1:8765 - Open a browser on your local machine pointing to the forwarded address: http://localhost:8765
For additional options for generating and serving dbt documentation consult the documentation page.
FAQ / How-to
What is dbt deps and when should I run it?
dbt deps installs dbt packages declared in packages.yml (for example utility packages like dbt_utils).
Run it when:
- You clone the repo for the first time
packages.ymlchanges- dbt asks for missing dependencies
On stat hosts:
set_proxy
dbt deps
unset_proxy
Without dbt deps, models/macros from external packages will not be available.
How to manually backfill a model
Airflow allows to backfill a model since the configured `start_date` in the DAG, which often is not enough. To backfill a model further than that we need to enter into a `an-launcher` server and run the desired `dbt` command as the `analytics` user.
Steps to do it:
ssh an-launcher1003.eqiad.wmnetsource /opt/conda-analytics/bin/activate- Clone the repository:
git clone https://gitlab.wikimedia.org/repos/data-engineering/dbt-jobs.git cd dbt-jobs- Make sure
logs/andtarget/folder exist and theanalyticsuser can write on them.mkdir logs && mkdir targetchmod 777 logs && chmod 777 target
- Make sure you use a
screenor similar to avoid issues if the connection is lost. - Run the dbt command in a single run or script:
Some examples:
start_date=2020-07-01
sudo -u analytics /opt/conda-analytics/bin/dbt run --full-refresh --project-dir . --profiles-dir . --target production --select "+base_moderator_actions" --exclude "base_moderator_actions_html" --vars "{\"start_date\": \"${start_date}\"}"
start_date=2020-08-01
while [ "$start_date" != 2026-06-01 ]; do
sudo -u analytics /opt/conda-analytics/bin/dbt run --project-dir . --profiles-dir . --target production --select "+base_moderator_actions" --exclude "base_moderator_actions_html" --vars "{\"start_date\": \"${start_date}\"}"
start_date=$(date -I -d "$start_date + 1 month")
done
Troubleshooting
Errors when changing a model's schema
When adding or removing columns from a model- take the following steps to avoid errors:
- Set
on_schema_changein the model config at the top of the sql file. This tells dbt what to do when the schema changes. The default value isignorewhich causes dbt to either return an error or make it so your newly added column doesn't appear in the table. Choosesync_all_columns,append_new_columns, orfaildepending on the desired behavior and how tolerant you are of unexpected schema changes. - update the model's
.ymlentry to reflect the changes. E.g. if you removecolumn_ainexample_model.sqlmake sure you also remove thecolumn_alines from the yml file. If you addcolumn_bto the model's sql file, add it to the yml file too. - include
full-refreshto apply schema changes to incremental models. E.g.dbt run --full-refresh -s my_model
If you don't follow these steps, you may find errors like the following in the dbt output:
17:18:27 Failure in model base_moderator_actions_history (models/movement-insights-contributors-metrics/base/base_moderator_actions_history.sql)
17:18:27 Runtime Error in model base_moderator_actions_history (models/movement-insights-contributors-metrics/base/base_moderator_actions_history.sql)
Runtime Error
Cannot update missing field user_is_extendedconfirmed in spark_catalog.cmyrick_wmf_contributors.base_moderator_actions_history
or
20:13:22 Failure in model base_moderator_actions_reverts (models/movement-insights-contributors-metrics/base/base_moderator_actions_reverts.sql)
20:13:22 Runtime Error in model base_moderator_actions_reverts (models/movement-insights-contributors-metrics/base/base_moderator_actions_reverts.sql)
Runtime Error
cannot resolve '`snapshot`' given input columns: