Skip to content

fix(migrations): normalize legacy metadata cache timeouts - #45790

Open
sushu0 wants to merge 7 commits into
apache:masterfrom
sushu0:codex/migrate-legacy-metadata-cache-timeouts
Open

sushu0 wants to merge 7 commits into
apache:masterfrom
sushu0:codex/migrate-legacy-metadata-cache-timeouts

Conversation

@sushu0

@sushu0 sushu0 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

Fixes #45460, following the agreed migration approach in #45460 (comment).

Older database forms stored metadata cache timeouts as strings. With cachelib 0.17, a stored value such as "schema_cache_timeout": "3600" reaches the cache backend and raises a TypeError. Add a data-only migration that converts non-negative integer strings in catalog_cache_timeout, schema_cache_timeout and table_cache_timeout to integers, including "0" to 0.

Only rows with a conversion are updated. Existing integers, missing keys, negative/invalid values, malformed or unrepresentable JSON, non-object configurations and unknown settings are preserved. Invalid configurations are left for operator repair rather than assigned a guessed default. Decimal-aware JSON decoding preserves unrelated high-precision and very large numeric settings when an affected row is serialized again; the shared JSON wrapper does not expose this decoding option. A JSON number outside Decimal's representable range leaves the whole row unchanged rather than interrupting the migration.

The SELECT requests row locks on backends that support them. Each UPDATE also matches the original extra value; if it changed after the read, the migration raises so Alembic can roll back instead of overwriting an edit or silently finishing with an unconverted row.

Revision 9670ab7f6bc6 follows f72b6e9c104a, the migration head at merged master 1aceca35ed218288dcf2b20ed09dbc053c204a47. Its revision ID is unchanged. This PR does not choose a 7.0 RC cherry-pick order; release-branch migration ordering remains a release-manager decision.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Not applicable; data migration only. A legacy string timeout causes the actual catalog/schema/table cached model methods to fail before migration. Reading the migrated row through the same methods writes successfully to a real cachelib SimpleCache.

TESTING INSTRUCTIONS

  • Initial regressions before the normalization rewrite had 10 failures / 16 passes. The cache cases first assert the real cachelib TypeError on stored legacy rows, then verify successful writes after upgrade. Separate regressions caught lossy JSON-number serialization and an out-of-range JSON Decimal raising InvalidOperation before those preservation fixes.
  • python -m pytest tests/unit_tests/migrations/test_normalize_metadata_cache_timeouts.py tests/unit_tests/migrations/test_single_migration_head.py tests/unit_tests/utils/cache_test.py -q — 48 passed on 2cbe5915. Covers all three cache paths, zero, mixed connections, invalid/unrelated data, Decimal preservation and parse-range errors, idempotent no-write behavior, transaction rollback, rejection of a stale configuration snapshot and downgrade.
  • superset db heads reports exactly 9670ab7f6bc6 (head); the project's single-head test also passes.
  • Changed-file Ruff and python -m py_compile passed. Staged-file pre-commit passed all applicable checks except the three native Windows signal.SIGALRM/signal.alarm errors in untouched superset/utils/core.py. The same mypy hook environment with --platform linux --check-untyped-defs passed both new Python files. Pylint excludes migration and test files under the repository configuration.

For deployment-level verification, use a disposable metadata database with legacy string timeouts in dbs.extra, run superset db upgrade, inspect the integer values and load the catalog/schema/table lists with metadata caching configured. Local tests use actual SQLite rows, Alembic MigrationContext/Operations and project model/cache code. The stale-row test injects a real row update through an execution event on the same connection; it proves conditional-update behavior, not two-connection concurrency. The actual migration SELECT was also captured and compiled with the PostgreSQL/MySQL/SQLite dialects. A full production metadata upgrade, live warehouse, browser flow, two-connection test and MySQL/PostgreSQL migration/row-lock execution were not exercised locally.

Maintainer-requested rollback proof: the new two-row case observes a completed first UPDATE, changes the remaining row before the second UPDATE, lets the conflict exception propagate out of engine.begin(), and verifies both exact original texts through a new connection. It passes with the existing runtime logic. The rollback test was added in ebeb64e8. The latest follow-up ordinarily merges master 1aceca35 and changes only Revises/down_revision: the CLI first reported f72b6e9c104a and 9670ab7f6bc6 as two heads, then only 9670ab7f6bc6 after reparenting. The revision ID, upgrade/downgrade logic and existing tests are unchanged.

ADDITIONAL INFORMATION

Transaction and downgrade: updates use the Alembic connection without an independent commit; a real SQLite test verifies that the enclosing transaction can roll back the changes. Downgrade deliberately leaves integer values intact: older versions already accept integers, and the migration cannot distinguish an original integer from a converted value or a value saved after upgrade. The combined reversibility checkbox above is left unchecked because the original JSON types are not reconstructed.

Runtime/locks: one SELECT over dbs.id/dbs.extra, followed by one conditional UPDATE per affected database connection: O(number of configured connections), independent of warehouse row counts. A bounded local timing sample of the locking/CAS runtime body at b0c8ee0 (Python 3.12.14, SQLAlchemy 2.0.55, in-memory SQLite) took 0.148 seconds for the actual upgrade and commit of 1,000 synthetic connection rows with three string timeouts each, excluding app startup, seeding and verification. This is a local reference, not a production estimate or upper bound. The test/chain-only follow-up did not rerun this sample. There is no DDL or table rebuild. On PostgreSQL/MySQL the SELECT can lock all selected connection rows, including those not ultimately changed, until the Alembic transaction completes; pause configuration edits during the usual upgrade window. A conditional-update conflict aborts the migration and calls for retrying with edits paused. Production wall-clock time and downtime were not measured.

AI assistance disclosure: OpenAI Codex assisted with investigation, implementation, test preparation, automated review and local validation. This PR does not claim human-executed validation or prior human review of the new migration.

Copilot AI balanced review requested due to automatic review settings October 27, 2026 15:02

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Comment on lines +77 to +80
connection.execute(
_DATABASES.update()
.where(_DATABASES.c.id == database_id)
.values(extra=json.dumps(extra))

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.

Suggestion: A concurrent edit to dbs.extra after the SELECT can be overwritten by this stale snapshot, losing the newer configuration.

Assessment: 🟠 Major · 🔁 Occurrence: Rarely · 🏷️ Race condition

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/migrations/versions/2026-10-27_00-00_9670ab7f6bc6_normalize_metadata_cache_timeouts.py
**Line:** 77:80
**Comment:**
	*Race Condition: A concurrent edit to `dbs.extra` after the SELECT can be overwritten by this stale snapshot, losing the newer configuration.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

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.

✅ CodeAnt verified this suggestion was addressed in subsequent commits and marked this thread resolved as of b0c8ee0.

The update now matches both the database ID and the originally selected extra value. If a concurrent edit changed extra, the update affects no row and the migration raises an error instead of overwriting the newer configuration.

If that's not right, unresolve this thread and CodeAnt will leave it open.

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.

✅ CodeAnt verified this suggestion was addressed in subsequent commits and marked this thread resolved as of ebeb64e.

The update now matches both the database ID and the originally selected extra value. If a concurrent edit changed extra, the update affects no row and the migration raises an error instead of overwriting the newer configuration.

If that's not right, unresolve this thread and CodeAnt will leave it open.

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.

✅ CodeAnt verified this suggestion was addressed in subsequent commits and marked this thread resolved as of 2cbe591.

The update now matches both the database ID and the originally selected extra value. If a concurrent edit changed extra, the update affects no row and the migration raises an error instead of overwriting the newer configuration.

If that's not right, unresolve this thread and CodeAnt will leave it open.

@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt  CodeAnt PR Risk

🟢  Low Risk

  • The PR appears safe to merge; updates match the originally read configuration and abort on conflict instead of overwriting concurrent edits.
  • Migration tests cover stale edits, rollback, and timeout conversion.

Assessed commit: 2cbe5915d405

@netlify

netlify Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 2cbe591
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ae18aac57288048fe88383d
😎 Deploy Preview https://deploy-preview-45790--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.56%. Comparing base (1aceca3) to head (2cbe591).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #45790      +/-   ##
==========================================
- Coverage   84.57%   84.56%   -0.01%     
==========================================
  Files        3069     3068       -1     
  Lines      205227   205106     -121     
  Branches    48222    48180      -42     
==========================================
- Hits       173561   173455     -106     
+ Misses      28362    28347      -15     
  Partials     3304     3304              
Flag Coverage Δ
hive 34.56% <ø> (ø)
mysql 52.51% <ø> (ø)
postgres 52.51% <ø> (-0.01%) ⬇️
presto 36.28% <ø> (ø)
python 87.65% <ø> (ø)
sqlite 52.28% <ø> (ø)
unit 82.32% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Possible DB migration conflict

@sushu0 the base branch master picked up a new database migration under superset/migrations/ after this PR was opened, and this PR adds or modifies migration files too. Alembic migrations form a single linear chain, so if both branch off the same parent revision, merging this PR as-is could leave the migration history with two heads instead of one, which makes superset db upgrade fail with Multiple head revisions are present.

To check and, if needed, fix this:

  1. Rebase this branch onto the latest master.
  2. Run superset db heads. If it lists more than one revision, you have a conflict.
  3. In your new migration's script, update down_revision (and the Revises comment) to point at the other head instead, so the chain has a single head again.

See Merging DB Migrations in the contributing guide for a worked example.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Possible DB migration conflict

@sushu0 the base branch master picked up a new database migration under superset/migrations/ after this PR was opened, and this PR adds or modifies migration files too. Alembic migrations form a single linear chain, so if both branch off the same parent revision, merging this PR as-is could leave the migration history with two heads instead of one, which makes superset db upgrade fail with Multiple head revisions are present.

To check and, if needed, fix this:

  1. Rebase this branch onto the latest master.
  2. Run superset db heads. If it lists more than one revision, you have a conflict.
  3. In your new migration's script, update down_revision (and the Revises comment) to point at the other head instead, so the chain has a single head again.

See Merging DB Migrations in the contributing guide for a worked example.

This branch has not been deployed

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

Labels

risk:db-migration PRs that require a DB migration size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL Lab schema/table lists fail with "timeout must be an int or datetime.timedelta, got 'str'" on databases with legacy string cache timeouts (7.0.0rc2)

3 participants