dbt™ Model Query Cost Optimizer — BigQuery
Automate BigQuery dbt™ cost optimization with a DinoAI agent that identifies expensive models, rewrites SQL anti-patterns, opens a PR, and posts a cost summary to Slack.
Automatically detect your most expensive dbt™ models, diagnose SQL anti-patterns in the model source files, apply targeted rewrites, and open a pull request — all triggered by a single DinoAI agent session. The agent reads BigQuery performance stats to understand where bytes and slot time are being wasted, makes the fixes directly in your repository, and posts the PR link to Slack without any manual intervention.
Before You Start
Paradime
Your Paradime API endpoint, API key, and API secret — generate these under Workspace Settings → API. Make sure to enable
DinoAI agent APIcapabilities. Requires Admin access.
BigQuery
Your Paradime workspace must have an active BigQuery connection with sufficient permissions to read job metadata. The agent calls
get_bigquery_query_performance_statsto retrieve performance stats for the BigQuery jobs that each dbt™ model produced.
Model source
You need at least one of the following so the script knows which dbt™ models to analyse:
A Bolt schedule name — the script reads job IDs directly from the latest run's
run_results.jsonartifact, which maps each dbt™ model to the BigQuery job it producedOne or more BigQuery job IDs supplied directly via CLI flag or environment variable, if you already know which models are expensive
Integrations
The following must already be connected in Paradime:
Slack — the agent posts the PR link and a cost summary to
#finopsviapost_slack_message
What You'll Build
By the end of this guide you'll have:
A Poetry project wired to the Paradime SDK
A
run_query_optimizer.pytrigger script that reads dbt™ model results from Bolt artifacts (or accepts job IDs directly), filters to the top costly models by bytes processed, and hands the full context to the agentA
query-optimizerDinoAI agent YAML that diagnoses anti-patterns, rewrites dbt™ model SQL and config, opens a PR, and notifies Slack
What the Agent Does
Once triggered, the agent works through five phases without stopping:
The agent never drops columns referenced by downstream models, never pushes directly to main, and never guesses — if a business decision is required it leaves a TODO comment in the SQL and calls it out in the PR description.
Anti-Patterns the Agent Detects and Fixes
PARTITION_FILTER_MISSING
Partitioned table scanned without a partition filter
Adds partition_by config and an is_incremental() WHERE clause
NO_CLUSTERING
High-cardinality filter columns not declared as cluster_by
Adds cluster_by to the config block
WRONG_MATERIALIZATION
Frequently queried view causing repeated full scans
Changes to table or incremental materialization
INCREMENTAL_OPPORTUNITY
Full table with a natural time-based key
Converts to incremental with is_incremental() filter
SELECT_STAR
All columns selected, pulling unnecessary bytes
Enumerates only columns used by downstream models
CARTESIAN_FAN_OUT_JOIN
JOIN on non-unique key causing row multiplication
Adds QUALIFY ROW_NUMBER() deduplication
REPEATED_CTE
CTE referenced more than twice in the same query
Materialises as a separate intermediate model
UNFILTERED_LARGE_TABLE
Large source pulled with no predicate
Pushes a WHERE clause into the CTE
Architecture Overview
How It Works
run_query_optimizer.py loads configuration from environment variables, optionally reads the latest Bolt run's run_results.json artifact to extract the BigQuery job ID produced by each dbt™ model, filters to the top-N costliest models above a configurable bytes threshold (default: 10 GB), then triggers a single query-optimizer DinoAI agent session with the full model context pre-embedded in the opening message. The script polls every 20 seconds until the agent completes or times out (default: 60 minutes), then prints the full optimisation report to stdout. An optional follow-up drill-down on a single model can be sent within the same session.
Set Up the Poetry Project
Create the following pyproject.toml at the same directory level as your dbt_project.yml. This is required so the agent can locate and rewrite dbt™ model files correctly during the optimisation pass.
Install dependencies by running:
The only two third-party dependencies are requests (for downloading Bolt artifacts) and paradime-io (the Paradime SDK). All other imports — argparse, os, sys, textwrap, time — are part of the Python standard library.
Create the Agent YAML
Create the following file in your repository at .dinoai/agents/query-optimizer.yml. This defines the agent's role, five-phase goal, anti-pattern knowledge, guardrails, and Slack output channel.
tools.mode: allowlist restricts the agent to only the tools listed. This is important for a write-capable agent — it prevents accidental access to tools outside the intended optimisation workflow.
The Slack channel is set to #finops by default. Update slack.channel before committing if your team routes cost alerts to a different channel.
Create the Trigger Script
Create scripts/run_query_optimizer.py. This script reads dbt™ model results from Bolt artifacts (or accepts BigQuery job IDs directly via CLI), filters to the top costly models by bytes processed, builds a structured opening message with all pre-fetched metadata, triggers the query-optimizer agent, and polls until completion.
The script accepts --schedule-name or --job-ids but not both — they are mutually exclusive. When --schedule-name is used, the script reads run_results.json from the latest Bolt run and maps each dbt™ model name to the BigQuery job ID it produced. When --job-ids is used, the agent receives the job IDs directly and resolves the model files itself via performance stats.
When no models are found above the bytes threshold the script exits cleanly with code 0 — this makes it safe to run on a schedule even when recent Bolt runs had no costly models.
Set Your Environment Variables
Store your secrets in Paradime before scheduling. Go to Account Settings → Environment Variables and add the following:
PARADIME_API_ENDPOINT
Your Paradime GraphQL endpoint
PARADIME_API_KEY
Your Paradime API key
PARADIME_API_SECRET
Your Paradime API secret
BOLT_SCHEDULE_NAME
Name of the Bolt schedule whose run_results.json maps dbt™ models to their BigQuery job IDs
QUERY_OPTIMIZER_TOP_N
(optional) Max models per run — defaults to 5
QUERY_OPTIMIZER_MIN_BYTES_GB
(optional) GB threshold — defaults to 10
QUERY_OPTIMIZER_DRILL_MODEL
(optional) Model name to drill into after the main pass
QUERY_OPTIMIZER_TIMEOUT
(optional) Max seconds to wait per session — defaults to 3600
QUERY_OPTIMIZER_JOB_IDS
(optional) Comma-separated BigQuery job IDs to analyse directly — bypasses Bolt artifact fetch when you already know which dbt™ models are expensive
Your Paradime API endpoint, key, and secret are available under Workspace Settings → API. Make sure the key has DinoAI agent API capabilities enabled.
Run the Optimizer Manually
Option A — Automatically detect costly dbt™ models from a Bolt schedule:
Option B — Supply BigQuery job IDs directly (when you already know which models are expensive):
Option C — Include a follow-up drill-down on a specific dbt™ model:
You should see output like:
The agent then posts its full optimisation plan and PR link to stdout, and a cost summary to #finops on Slack.
Schedule with Bolt
Run the optimizer automatically on a recurring cadence so costly dbt™ models are caught and fixed without any manual intervention. The schedule reads the latest Bolt run's run_results.json, identifies the top offending models by bytes processed, and triggers the agent to fix and PR them.
Add Environment Variables to Paradime
Go to Account Settings → Environment Variables and confirm the variables from the previous step are saved. Bolt schedules read from the same environment variable store, so no additional setup is needed.
Create the Bolt Schedule
Go to Bolt → Schedules and click New Schedule. Name it something like Query Cost Optimizer and add the following two commands in order:
Replace your_dbt_production_schedule with the exact name of the Bolt schedule that runs your dbt™ models in production. The script reads that schedule's run_results.json artifact, which contains a record per dbt™ model including the BigQuery job ID it produced.
poetry install runs first on every execution so that any dependency updates committed to pyproject.toml are picked up automatically — no manual intervention needed after a dependency bump.
Choose a Schedule Frequency
The right cadence depends on how frequently your production dbt™ models run and how quickly costs accumulate in your warehouse. The table below covers the most common options:
Every day at 8 AM
0 8 * * *
Monday–Sunday, 8:00 AM
Weekdays at 8 AM
0 8 * * 1-5
Monday–Friday, 8:00 AM
Once a week (Monday 8 AM)
0 8 * * 1
Every Monday, 8:00 AM
Twice a week (Mon & Thu)
0 8 * * 1,4
Monday and Thursday, 8:00 AM
Every 12 hours
0 */12 * * *
Midnight and noon daily
For most teams, weekdays at 8 AM (0 8 * * 1-5) is a sensible default — it runs after overnight production dbt™ models have completed, so the latest run_results.json artifacts are always available. The script exits cleanly with code 0 when no dbt™ models exceed the bytes threshold, so there is no cost or noise on quiet days.
File Structure
Your repository should look like this after completing the setup:
pyproject.toml must sit at the same directory level as dbt_project.yml. The agent uses run_terminal_command, read_file, and write_file to navigate and rewrite your dbt™ project files, so the working directory at session start must be the repo root.
Related Docs
Last updated
Was this helpful?