Conversation
When a chart has annotation layers, the DATA cache key bound the raw requesting user id, forcing one cache copy per user even when they would see identical annotation data. Bind the annotation *security scope* instead: NATIVE layers bind the `can_read` Annotation permission; chart-backed layers bind datasource access plus the annotation chart's own query cache key (which folds in datasource version, RLS clauses, and per-user Jinja/virtual-dataset RLS). Fall back closed on SupersetException. Also route three direct data_cache.set writers (SQL executor result cache, datasource column-values and metrics/dimensions endpoints) through a shared size-cap wrapper so oversized values can't bypass DATA_CACHE_MAX_VALUE_SIZE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #44402 +/- ##
===========================================
- Coverage 80.41% 66.27% -14.15%
===========================================
Files 2932 2932
Lines 174621 174814 +193
Branches 40559 40600 +41
===========================================
- Hits 140422 115854 -24568
- Misses 31532 56517 +24985
+ Partials 2667 2443 -224
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:
|
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chart-backed annotation cache-key bound access via
can_access_datasource (form_data=None), which under-approximates the
actual fetch gate. get_viz_annotation_data validates the annotation
chart's query context, whose raise_for_access also honors
has_promiscuous_chart_access (VIEWER_PROMISCUOUS_MODE) and guest-token
scopes. Under those configs a promiscuous-granted viewer and a
truly-denied user both fail can_access_datasource and would collapse onto
one {access:False} entry, letting the denied user read the viewer's
cached annotation payload (the real gate only runs on a cache miss).
Bind access on the same raise_for_access(query_context=...) the fetch
performs, keeping the except SupersetException fail-closed fallback.
Also add focused tests for set_data_cache_if_within_size /
oversized_data_cache_value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Heya @eschutho, small world. I hit this same root cause independently and opened #44406 before seeing this one. Converged on your access-scope idea (can_read/Annotation, can_access_datasource, reusing the referenced chart's own cache key) over the plain user_id + RLS-clause key I started with, credited over there. Main difference: this keeps the dataframe and annotation payload on one combined entry re-scoped by access class, mine splits them so the dataframe stays one shared entry regardless of how many access classes view the chart. We're touching the same file and test suite too, so whichever lands second needs a rebase. Should we reconcile onto one of these before either merges? |
|
I'll close this one @rusackas |
SUMMARY
The problem. When a chart has annotation layers, Superset was saving a separate copy of that chart's cached data for every user who viewed it — even when all of them would see exactly the same result. A single chart could turn into hundreds of identical copies in the cache, and since each copy can be tens of megabytes, this wasted a large amount of cache memory for no benefit.
Why it happened. Annotations (the extra markers/notes drawn on a chart) are loaded using the viewer's own permissions, and they get stored in the same cache entry as the chart's data. To make sure a user could never be served annotation data they weren't allowed to see, the code mixed the viewer's user ID into the cache key. That was safe, but too blunt: because the user ID is part of the key, every user got their own copy — including users who would see identical data.
The fix. Instead of keying the cache on who the user is, we now key it on what the user is allowed to see (their access scope). Users with the same access share one cache entry; users with different access — or no access — never share one. This keeps the exact same safety guarantee (nobody is served annotation data they shouldn't see) while letting identical results be reused instead of duplicated. Concretely:
So users with genuinely different access still get their own cache entries; users with the same access now share one.
Second, related change: size cap. Superset can already skip caching values that are too large (
DATA_CACHE_MAX_VALUE_SIZE), but a few code paths wrote to the DATA cache directly and skipped that check: the SQL executor's result cache, and two datasource endpoints (filter-dropdown values, and metrics/dimensions). This PR sends those through the same size check so oversized values can't slip in there either. It does nothing when the cap is turned off (the default), so it adds no overhead by default.Expiry (TTL) note. Every cache write touched here already sets an expiry time. The special
timeout=0("never expire") value is an intentional, documented feature and is left unchanged.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — this is a caching / cache-key change with no UI surface.
TESTING INSTRUCTIONS
Unit tests (added/updated, verified to fail before the fix and pass after):
skip_cache_value_too_large), and does nothing when the cap is disabled.Manual check: open a chart with annotation layers as two users who share the same role and RLS, and confirm only one cached data entry is created (not one per user). Then view it as a user whose access or RLS differs, and confirm they get their own separate entry.
ADDITIONAL INFORMATION
🤖 Generated with Claude Code