Skip to content

[MAINTENANCE] Discover stale BigQuery test datasets via datasets.list instead of INFORMATION_SCHEMA - #12017

Closed
joshua-stauffer wants to merge 1 commit into
developfrom
m/bigquery-cleanup-datasets-list
Closed

[MAINTENANCE] Discover stale BigQuery test datasets via datasets.list instead of INFORMATION_SCHEMA#12017
joshua-stauffer wants to merge 1 commit into
developfrom
m/bigquery-cleanup-datasets-list

Conversation

@joshua-stauffer

Copy link
Copy Markdown
Collaborator

Summary

Rewrites the BigQuery test-dataset cleanup script to discover stale datasets via the datasets.list API (through google-cloud-bigquery's Client) instead of querying INFORMATION_SCHEMA.SCHEMATA over a SQLAlchemy connection.

Behavior is otherwise unchanged:

  • Same dataset name patterns (gx_ci_test_[a-f0-9]{10}, py3[0-9]{1,2}_i[a-f0-9]{32}).
  • Same 1-hour age threshold before a dataset is considered stale (now an injectable parameter with that default, rather than hardcoded in SQL).
  • Same cascade-delete semantics (delete_dataset(..., delete_contents=True) in place of DROP SCHEMA ... CASCADE).
  • Same summary logging (count cleaned up, or that there was nothing to do).

Also adds a per-race guard: if a dataset disappears between being listed and being deleted, the sweep logs and continues instead of aborting.

Why

Two problems with querying INFORMATION_SCHEMA.SCHEMATA directly:

  • Least privilege. INFORMATION_SCHEMA.SCHEMATA is a project-level view, so reading it requires permission to see dataset metadata across the entire project, even though this job only ever needs to see the datasets it itself creates. The datasets.list API only returns datasets the caller can already see, so a credential scoped to just a naming-prefix namespace works correctly with it, but gets 403 Access Denied against INFORMATION_SCHEMA.SCHEMATA.
  • Region scoping. INFORMATION_SCHEMA queries are scoped to the region they run in, so a dataset created in a different location is invisible to the query and never gets swept. datasets.list has no such scoping.

Since datasets.list results don't include a dataset's creation time, the sweep now does a get_dataset per name-matching candidate to read Dataset.created before applying the age filter.

User impact

None — this is a CI-only maintenance script with no effect on the great_expectations package or its public API.

How to review

  • The core logic worth reviewing is find_stale_dataset_ids: pattern match on dataset_id, then age-filter on Dataset.created, with NotFound tolerated at both the per-dataset lookup and the delete step.
  • tests/scripts/test_cleanup_big_query.py covers the selection logic (pattern matching, age boundaries including a zero-threshold edge case, and the two race windows) against a mocked client.
  • Verified live against a real project using a service account whose IAM permissions are conditioned on dataset name prefix: confirmed it can list_datasets/get_dataset/delete_dataset on its own namespace, confirmed a non-matching permanent dataset is never selected regardless of age, confirmed a freshly created dataset survives the default threshold and is picked up under a zero threshold, and confirmed cascade delete removes a dataset's contents.
  • GE_TEST_BIGQUERY_DATASET is no longer read by the script (there's no default dataset to connect through anymore); it's harmless for the workflow to keep setting it as an unused env var.

…MATION_SCHEMA

INFORMATION_SCHEMA.SCHEMATA is a project-level view, so querying it requires
metadata read access across the whole project even though the sweep only
needs to see datasets it created itself. It's also region-scoped, so a
dataset created in a different location is invisible to the query and never
gets cleaned up. The datasets.list API has neither limitation: it returns
exactly the datasets the caller can see, works with a credential scoped to
just the CI dataset namespace, and isn't tied to a single region.

Age filtering now happens client-side against Dataset.created (fetched via
get_dataset, since list_datasets doesn't include creation time), and the age
threshold is an injectable parameter so it can be exercised in tests without
waiting an hour. Deletion uses delete_dataset(..., delete_contents=True) as
the equivalent of the previous DROP SCHEMA ... CASCADE, and tolerates a
dataset disappearing between listing and deletion.
@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for niobium-lead-7998 canceled.

Name Link
🔨 Latest commit 1088f4e
🔍 Latest deploy log https://app.netlify.com/projects/niobium-lead-7998/deploys/6a71fd26e84a6e00096bddb8

@joshua-stauffer

Copy link
Copy Markdown
Collaborator Author

wrong approach - datasets.list is too slow for this purpose

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant