Skip to content

fix(ol-dbt-cli): stop registering dbt's __dbt_tmp/__dbt_backup shadow tables - #2660

Merged
quazi-h merged 1 commit into
mainfrom
fix/ol-dbt-register-skip-dbt-shadow-tables
Sep 11, 2026
Merged

quazi-h merged 1 commit into
mainfrom
fix/ol-dbt-register-skip-dbt-shadow-tables

Conversation

@quazi-h

@quazi-h quazi-h commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

N/A — found while validating #2403. Partial mitigation of a larger ol-dbt local register issue tracked internally; see Scope below for what this deliberately does not fix.

Description (What does it do?)

ol-dbt local register enumerated Glue tables and filtered on two conditions only:

if table_type.upper() == "ICEBERG" and metadata_location:

dbt's create-temp-then-swap build artifacts satisfy both. <name>__dbt_tmp and <name>__dbt_backup are genuine Iceberg tables and Glue reports them with a real metadata_location, so they were registered as first-class sources alongside real ones.

They are not sources, and they do not survive. dbt deletes them on its next run, so a DuckDB view over one begins failing with HTTP 404 reading its metadata JSON — and the table can disappear between get_tables and CREATE VIEW within a single register run, which is exactly what an error during registration looks like.

Measured against ol_warehouse_production_staging on 2026-09-09, diffing Glue's live table list against _glue_source_registry:

Glue staging tables: 249    registered: 256
in Glue but NOT registered: 0
registered but NO LONGER in Glue: 7
    stg__edxorg__bigquery__mitx_user_email_opt_in__dbt_backup
    stg__edxorg__bigquery__mitx_user_email_opt_in__dbt_tmp
    stg__edxorg__bigquery__mitx_user_info_combo__dbt_backup
    stg__edxorg__bigquery__mitx_user_info_combo__dbt_tmp
    stg__mitxpro__app__postgres__courses_coursetopic__dbt_tmp
    stg__mitxpro__app__postgres__courses_platform__dbt_tmp
    stg__mitxpro__app__postgres__courses_program__dbt_tmp

All seven are shadow tables. Nothing real was missing. That same register run reported ✗ Errors: 6.

One claim I want to label honestly: attributing those six errors specifically to these seven tables is inferred, not proven — the per-table lines were not captured, and a --dry-run re-check cannot reproduce them because it never issues the CREATE VIEW. The mechanism matches exactly and no non-shadow table was absent from the registry, so it is the strongly likely cause, but the 249/256/7 counts are the measured part.

Implementation notes

  • Filters on the table name, anchored to the end: __dbt_(?:tmp|backup)\d*$.
  • Checked before the ICEBERG test, so a shadow table lacking Iceberg parameters isn't reported twice (once as an artifact, once as "not Iceberg").
  • Numbered variants (__dbt_tmp1, produced when a build is interrupted and retried) are covered. Registered views with __dbt_tmp1 in their name have been observed in this catalog.
  • Anchoring matters: a real table whose name merely contains the token — int__dbt_tmp_usage_metrics — must still register, and there's a test for it.

Scope — what this does not fix

This is not the fix for the bigger problem: Glue points the canonical name of a dbt-built Iceberg table at a __dbt_tmp-<uuid> metadata location, because Iceberg RENAME does not move data. Re-measured immediately after a fresh register of three layers on 2026-09-09, 624 of 640 non-raw registered views (97.5%) sit on such a location, and they either 404 or silently return the temp table's accumulated snapshots — duplicated and partially missing rows, measured at 31x duplication on int__mitxonline__proctored_exam_grades.

Fixing that means resolving the table's current snapshot from the Iceberg catalog rather than trusting Glue's metadata_location, or refusing to register such a view at all — a design decision with its own tradeoffs, tracked separately. This PR removes only the junk views and the registration errors they cause. It does not make local row counts or fill rates trustworthy.

How can this be tested?

cd src/ol_dbt_cli
uv run pytest tests/test_local_dev.py -k 'ShadowTable'   # 20 new tests
uv run pytest                                            # full suite: 639 passed
uv run ruff check . && uv run ruff format --check .

To confirm the behaviour end to end against real Glue (needs AWS creds):

uv run ol-dbt local register --database ol_warehouse_production_staging

Shadow tables now print as ⊘ <name> (dbt build artifact, not a real source) and are excluded from the registry. Re-run the count that surfaced the problem and expect no orphans:

-- against ~/.ol-dbt/local.duckdb, after registering
select glue_table from _glue_source_registry
where glue_table like '%__dbt_tmp%' or glue_table like '%__dbt_backup%';

Existing views registered before this change are not retro-actively removed. ol-dbt local cleanup-local drops locally-registered views if you want a clean slate; otherwise the next --force register leaves the seven stale ones in place, since the code no longer enumerates them to update.

Additional Context

Verified by negative control, not just by the tests passing: stubbing _is_dbt_shadow_table to return False fails 10 of the 20 new tests. The parametrised cases use the seven real table names measured above rather than invented ones, so the test suite documents the actual catalog state that motivated the change.

Pre-existing mypy errors. mypy surfaces 6 errors in ol_dbt_cli/lib/sql_parser.py from this package. They are untouched here — the file is byte-identical to main, and mypy --no-incremental reproduces them on main. They're invisible in a warm-cache run, which is why the pre-commit hook passes.

Behaviour change worth a reviewer's eye: anyone who has come to rely on a __dbt_tmp view being present locally will find it gone after the next register. That should be nobody — they 404 unpredictably — but it is a change in what register produces, not only in what it errors on.

🤖 Generated with Claude Code

… tables

`ol-dbt local register` enumerated Glue tables and filtered on two conditions
only — `table_type == ICEBERG` and a present `metadata_location`. dbt's
create-temp-then-swap build artifacts satisfy both: `<name>__dbt_tmp` and
`<name>__dbt_backup` are genuine Iceberg tables that Glue reports like any other.
So they were registered as first-class sources.

They are not sources, and they do not survive. dbt deletes them on its next run,
so a DuckDB view over one starts failing with `HTTP 404` reading its metadata
JSON, and the table can vanish between `get_tables` and `CREATE VIEW` inside a
single register run — which is what an error during registration looks like.

Measured against ol_warehouse_production_staging on 2026-09-09, diffing Glue's
live table list against `_glue_source_registry`:

    Glue staging tables: 249    registered: 256
    in Glue but NOT registered: 0
    registered but NO LONGER in Glue: 7   <- all seven __dbt_tmp / __dbt_backup

That same run reported `✗ Errors: 6`. Attributing those six specifically to
these seven is inferred rather than proven — the per-table error lines were not
captured — but the mechanism matches exactly and no non-shadow table was missing
from the registry.

Filters on the table NAME, checked before the ICEBERG test so a shadow table
without Iceberg parameters is not reported twice. Numbered variants
(`__dbt_tmp1`, from an interrupted build) are covered, and the suffix is anchored
to the end of the name so a real table merely containing the token —
`int__dbt_tmp_usage_metrics` — is still registered.

SCOPE: this is deliberately NOT the fix for the larger problem that Glue points
the CANONICAL name of a dbt-built table at a `__dbt_tmp` metadata location, which
is why 624 of 640 non-raw registered views read duplicated or partial data. That
needs resolving the table's current snapshot rather than trusting Glue's
`metadata_location`, and it is a separate design decision. This change removes
only the junk views and the errors they cause.

20 new tests, including the seven real table names measured above. Verified by
negative control: stubbing the predicate to `return False` fails 10 of them.
Full ol_dbt_cli suite 639 passed; ruff clean. The 6 mypy errors visible from this
package are pre-existing in `lib/sql_parser.py` (identical to main, reproduced on
main with `--no-incremental`) and are untouched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 9, 2026 20:39
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

🔎 ol-dbt impact — column-level blast radius

✅ No column-level downstream impact detected for the changed models.

Posted by ol-dbt impact (annotate-only — does not block merge).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused filtering logic matches the documented scope and is adequately covered by tests.

Pull request overview

Filters transient dbt Iceberg shadow tables from local Glue registration.

Changes:

  • Detects __dbt_tmp and __dbt_backup suffixes, including numbered variants.
  • Adds focused positive, negative, and integration-style tests.
File summaries
File Description
src/ol_dbt_cli/ol_dbt_cli/commands/local_dev.py Excludes dbt shadow tables during Glue enumeration.
src/ol_dbt_cli/tests/test_local_dev.py Tests shadow-table detection and filtering.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@quazi-h
quazi-h merged commit 49decc2 into main Sep 11, 2026
21 checks passed
@quazi-h
quazi-h deleted the fix/ol-dbt-register-skip-dbt-shadow-tables branch September 11, 2026 13:06
quazi-h added a commit that referenced this pull request Sep 15, 2026
…is tuned an order of magnitude too loose

ol-dbt-local-dev told you to register, and `list-sources` warns when the registry
is ">1 day old". Both understate the problem. `register` stores the Iceberg
metadata_location Glue reports at that instant, and for a dbt-built table Glue
routinely points the canonical name at a __dbt_tmp directory that the next
production materialization swaps and deletes.

Measured while validating #2403: a registry refreshed 60 MINUTES earlier failed
with HTTP 404 reading the metadata JSON for int__micromasters__dedp_proctored_exam_grades,
killing two mart models mid-validation. Re-registering only the intermediate layer
(41 pointers moved, 118 unchanged) fixed it. Re-registering the same three layers
26 hours later moved another 336 pointers — 184 staging, 108 intermediate, 44
dimensional — which is the churn rate the one-day threshold is failing to track.

The 404 is the lucky shape. When the __dbt_tmp directory still exists but holds a
mid-build snapshot, the view returns duplicated or partial rows and nothing fails
at all: a clean run and wrong numbers, which is the dangerous case when those
numbers are about to be quoted in a PR. So the guidance is unconditional
re-registration immediately before a build, not a reaction to a visible error.

#2660 stopped __dbt_tmp tables being registered as sources in their own right but
deliberately scoped out canonical names pointing at __dbt_tmp locations (621/636
dbt-built tables at last count); that remains tracked separately. Until it lands,
freshness is the caller's responsibility, so the skill now says so.

Verified the three documented register commands run verbatim from a clean worktree
(0 errors each) and pre-commit passes on the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants