perf: prune partitions in get_models_runs - #2352
Draft
tlangton3 wants to merge 4 commits into
Draft
Conversation
`current_tests_run_results_query` filters both of its large CTEs with
`edr_datediff(edr_cast_as_timestamp(col), now, 'day') < days_back`. On BigQuery a
function applied to the column defeats partition pruning, so both scan all
history however small `days_back` is.
Adds a dispatched `days_back_filter` macro: the default implementation renders
exactly the predicate above, so nothing changes off BigQuery, while `bigquery__`
compares the column directly and adds a bound on the partition column.
Measured with a 7-day lookback, selecting a single column, on tables as they
already are — no repartitioning needed to get this:
elementary_test_results (12M rows, 13GB) 1,368 MB -> 13.68 MB
dbt_run_results (17M rows, 41GB) 1,627 MB -> 63.56 MB
For reference, an unfiltered scan of elementary_test_results reads 1,279 MB: the
current predicate reads *more* than no filter at all, since it also pays to read
the column it filters on.
The bound on `created_at` looks redundant — it is implied by the first predicate —
but BigQuery only prunes on the raw partition column, and `execute_completed_at`
is stored as a string, so a cast is unavoidable there and no predicate on it can
prune. dbt_run_results is not even partitioned on the warehouse measured above;
the 26x comes from block-level pruning on `created_at` alone, and partitioning it
would improve that further.
The bound is also safe: `created_at` is written at or after both columns, so any
row satisfying the first predicate satisfies the bound. Verified over 17.2M rows —
`created_at - generated_at` >= 0s, `created_at - execute_completed_at` >= 5s, and
zero rows violating either.
The guard is given a day of slack rather than the same bound: `created_at` is
stamped by the warehouse at insert while the columns it guards come from the dbt
client, so the two are not the same clock. Pruning is at day granularity, so the
slack costs at most one extra partition — measured as no difference at all on the
figures above, since the real predicate is the binding constraint.
…ilter elementary-data#1940 gave this macro a `bigquery__` override that duplicated the whole body just to change its `where` clause. Now that `days_back_filter` dispatches on the predicate itself, the two copies collapse back into one and the dispatch disappears. Behaviour is unchanged on every adapter: `default__days_back_filter` renders the datediff form the original had, and `bigquery__days_back_filter` renders the direct comparison the override had. It also gains the `created_at` bound. test_result_rows declares no partitioning today, so the direct comparison on `detected_at` was relying on block-level pruning alone; the bound takes a 7-day lookback from 1,060 MB to 101 MB on a 26M row / 41GB copy of that table, and improves further once it is partitioned.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueComment |
tlangton3
requested a deployment
to
elementary_test_env
September 11, 2026 09:47 — with
GitHub Actions
Waiting
Contributor
|
👋 @tlangton3 |
Only 4 of 48 macros here carry a doc block and none exceeded 12 lines; this had
24. Keeps the two things a reader cannot infer and would otherwise 'tidy' away —
that the second bound is what makes BigQuery prune, and that its day of slack is
deliberate — and drops the rest, which the PR description already covers.
Also builds the predicate by joining a list rather than capturing a {% set %}
block, so the compiled SQL is a single line instead of carrying the macro's
indentation and blank lines into every query.
`get_models_runs` computes `row_number()` over the whole of model_run_results and only then filters to `days_back`, so the report reads all history on every run. Moves the filter into the CTE and gives it a `created_at` bound to prune on. The move is safe: for any row inside the window, every row ranked above it for the same unique_id is newer, and therefore also inside the window — so ranks are unchanged for every row the outer query returns, and the `rank = 1 -> compiled_code` selection is preserved exactly. Confirmed against 39,185 rows in a 7-day window: zero rank differences, and compiled_code selected for the same 1,678 rows either way. Requires elementary-data/dbt-data-reliability#1057, which exposes `created_at` on the model_run_results view. Without it this cannot prune, since the view's other timestamps are strings and a cast defeats pruning.
tlangton3
force-pushed
the
perf/prune-get-models-runs
branch
from
September 11, 2026 12:51
d9c1653 to
74d795b
Compare
tlangton3
requested a deployment
to
elementary_test_env
September 11, 2026 12:51 — with
GitHub Actions
Waiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
Draft — depends on elementary-data/dbt-data-reliability#1057, which exposes
created_aton themodel_run_resultsview. Until that is merged and this project's package dependency points at it, the guard below has no column to prune on and CI here will fail. Opening now so the change is ready; happy to rebase or close and re-open once the ordering suits you.It also stacks on #2351, which adds the
days_back_filtermacro this uses. The diff shown will shrink to a single file once that merges.What
get_models_runscomputesrow_number()over the entiremodel_run_resultsview and only then filters todays_back:So every report run reads all history. On one warehouse this is the single most expensive query the CLI issues: ~12.4 GB per invocation against a 17M row / 41GB table, and ~1,115 GB over a week.
The fix
Moving the filter into the CTE and bounding
created_at, which is the column BigQuery can prune on.Worth being precise that the filter move earns nothing by itself — it is the
created_atbound that prunes. I had also replacedselect *in the CTE with an explicit column list, and dropped it again after measuring: BigQuery already prunes to the columns a query references, so both shapes read 31,868 MB, identical. It was ten lines and a second place to maintain the column set for no gain.On the warehouse above, the 8-day window is 0.85% of the table (146,402 of 17,207,248 rows across 779 partition-days), so this should fall to a fraction of a GB per invocation. I cannot post a measured after-figure until #1057 is in, and would rather leave it blank than estimate.
Why the move is safe
The filter can move above the window function without changing any returned row's rank. For any row R inside the window, every row ranked above R for the same
unique_idis newer than R, and therefore also inside the window. So no row that contributed to R's rank is removed, and R's rank is identical either way — which also preserves theinvocations_rank_index = 1 → compiled_codeselection exactly.Confirmed rather than argued, over a 7-day window on real data:
Non-BigQuery adapters
Unchanged.
default__days_back_filterrenders the sameedr_datediff(edr_cast_as_timestamp(...)) < days_backpredicate this macro already used; only its position in the query moves, and that is rank-preserving as above.exclude_elementarydeliberately stays in the outer query. It could move into the CTE too — filtering wholeunique_idgroups cannot affect ranks within other groups — but it prunes nothing, so I left the change surface as small as possible.