feat(analytics): internal refetch + uncached request mode (poll 1/4) - #584
atilafassina wants to merge 3 commits into
Conversation
Surface the request store's existing settled-key re-run as an internal
refetch(cacheKey) on the analytics request layer, and add a force-uncached
execution mode: a per-run skipCache signal on the query payload that the
analytics route honors to bypass its TTL cache. Lands dormant — no public
API, no scheduler; consumed only by store unit tests. Foundation for the
usePoll scheduler (phase 2) and the useAnalyticsQuery({ poll }) binding
(phase 3).
xavier loop: iteration 1 (phase 1/4)
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 1.2 MB (+20 KB) | 421 KB (+8.3 KB) |
| Type declarations | 434 KB (+7.1 KB) | 157 KB (+3.4 KB) |
| Source maps | 2.3 MB (+44 KB) | 790 KB (+17 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 3.9 MB (+71 KB) | 1.3 MB (+29 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
96 KB (+250 B) | 2.5 KB | 98 KB (+250 B) | external | 313 KB (+556 B) |
./beta |
93 KB (+21 B) | 457 B | 93 KB (+21 B) | external | 280 KB (+83 B) |
./testing |
38 KB (+21 KB) | 30 KB (+30 KB) | 68 KB (+51 KB) | external | 199 KB (+147 KB) |
./tsdown |
520 B | 0 B | 520 B | external | 813 B |
./type-generator |
23 KB | 0 B | 23 KB | external | 65 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 92 KB |
. |
utils.js |
initial | 4.0 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 76 KB |
./beta |
stream-manager.js |
initial | 5.8 KB |
./beta |
wide-event-emitter.js |
initial | 3.2 KB |
./beta |
databricks.js |
initial | 3.2 KB |
./beta |
configuration.js |
initial | 2.3 KB |
./beta |
service-context.js |
initial | 1.3 KB |
./beta |
client.js |
initial | 434 B |
./beta |
client-options.js |
initial | 220 B |
./beta |
supervisor-api.js |
lazy | 192 B |
./beta |
databricks.js |
lazy | 142 B |
./beta |
index.js |
lazy | 123 B |
./testing |
manifest.js |
initial | 26 KB |
./testing |
index.js |
initial | 9.5 KB |
./testing |
wide-event-emitter.js |
initial | 2.9 KB |
./testing |
index.js |
lazy | 26 KB |
./testing |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./testing |
utils.js |
lazy | 1.2 KB |
./tsdown |
index.js |
initial | 520 B |
./type-generator |
index.js |
initial | 23 KB |
@databricks/appkit-ui
npm tarball (packed): 351 KB (+927 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 395 KB (+575 B) | 133 KB (+205 B) |
| Type declarations | 229 KB | 84 KB |
| Source maps | 769 KB (+2.3 KB) | 254 KB (+803 B) |
| CSS | 16 KB | 3.2 KB |
| Total | 1.4 MB (+2.8 KB) | 474 KB (+1008 B) |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
5.3 KB | 49 KB | 55 KB | 208 KB | 14 KB |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
433 KB (+55 B) | 49 KB | 481 KB (+55 B) | 1.3 MB (+3 B) | 177 KB (+171 B) |
./react/beta |
1.0 KB | 0 B | 1.0 KB | 0 B | 1.9 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 5.2 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 20 B |
./react |
index.js |
initial | 430 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 1.0 KB |
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 34876794023 -R databricks/appkit -n appkit-template-0.75.1-pr.84ca15e-poll-1-refetch-584 -D appkit-pr-584 \
&& unzip -o "appkit-pr-584/appkit-template-0.75.1-pr.84ca15e-poll-1-refetch-584.zip" -d "appkit-pr-584" \
&& databricks apps init --template "appkit-pr-584"The template pins |
Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Atila Fassina <atila@fassina.eu>
Rework the poll-1 skipCache mode from a cache *bypass* into a *refresh*. Previously it disabled the cache (JSON) / skipped getOrExecute (Arrow), which left the shared entry stale to TTL for other readers and dropped in-flight single-flight coalescing. Now skipCache invalidates the entry and runs the normal cached path, so it re-hits the warehouse, writes the fresh result through to the shared entry, and keeps concurrent-request coalescing. Also: - coerce skipCache to a strict boolean so an untrusted body can't enable it with a truthy non-boolean (consistent with the format check on the route). - fix refetch() leaking its one-shot uncached mark when store.start() no-ops on an absent entry, and clear the mark set on resetAnalyticsRequestStore. - add server refresh/write-through + boolean-coercion tests and client mark-leak/reset tests. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Atila Fassina <atila@fassina.eu>
| const ttl = queryDefaults.cache?.ttl; | ||
| return { | ||
| query: (q, params, formatParameters, signal) => { | ||
| query: async (q, params, formatParameters, signal) => { |
There was a problem hiding this comment.
why is this async now? we are not doing any async operation inside no?
There was a problem hiding this comment.
🟡 Changes recommended
Cache refresh has unresolved one-shot state and in-flight invalidation issues, with missing Arrow route coverage.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds internal analytics refetch support and uncached request propagation for future polling.
Changes:
- Adds
refetch(cacheKey)andskipCacherequest handling. - Supports cache bypassing for JSON and Arrow requests.
- Expands store and route tests.
Review findings:
- Moderate (3 votes):
skipCachepersists across runs instead of being consumed once. - Moderate (3 votes): JSON refetch does not invalidate in-flight cache requests.
- Moderate (3 votes): Arrow refetch has the same in-flight race.
- Nit (1 vote): Add route-level Arrow cache-refresh coverage.
File summaries
| File | Summary |
|---|---|
packages/appkit/src/plugins/analytics/types.ts |
Adds the internal skipCache request flag. |
packages/appkit/src/plugins/analytics/tests/analytics.test.ts |
Tests JSON cache refresh and request-flag behavior. |
packages/appkit/src/plugins/analytics/analytics.ts |
Handles cache-bypassed JSON and Arrow requests. |
packages/appkit-ui/src/react/hooks/analytics-request-store.ts |
Implements internal refetch and payload injection. |
packages/appkit-ui/src/react/hooks/__tests__/analytics-request-store.test.ts |
Tests refetch lifecycle and transport behavior. |
Review details
Suppressed comments (1)
packages/appkit/src/plugins/analytics/analytics.ts:315
- The new server tests verify
skipCachefor the JSON_ARRAY/SSE path, but this forwarding into the separate ARROW_STREAM path is only tested on the client payload. Add a route-level ARROW_STREAM test that primes an INLINE result, sendsskipCache: true, and verifies a second warehouse execution plus a subsequent cached fresh result; otherwise the Arrow-specific eviction/repopulation behavior can regress undetected.
skipCache,
- Files reviewed: 5/5 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const shouldSkipCache = options.skipCache || uncachedKeys.has(cacheKey); | ||
| if (uncachedKeys.has(cacheKey)) { | ||
| uncachedKeys.delete(cacheKey); |
| if (skipCache) { | ||
| await this.cache.delete( | ||
| this.cache.generateKey(cacheConfig.cacheKey, executorKey), | ||
| ); |
| if (skipCache) { | ||
| await cache.delete(cache.generateKey(arrowKey, executorKey)); | ||
| } |
| * Used by polling/refetch operations to force fresh execution. | ||
| * @internal | ||
| */ | ||
| skipCache?: boolean; |
There was a problem hiding this comment.
given that we have this issue #549 should we make this not internal? and also allow the hook in the UI to pass the skip?
| const parsed = JSON.parse(options.payload); | ||
| requestPayload = JSON.stringify({ ...parsed, skipCache: true }); |
There was a problem hiding this comment.
this exploded my mind a bit, don't we have skipCache in the options? why do we need to parse and stringify again?
Stacked PR 1 of 4 — analytics query polling (
useAnalyticsQuery({ poll })).Base:
main. This is the bottom of the stack; review/merge first.What
Surface the analytics request store's existing settled-key re-run as an internal
refetch(cacheKey), and add a force-uncached execution mode so a re-run with unchanged params genuinely re-hits the warehouse.analytics-request-store.ts: internalrefetch(cacheKey)marks the key for uncached execution (per-run, consumed once) then calls the genericstore.start(cacheKey).skipCache: truefield toIAnalyticsQueryRequest; the analytics route honors it to bypass its TTL cache (works for JSON_ARRAY and ARROW_STREAM). Backward-compatible (optional, defaults false).Dormant
No public API, no scheduler. Consumed only by store unit tests. Nothing observable changes yet.
Stack
usePollscheduleruseAnalyticsQuery({ poll })binding + public types (behavior-adding)Tests
Full suite green; store unit tests assert deferred-start +
refetchfresh settle on unchanged params, andskipCacheon the outgoing request.This pull request and its description were written by Isaac.