feat: migrate Global Async Queries onto the Global Task Framework - #43407
Draft
villebro wants to merge 18 commits into
Draft
feat: migrate Global Async Queries onto the Global Task Framework#43407villebro wants to merge 18 commits into
villebro wants to merge 18 commits into
Conversation
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #43407 +/- ##
==========================================
+ Coverage 78.82% 78.88% +0.05%
==========================================
Files 2876 2884 +8
Lines 164459 165220 +761
Branches 37956 38152 +196
==========================================
+ Hits 129634 130331 +697
- Misses 32378 32426 +48
- Partials 2447 2463 +16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
6 tasks
Merged
6 tasks
…at-most-once pub/sub) (#43409)
5 tasks
9 tasks
6 tasks
6 tasks
Merged
9 tasks
…ta + guest face pile (#43434)
9 tasks
This was referenced Aug 23, 2026
This was referenced Aug 24, 2026
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.
SUMMARY
Before this epic, Superset had three async execution paths:
AsyncQueryManager,qc-<hash>descriptors, andresult_urlresult reassembly.@task/.schedule()abstraction with ataskstable, deduplication, DAG dependencies, progress, timeouts, cancellation, a Task List UI, and a REST API.This epic migrates GAQ chart-data execution onto GTF and removes GAQ's dedicated plumbing.
GLOBAL_ASYNC_QUERIESremains the operator switch for whether chart-data requests may run asynchronously, but the execution, visibility, deduplication, and completion tracking now use GTF task records and APIs.The core architectural change is the async task boundary. GAQ treated the query context as the async task, even though the query context is mostly a container for one or more executable queries. This branch changes the task unit to the
QueryObjectitself. That is a major simplification because Superset already computes cache keys, cache identity, datasource/RLS/impersonation scope, and result caching at theQueryObjectlevel. EachQueryObjectnow becomes one SHARED GTF task keyed byquery_cache_key; when the tasks complete, the client re-issues the original chart-data request and reads from the warmed per-query cache.Polling is the primary readiness and correctness path in this PR: chart completion is driven by
GET /api/v1/task/status_changesand works without a websocket server. The websocket work is included because it unlocks realtime Task List/list-view updates and faster chart wakeups, but that push layer is still WIP and should be treated as secondary to polling while it stabilizes.Along the way, the branch consolidates distributed coordination behind one service/config and turns
superset-websocketinto a generic server-to-browser push transport. It also changes the websocket server build so it can run as a compact standalone bundle: the generated server artifact is about 1.2 MB, instead of requiring a runtime dependency tree to be shipped with it. That makes it practical to include in the official Superset image instead of requiring operators to build and ship a separate websocket image. Task List is the first realtime list-view surface; the shared list hook can support other entity lists once those entities publish change nudges.This is a long-lived feature branch. Each step was reviewed and merged into
gaq-to-gtf; this umbrella PR merges the completed epic back tomaster.Key Features
QueryObject, keyed byquery_cache_key, deduplicated across compatible users. Contribution queries use a real GTF DAG edge to wait for the totals query.qc-<hash>query-context descriptor is gone. GTF tasks warm the existing per-query cache, and the client re-issues the same POST once all task IDs settle.async_modecontrols whether a chart-data request may run async. Absentasync_modekeeps the API synchronous and preserves programmatic client behavior.CoordinationServiceand the existingDISTRIBUTED_COORDINATION_CONFIG.superset-websocketroutes by channel name and forwards{channel, payload}envelopes. It no longer knows about GAQ-specific event shapes.useListViewResourcecan subscribe to entity-change nudges and patch visible rows in place. Tasks publish nudges on status/progress changes, so Task List updates without manual refresh.GLOBAL_ASYNC_QUERIES_MIN_CACHE_TTLfloors result-cache TTL only on the async chart-data write path so the follow-up read does not miss an evicted result.node_modulestree.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
With GAQ enabled, opening the deck.gl demo dashboard spawns five separate GTF tasks, one of which is deduplicated because some charts share identical queries. These are visible during and after execution on the Tasks list view:
Additional screens and videos to be added.
TECHNICAL DETAILS
Architecture Overview
The key design decision is that the atomic async unit is the
QueryObject, not the query context.QueryContextProcessor.get_df_payload_result(query_obj)already executes and caches exactly oneQueryObject. Itsquery_cache_keyincludes the pieces that matter for safe reuse: datasource identity,extra_cache_keys, RLS, impersonation, result type/format, and query payload. The oldqc-<hash>cache entry did not hold results; it described a query context so the client could later reconstruct the response. That made GAQ coordinate at one level while caching and execution already happened at another.This branch aligns those layers:
This removes the GAQ-specific
AsyncQueryManager,/api/v1/async_event/,load_chart_data_into_cache, GAQ JWT/transport configuration,GLOBAL_ASYNC_QUERIES_CACHE_BACKEND, and theqc-<hash>//api/v1/chart/data/<cache_key>replay path.Coordination Model
The branch introduces
superset/coordination/as the common abstraction for distributed coordination:CoordinationServicelock/KV helpersLock release is ownership checked and atomic on Redis-backed coordination, so an expired holder cannot delete a lock acquired by a later holder.
Delivery Guarantees: Redis Streams vs Pub/Sub
This branch deliberately uses two Redis delivery patterns because the signals have different correctness requirements.
This split is important for the GAQ-to-GTF cutover. A missed task-completion or lock-release signal can hang a chart request, cause a duplicate submit race, or leave a dependent task waiting unnecessarily, so those signals use Streams. A missed realtime nudge only means a visible row stays stale until the next fetch/poll, so Pub/Sub is the right lower-overhead fit. These Pub/Sub nudges are intentionally free of query context, result payloads, or authorization-bearing state; they wake the browser so it can fetch the authoritative state through the normal protected API surface.
The same rule should apply to unrelated future features that use
CoordinationService. Use Streams when a consumer is blocked waiting for a specific event and correctness depends on observing it. Use Pub/Sub when the event is only an optimization for UI freshness or latency and the feature has an authoritative fallback path.Realtime Transport
superset-websocketis now feature-agnostic. It subscribes to named backend channels and forwards a generic envelope to connected browsers.The server build also changes from a package that required its dependency tree at runtime into a single esbuild-generated bundle. The resulting
dist/index.cjsartifact is about 1.2 MB locally, which keeps the runtime artifact compact, lets it run standalone, and makes official-image inclusion feasible without asking operators to maintain a custom websocket image.There are two realtime channel tiers:
entity-changes:task. Payloads carry opaque entity identifiers only. The browser fetches any sensitive data through the normal authorized REST API.realtime:<channel_id>. These are still wakeups, not an authoritative state channel; the authenticated user or embedded guest token holder receives the nudge, then reconciles through polling/REST.The websocket path accelerates the UI, but chart completion correctness still comes from the
status_changespoll and GTF task state.Operator-Facing Changes
GLOBAL_ASYNC_QUERIESremains the chart-data async switch and auto-enablesGLOBAL_TASK_FRAMEWORKDISTRIBUTED_COORDINATION_CONFIGalready existed; this branch makes it the single coordination backend for GAQ-on-GTF, GTF await/notify, locks, and realtime Pub/Sub.GLOBAL_ASYNC_QUERIES_POLLING_DELAYalready existed and remains the base chart-data poll cadence.DISTRIBUTED_COORDINATION_SIGNAL_TTL,GLOBAL_ASYNC_QUERIES_DEFAULT,GLOBAL_ASYNC_QUERIES_MIN_CACHE_TTL,GLOBAL_ASYNC_QUERIES_POLLING_MAX_DELAY,GLOBAL_ASYNC_QUERIES_POLLING_STALE_TIMEOUT, and feature-agnosticWEBSOCKET_*settingsGLOBAL_ASYNC_QUERIES_CACHE_BACKEND, GAQ JWT/transport settings (GLOBAL_ASYNC_QUERIES_JWT_*,GLOBAL_ASYNC_QUERIES_TRANSPORT,GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL),AsyncQueryManager,/api/v1/async_event/,qc-<hash>replayGET /api/v1/task/status_changes, taskdepends_on, chart-dataasync_moderequest flagUPDATING.mdcontains the operator migration notes.Progress Tracker
Every step was merged into
gaq-to-gtf; the branch is complete.task_dependenciesand Task List dependency displayQueryObjectserializationQueryObjecttasks,status_changes, cache re-request, GAQ plumbing removal, embedded guest visibilityasync_modeopt-in, GTF auto-enable,GLOBAL_ASYNC_QUERIES_DEFAULT, per-dashboard overrideuseListViewResource; task nudges on transitions/progressguest_keywideningGLOBAL_ASYNC_QUERIES_MIN_CACHE_TTLasync result-cache floorasync_eventsretirementdedupe_countWhat each step shipped
superset/coordination/to consolidate distributed locks, Pub/Sub, Redis Streams, key/value operations, and await/notify helpers overDISTRIBUTED_COORDINATION_CONFIG.task_dependencies,Task.dependencies,TaskOptions.depends_on, block-and-wait scheduling withall_successsemantics, REST API exposure, and Task List dependency indicators.DISTRIBUTED_COORDINATION_SIGNAL_TTL, falling back to metastore polling when no backend is configured.serialize_query/load_serialized_queryso a raw query dict can round-trip throughQueryContextFactoryand preserve the samequery_cache_key./chart/datainto one SHARED GTF task perQueryObject; returns{task_ids}; pollsstatus_changes; re-issues from warm cache; removes GAQ manager/event/cache replay plumbing and supports embedded guest task visibility.async_modefor async chart data, keeps absentasync_modesynchronous, auto-enables GTF when GAQ is enabled, and adds dashboard/deployment policy controls.superset-websocketto generic Pub/Sub routing, adds the frontend realtime client, and anonymizes embedded guests in the Task List face pile.dist/index.cjsfile that can run standalone without a shippednode_modulestree, making it compact enough to include in the official image behind a websocket entrypoint/profile.useListViewResource; visible rows are debounce-batched, fetched through authorized REST endpoints, and merged in place. Task transitions/progress emit entity-change nudges.guest_keywiden (fix(gtf): close async-chart-data waiter race + widen guest_key column #43461). Returns a server-issued poll cursor captured before scheduling so fast terminal tasks cannot be missed, and widenstask_subscribers.guest_key.NullCacheDATA backends, makes the post-completion re-request a single synchronous request, atomically releases Redis locks, fixes dependency-cancel publishing, and retiresasync_events.query_cache_key; increments and displaysdedupe_countwhen work is reused.Security and backward compatibility
Security. The role/capability matrix is unchanged. Query reuse is bounded by
query_cache_key, which includes the cache-affecting datasource, RLS, impersonation, and query inputs. Async execution usesoverride_user; embedded guest subscribers use token-derived guest keys. Realtime public entity nudges carry opaque IDs only, and sensitive fields are fetched through authorized REST APIs and existing task filters.Backward compatibility.
GLOBAL_ASYNC_QUERIESremains the operator-facing switch. API clients that do not sendasync_modecontinue to receive synchronous200responses. The breaking removals are limited to GAQ internals and GAQ-specific configuration/API paths and are documented inUPDATING.md.Remaining follow-ups
superset_core.coordinationabstraction for extensions, similar tosuperset_core.tasks.TESTING INSTRUCTIONS
Each step PR carries focused unit/integration coverage and was green when merged into
gaq-to-gtf.End-to-end manual validation for the full epic:
GLOBAL_ASYNC_QUERIESand configureDISTRIBUTED_COORDINATION_CONFIG;GLOBAL_TASK_FRAMEWORKis auto-enabled.superset-websocketentrypoint withWEBSOCKET_ENABLEDand the corresponding websocket URL/JWT config.202with GTF task IDs on cache miss, then resolve after the client pollsGET /api/v1/task/status_changesand re-issues the chart-data POST.superset.query_object_v1tasks and updates live without manual refresh.200responses.Useful targeted commands:
pytest tests/unit_tests/common/test_query_serialization.py -v pytest tests/unit_tests/common/test_async_min_cache_ttl.py -v pytest tests/unit_tests/tasks/ -v pytest tests/integration_tests/tasks/ -v npm run test -- src/middleware/realtime.test.ts src/hooks/apiResources/useListViewResource.test.tsxADDITIONAL INFORMATION
GLOBAL_ASYNC_QUERIES(auto-enablesGLOBAL_TASK_FRAMEWORK); optionalWEBSOCKET_ENABLEDfor realtime transport