Commit c1c531b
authored
fix+feat(nexus/graph): cross-graph Composer traversal + query result caching (#1059)
* fix(nexus/graph): resolve cross-graph traversal in the Composer query compiler
A multi-hop column path that crosses a named-graph boundary (e.g. an ExtractedItem
in one graph -> chunk_of -> PDFPaperFile in another) returned empty cells: the whole
WHERE body was wrapped in a single `GRAPH ?g { ... }`, which binds one graph for every
triple, so no single graph satisfies the path.
Pick the scoping strategy per query:
- 1 graph selected -> the original single `GRAPH ?g` form (unchanged, fastest).
- >1 graph selected -> each triple is wrapped in its own `VALUES ?gK GRAPH ?gK`, so a
row's path may span graphs. Per-triple GRAPH keeps Jena/TDB2 on its native quad index;
a `FROM`-union default graph is also correct but ~10x slower on TDB2 (dynamic dataset).
Single-graph output is byte-identical (all golden tests unchanged). Adds multi-graph
unit + integration coverage.
* feat(nexus/graph): cache Composer query results with a bypass toggle
Executing the page SPARQL is the expensive part of a Composer query (seconds on large
cross-graph views) and the data changes infrequently, so memoize it.
Backend: GraphQueryService caches the page rows (new `page_cache_key`, keyed on the full
spec + workspace + page window) and the count, via the existing naas-abi-core CacheService
(FS tier -> shared across workers, survives restart). TTL 5 min, override with env
`NEXUS_QUERY_CACHE_TTL_SECONDS`. A request `force_refresh` flag bypasses both caches
(superset of `force_count_refresh`). Cache failures degrade to a live query.
Frontend: `forceRefresh` in the query client/types, a persisted `bypassCache` toggle in
use-explore surfaced as an "Always refresh" checkbox in the Composer toolbar; the Refresh
button now always fetches fresh.
Adds service tests: cache hit skips the store, force_refresh bypasses, no-cache default
recomputes, distinct page key per sort.
* feat(nexus/graph): cache column discovery (the "add column" dropdown)
Column discovery fires ~5 SPARQL queries per call (datatype props, relations, relation
target classes, incoming relations + their source classes) and was uncached — the main
Composer latency when picking a grain / adding a column. The discovered columns change
rarely, so memoize them.
GraphQueryService.discover_columns now caches its result via the same CacheService (new
`columns_cache_key` over workspace + grain graphs + class URIs + type-resolution graphs),
using the injected `columns_cache` (5-min TTL, shared with the query/count cache). Cache
miss/failure falls through to a live discovery. DiscoveredColumn/TargetClassData round-trip
through JSON. Adds cache-hit + serialization-round-trip tests.
* refactor(nexus/graph): read query-cache TTL from Settings, not raw env
The cache TTL was read with a bare `os.environ.get("NEXUS_QUERY_CACHE_TTL_SECONDS")`,
bypassing the app's pydantic-settings config (and that var was never set anywhere, so it
was effectively a hardcoded 300). Move it to `Settings.graph_query_cache_ttl_seconds`
(env `GRAPH_QUERY_CACHE_TTL_SECONDS`, default 300) like every other tunable.
Also make `0` genuinely disable caching: `timedelta(0)` is falsy, so the CacheService would
treat it as "no expiry" (infinite cache) — instead, TTL <= 0 now injects no cache at all.
* refactor(nexus/graph): back the query cache with the engine's multi-tier CacheService
Use the engine's configured CacheService (`ABIModule.get_instance().engine.services.cache`)
instead of a one-off FS cache, so the Composer query/column cache benefits from the hot
(Redis) + cold (FS/object-storage) tiers. `_resolve_query_cache()` prefers the engine cache
and falls back to a local FS cache if none is available; None when TTL <= 0.
Reads are a hot→cold read-through; writes go to cold (durable) AND, when a hot tier exists,
to hot (fast subsequent reads) — the CacheService's default set_json is cold-only, so hot is
populated explicitly. (Deployments enable the hot tier by adding a redis adapter under
`services.cache` in config; without it the engine cache defaults to a single cold FS tier.)1 parent aa08c1e commit c1c531b
13 files changed
Lines changed: 520 additions & 68 deletions
File tree
- libs/naas-abi/naas_abi/apps/nexus/apps
- api/app
- core
- services/graph
- adapters/primary
- query
- adapters/primary
- web/src
- components/graph/explore
- lib/graph-query
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
346 | 346 | | |
347 | 347 | | |
348 | 348 | | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
349 | 353 | | |
350 | 354 | | |
351 | 355 | | |
| |||
Lines changed: 72 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| 15 | + | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
| |||
83 | 85 | | |
84 | 86 | | |
85 | 87 | | |
86 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
87 | 92 | | |
88 | 93 | | |
89 | 94 | | |
90 | 95 | | |
91 | 96 | | |
92 | 97 | | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
93 | 101 | | |
94 | 102 | | |
95 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
96 | 159 | | |
97 | 160 | | |
98 | 161 | | |
| |||
1149 | 1212 | | |
1150 | 1213 | | |
1151 | 1214 | | |
1152 | | - | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
1153 | 1222 | | |
1154 | 1223 | | |
1155 | 1224 | | |
| |||
1169 | 1238 | | |
1170 | 1239 | | |
1171 | 1240 | | |
| 1241 | + | |
1172 | 1242 | | |
1173 | 1243 | | |
1174 | 1244 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
244 | 244 | | |
245 | 245 | | |
246 | 246 | | |
| 247 | + | |
| 248 | + | |
247 | 249 | | |
248 | 250 | | |
249 | 251 | | |
| |||
0 commit comments