feat(cache): local graph-database cache for localized shards (Grafeo) #143
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "142_graph-database-cache"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements #142.
Caches parsed stream files in an embedded Grafeo graph at
.streams-cache.grafeoinside the stream folder. Commands validate it withstatalone and re-read only files that actually changed.Design
The graph is split into two layers, which is what makes the change safe:
:File,:Shard,HAS_ROOT/HAS_CHILD/IN_FILE) — stores rawShardtrees. Independent of anyRepositoryConfigurationor timezone.:Tag,:DimensionValue,:Day,TAGGED/PLACED_IN/ON_DAY) — derived by localizing under the canonical merged configuration. Purely additive; only graph queries read it.Commands read the structural layer and re-run the pure
localize_stream_filewith their own configuration and timezone. That matters because the same folder is loaded three different ways today (todo/edituseTaskConfigurationat UTC,timesheetusesBasicTimesheetConfigurationat the repo timezone, the LSP merges both). CachingLocalizedShards directly would have forced config and tz into the cache key; this way output is unchanged.Everything is derived state: a corrupt cache, an unknown schema version, or a lock held by another process all resolve to rebuilding or falling back. A cache failure can never fail a command.
Verified
On the real 126-file corpus,
todoandtimesheetare byte-identical with and without the cache. All four build targets pass:nix flake checkstreamd-muslstatic-pie linked, strippedstreamd-windowsPE32+ executablestreamd-deb261 tests pass (up from 211 on
main— 50 new), including equivalence across both configurations × two timezones on cold and warm caches, corruption recovery, schema bump, deletion, and lock contention.Performance: no local win
Measured on this machine (126 files, 235 KB, local SSD):
The warm path is 1 ms slower than not caching at all. Parsing 235 KB is already trivial, so graph open + stat + reconstruct costs slightly more than it saves. This is consistent with the issue's premise — the target is the WSL/OneDrive share where per-file reads dominate — but it means the benefit is still unproven. The honest success criterion remains a before/after warm run on the work machine; if it does not pay off there,
STREAMD_NO_CACHE=1andstreamd cache clearmake it a no-op.Deviations from the plan
Three, each with a reason:
["edge", "storage"], not["gql"]. The planned line enables neither the LPG property-graph store nor persistence —GrafeoDB::opendoes not exist under it. Dependency tree is 46 crates with no*-sys/cc/bindgen; neither allocator feature is enabled.didSave/didChangeWatchedFiles. Redundant: invalidation is stat-based, so the next process to open the cache re-reads just that file. The graph is self-correcting.:Daynodes added. The plan listedfiles_touching_date, but dates are localization output, not structural facts. Modelling days as nodes keeps canonical moments out of the config-independent layer while still making date questions a traversal.Notable fixes found while building
open_or_rebuildtreated any open failure as corruption and deleted the file — so a second process would wipe a database the first was actively using. Lock contention is now distinguished and propagated untouched. Pinned bytest_a_locked_cache_is_not_deleted.load_markdown_shardswas order-nondeterministic. It returnedWalkDirorder; since consumers use a stablesort_by_key(|s| s.moment), two files sharing a timestamp could be ordered arbitrarily between runs. Both paths now sort by the timestamp-prefixed filename. This is a small deliberate behaviour change, in the direction of determinism.@don'tterminated the literal early.Follow-up for the user
The stream folder is a git repo with no ignore entry for the cache;
.streams-cache.grafeo*should be added there (it is already ignored in this repo). It should also be excluded from OneDrive sync — it is a binary rewritten on every note change, so syncing it means constant upload churn and cross-machine conflict copies. No cache file was left behind on the notes folder.https://claude.ai/code/session_012figbDQRDBcAx11FYarFBu
streamd cachestatus, rebuild and clear f7965ce7adUpdate: cache relocated to the XDG cache directory
Pushed
603d240. The cache no longer lives in the stream folder.Location is now
~/.cache/streamd/<slug>-<hash>.grafeoviaProjectDirs::cache_dir(). XDG cache, not config — it is rebuildable derived state that should be safe to delete and pointless to back up.One database per stream folder. The name is
<slug>-<hash>.grafeo, where the hash is FNV-1a over the folder's canonical absolute path. Multiple streamd instances with different config files therefore never share a cache, and two folders sharing a basename do not collide. Canonicalizing first means.,.., a trailing slash and symlinks all resolve to one cache rather than four. The slug is for humans browsing the cache directory and never affects identity.STREAMD_CACHE_DIRoverrides the directory; it also keeps tests out of the developer's real cache.The relocation surfaced a bug in the original design
Grafeo's
spillfeature creates a.grafeo.spillsidecar directory beside every database. The cache was therefore never the single file it was documented to be, andremove_cachewas orphaning the sidecar on everyclear— including inside the stream folder, where it left an empty directory behind after the earlier version ran.Fixed by dropping
spillfrom the feature set (storage= wal + grafeo-file + spill + mmap →wal+grafeo-file). There is now exactly one file per stream folder, verified on the real corpus.remove_cachestill sweeps.spill/.walsidecars defensively, so a cache written by a differently-configured build leaves nothing orphaned. I also removed the stray.spilldirectory the earlier build left in the notes folder.Re-verified
todoandtimesheetbyte-identical with and without the cache, on the real 126-file corpus~/.cache/streamd/holds exactly one file; the stream folder is untouchednix flake check,streamd-musl,streamd-windows,streamd-deball passDocs
README's "Local Cache" section rewritten for the new location, and the
.gitignore/ sync-exclusion advice removed — there is nothing to exclude any more. R26 in REQUIREMENTS updated with the path scheme, canonicalization and per-folder separation rules. The repo's own.gitignoreentry is gone.The earlier "Follow-up for the user" note in the PR description is obsolete — no
.gitignoreentry or OneDrive exclusion is needed now.https://claude.ai/code/session_012figbDQRDBcAx11FYarFBu