Multi-profile / multi-target DAG¶
Run one dbt project across two dbt targets (or profiles) from a single DAG.
Typical case: some models must use dbt-glue (talks to a live Glue
Interactive Session) while the rest use dbt-spark inside a Glue Spark Job.
Setup¶
1. profiles.yml with two targets¶
my_project:
target: dev
outputs:
spark_dev: # used by the Spark JobRunner
type: spark
method: session
# ... etc
shell_dev: # used by the Python-Shell runner
type: duckdb
path: ":memory:"
# ... etc
2. Runner-level defaults¶
from dbt_aws.spark.runners import GlueSparkRunner
from dbt_aws.nonspark.runners import GluePythonShellRunner
spark = GlueSparkRunner(
mode="create",
iam_role_name="Glue-Job-Role",
# NEW --------------------------------------------
target="spark_dev", # every task from this runner
# gets --target spark_dev
# profile_name unset -> dbt uses the profile named in
# dbt_project.yml (`profile: my_project`)
)
shell = GluePythonShellRunner(
mode="create",
iam_role_name="Glue-Job-Role",
# NEW --------------------------------------------
target="shell_dev",
profile_name="my_project", # explicit --profile my_project
)
3. Compose them in one DAG¶
Two options for picking the runner per model.
Option A -- bulk by tag:
from dbt_aws.common.builder import DbtDag
dag = DbtDag(
dag_id="daily",
project=...,
runners={"spark": spark, "shell": shell},
default_runner="spark",
tag_runners={"landing": "shell"}, # tag "landing" -> shell runner
project_archive_s3="s3://...",
target="spark_dev", # DAG-level fallback
)
Every model tagged landing runs on the shell runner with
--target shell_dev --profile my_project. Everything else runs on
Spark with --target spark_dev and no --profile flag.
Option B -- per model in the dbt project:
-- models/landing/redshift_ingest.sql
{{ config(
meta={"stratus": {"runner": "shell",
"target": "shell_dev",
"profile_name": "my_project"}}
) }}
select 1 as n
The meta.stratus.* block is read at DAG-parse time; no DAG code
change needed to move a model between runners/targets.
Precedence¶
For any node, the effective --target and --profile-name come from
the first layer below that sets a value:
1. overrides={<uid>: {"target": ..., "profile_name": ...}}
2. meta.stratus.target / .profile_name (in the dbt model YAML/SQL)
3. tag_targets[<tag>] / tag_profiles[<tag>] (on DbtDag)
4. runner.target / runner.profile_name (on the runner constructor)
5. DbtDag(target=...) (DAG-level; profile has no DAG-level knob)
The two ladders resolve independently. A model can pick its target via
tag_targets while still using its runner's default profile_name.
Verifying what got sent¶
Every --target / --profile-name value ends up in the Glue
JobRun's Arguments. Check with aws glue get-job-runs:
$ aws glue get-job-runs --job-name my-glue-job --max-results 1 \
--query 'JobRuns[0].Arguments' --output json
{
"--command": "run",
"--select": "landing_model",
"--target": "shell_dev",
"--profile-name": "my_project",
...
}
The worker_entrypoint.py script consumes these flags and translates them
to dbt run --target ... --profile ... on the worker.
Parse-time visibility¶
When either feature is used, the builder logs the distribution at DAG-parse time (visible in the Airflow scheduler / dag-processor log):
INFO dbt_aws.common.builder runner distribution: shell=2, spark=3
INFO dbt_aws.common.builder target distribution: shell_dev=2, spark_dev=3
A WARNING fires when a tag_targets or tag_profiles entry references
a tag no selected node carries -- typo guard.
Runnable examples¶
Two example DAGs demonstrate the profile/target ladder end-to-end:
dag_smoke_profile_target_ladder.py-- local-only DAG that exercises every layer of the ladder and asserts each task'sscript_argsat DAG-parse time. Safe to load without AWS creds.dag_smoke_profile_target_ladder_real.py-- same feature set, hitting real Glue Spark + Glue Python Shell. Requires AWS credentials plus the IAM / S3 setup described in Deployment prerequisites.
When NOT to use this¶
- Every model runs the same profile and target -- just use
DbtDag(target=...)and let dbt pick the profile fromdbt_project.yml. - Different runners already carry different
target=inrunners.yml-- no per-tag / per-model overrides needed.
The feature is opt-in: DAGs that don't set target= / profile_name= on any
layer see byte-identical behaviour, no extra log lines, no new args
in the worker script.