Know what a SQL change costs before you merge it.

DiffCost dry-runs each changed query or dbt model in your own BigQuery project, once on the base branch and once on the pull request, and posts the price change on the pull request. Dry runs are free and read no rows.

Free for every GitHub organization. A GitHub owner's first sign-in starts a 14-day Team trial, with no card.

Example: pull request #142 changes models/marts/fct_daily_revenue.sql
select order_date, sum(revenue) as revenue
from {{ ref('stg_orders') }}
Removed: where order_date >= '2024-01-01'
group by order_date

Partitions scanned per run

Base branch: 0.33 TiB Pull request: 1.55 TiB0.33 TiB

Cost per run

Before: $2.09 After: $9.66

+$7.57 per run, about +$227 a month at 30 runs. The query now reads every partition back to 2013.

Before: $2.09 After: $2.09

No change. The filter limits the scan to partitions from 2024 on.

diffcost/cost-check Worst query +$7.57/run exceeds $5.00/run limit Merge blocked
diffcost/cost-check No cost regression detected Ready to merge

One report on the pull request

Every changed query or model gets a row: what it costs to run today, what it will cost after the merge, and why it changed. Each new push updates the same comment.

See how to read the report.

github-actions commented on pull request #142

💰 DiffCost — BigQuery Cost Report (cost per run)

Query / Model Current This PR Scan Delta Cost Change
fct_daily_revenue $2.09 $9.66 +1.21 TiB +$7.57 (↑362%)
stg_orders $0.031 $0.031 0 B —
TOTAL $2.12 $9.69 +1.21 TiB +$7.57 (↑357%)

⚠️ This PR increases query costs by $7.57/run (+357%, ≈$227.10/mo at 30 runs/mo).

Largest single-query increase: fct_daily_revenue +$7.57/run (↑362%).

📊 Data Scanned per run: 0.34 TiB → 1.55 TiB (+1.21 TiB)

🔍 Root cause analysis

🔴 fct_daily_revenue: Partition filter removed: order_date >= '2024-01-01'

Removing a partition filter causes a full table scan.

What DiffCost checks

The cost of each change, per run

BigQuery's dry run returns the bytes a query would scan. DiffCost prices them at your rate per TiB. On Editions (slot) pricing it reports bytes and a scan multiplier instead, because slot cost depends on your reservation.

1.546 TiBscanned
$6.25per TiB, on-demand
$9.66per run

The usual causes

It compares the SQL before and after the change and names a dropped partition filter on a date column, a new SELECT * or a join with no condition as the likely cause.

  • Partition filter removed: order_date >= '2024-01-01'
  • SELECT * added
  • CROSS JOIN added: dim_calendar

A merge check you can require

On Starter and Team, the DiffCost GitHub App posts a diffcost/cost-check status. It fails when the largest single-query increase is over your limit in $/run or percent. Make it a required check to block the merge.

# .diffcost.yml
thresholds:
  absolute: 5.00  # USD per run
  relative: 200   # percent

Your SQL stays with you

Queries go only to BigQuery, in your project, and sign-in can be keyless with Workload Identity Federation. DiffCost's service receives the repository, the pull request and numbers: per-run costs and bytes. Never SQL, table or model names, file paths or rows.

See what leaves your runner.

{
  "repo": "acme/analytics",
  "pr_number": 142,
  "total_cost_before": 2.12,
  "total_cost_after": 9.69,
  "queries": [
    { "bytes_before": 367676688328, "bytes_after": 1699405171891,
      "cost_before": 2.09, "cost_after": 9.66 },
    { "bytes_before": 5453577674, "bytes_after": 5453577674,
      "cost_before": 0.031, "cost_after": 0.031 }
  ]
}
What DiffCost receives for pull request #142, shortened. The rest is the commit SHA, the PR author's login, and totals and counts.

Set up in three steps

The quickstart has the exact commands and the full workflow file to copy.

  1. Let a service account dry-run queries

    BigQuery Job User on the project, and Data Viewer on the datasets your SQL reads. See BigQuery access.

  2. Add the workflow

    One job on every pull request: check out with full history, sign in to Google Cloud, run diffcost/action@v1. dbt projects also pass their manifest; see the dbt guide.

  3. Open a pull request

    The report appears as a comment. To block merges and keep a history, add an API key and the DiffCost GitHub App; see Merge blocking.

PRs from forks get structural analysis in the job summary only: GitHub gives them no secrets, no OIDC token and no write access, so the workflow skips the Google Cloud sign-in for them.

.github/workflows/diffcost.yml
name: DiffCost
on: pull_request

permissions:
  contents: read
  pull-requests: write
  id-token: write

jobs:
  diffcost:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: google-github-actions/auth@v2
        if: github.event.pull_request.head.repo.full_name == github.repository
        with:
          workload_identity_provider: "projects/…/providers/github-provider"
          service_account: "diffcost-ci@my-project.iam.gserviceaccount.com"
      - uses: diffcost/action@v1
        with:
          sql_paths: "queries/**/*.sql"
          gcp_project: "my-project"

Free to start

The report is free for every GitHub organization. Starter ($49 a month) adds merge blocking, Slack alerts and cost trends. Team ($149 a month) adds per-repository limits and cost by author.