Repository navigation
Replies: 4 comments
|
Additional browser-side measurements from the same DSH 0.2.1-rc.2 deployment. This is a separate bottleneck from the server startup profile in the original post.
The original post's ~323 ms Could the list API send only fields needed for sidebar rows, fetch heavier projections when a session is opened, and support effective pagination/incremental loading (including archived sessions)? In 0.2.1-rc.5, |
|
Runtime supplement: the same projection data also stalls the page open, and the list call serializes the session open. Same deployment ( I added
The list call serializes the session open. DevTools' Socket panel ( The snapshot has a message cap, not a byte cap. One contributor was a third-party plugin, and it is now fixed. The Agent Teams activity panel polled Suggestion for the list API, on top of the previous comment:
The startup problem in the original post and this runtime problem are the same underlying data (very large projection values, eagerly materialized); the startup patch reduces boot cost but does not reduce what every page load has to ship. |
|
Which projection blocks actually cost the bytes: per-key shares and a real A/B. I reproduced the list RPC against the live host so the numbers below come from the real response rather than an estimate: Result: 880 items, 7,419,220 B decoded, 890,205 B gzip(-6). nginx reports 1,128,369 B transferred for the same request at its
Per key, aggregated across the 880 rows:
The two halves disagree, which matters for choosing a fix:
Real A/B on the same captured response (rebuild the document with one key removed, then
If the goal is "the sidebar opens faster", dropping Archived sessions are a large share of the list. On this data directory 182 of the 880 rows are sessions the user has archived, but they are the oldest and heaviest ones:
So half of the transferred list is content for sessions the sidebar is not showing by default. Excluding archived sessions from the default list (or paginating), and fetching their projections only when the archive view is opened, is a cheap and independent win — smaller than the Suggested priority, given these numbers:
|
Follow-up after reducing the session set: two remaining page-open pathsI repeated the browser test after the two large background costs had been removed:
The new 1. Attachment-heavy sessions: server TTFB is fast; transfer and cache policy dominateOne older session contains four PNG files served through
The response contains:
So this case is not slow server computation. It is slow delivery over the VPN, compounded by DSH forcing Chrome to download unchanged attachments again on every open. Revalidation (ETag/Last-Modified + 2. Large text/history sessions: the list is no longer the gateA different session with 2 rounds / 69 steps / 7.9M tokens stayed on “loading history” for about 12.7 s after the click. During that interval the ordinary Fetch/XHR traffic was only 13.1 KB (mostly small status requests and 304 Agent Teams polls). This points to the existing This supports the earlier suggestion to cap While switching sessions I also captured this frontend error:
The stack goes through Relationship to the original report: the startup and list regressions are both caused by eagerly materializing large per-session data across the whole retained set. After that set is reduced, the same design still appears at the single-session boundary through an unbounded-by-bytes follow snapshot. The attachment case is a separate transport/cache bug that amplifies the delay on VPN links. |
Uh oh!
There was an error while loading. Please reload this page.
Summary
dsh webstartup time grows with the number of persisted sessions. On a long-livedinstallation (968 session directories, 3+ months of use) startup went from 12 s to 25 s in
three days, and RSS right after boot is 1.42 GB versus 454 MB with an empty data directory.
The dominant term is the eager, unconditional load of the entire session projection cache —
including a recursive
isJsonValuevalidation of every value on the read path — for datathe process itself just wrote.
Environment
web: 199 plugin entries (190 from@deepseek-ai/dsh-base+@deepseek-ai/dsh-web-app, 9 third party)storages/session_projcache/sessions/= 969 JSON files, 209.1 MB (avg 215 KB, max 2.9 MB, avg 25.8 rows per record)dsh web: http://…ready lineReproduction
dsh web --no-openand time until the ready line.cp -a --reflink=auto) of the same directory:Reading and parsing the cache directory alone (
readdir+readFile+JSON.parse, novalidation) takes 2.4 s;
readdiritself is 0.8 ms, so it is all parse work.Current behavior
CPU profile of one boot (20.3 s wall, 18402 samples):
parseRecordsubtree (including zodvalueSchema.parse): 5327 ms (26%)compileSourceTextModule: 1925 msdsh-util-valuesself time: 3849 ms —walkJsonValue1462,hasIntrinsicConstructor895,enumerableStringKeys638,isJsonValue504The chain:
dsh-session-projection-cache/lib/index.js:151—const domain = await this.ctx.storageDomain.open(projectionCacheDomainSpec);dsh-storage-domain/lib/index.js:355async open(spec)→:364const snapshot = await unit.loadAll();— unconditional, no notion of "which records doesthe boot path actually need".
dsh-storage-json/lib/index.js:325loadPerRecordState()—readdirthenPromise.all(readRecord)over every*.json(:410-426), each oneJSON.parsefollowed by
tableSpec.valueSchema.parse.dsh-session-projection-cache/lib/index.js:28-32—checkpointRow = z.object({ ver, seq, val: z.custom(isJsonValue) }), so every row'svalis walked recursively (25.8 rows per record on average, up to hundreds for long sessions).
Two aggravating factors:
put()only doessnapshotJsonValue(rows)(lib/index.js:348-349) — while the read path re-validates everyvalue deeply on every boot. We pay the full cost for data that no other process can have
corrupted.
JSON.stringify({ version, record }, null, 2), so 209 MB on disk where compact JSON wouldbe 107 MB: 2× the bytes to read, decode and validate, for no benefit.
dsh-session-projection-cache/README.mdalready documents "No eviction or retentionsurface — records accumulate per session", so for a long-lived installation this cost is
monotonically increasing (this one adds 10–20 sessions per day).
Related context: the rest of the boot path is cheap and linear —
session/listcosts ~323 msfor 968 sessions (one zstd header read +
statper artifact). The projection cache is~100% of the startup regression and ~65% of post-boot RSS.
Expected behavior
open()should be able to open a domain without reading every record. A lazyget(key)/load(key)path would let plugins that only touch a few records avoid thefull snapshot.
self-written data.
Patch sketch
storage-domain:open(spec)accepts{ eager?: boolean }; exposeget(key); keeploadAll()for callers that genuinely need a full snapshot.session-projection-cache: do notopen()inService.init; open lazily on the firstprojection read, or open with
eager: falseand fetch per session.storage-json: move theisJsonValuecheck intoput()/serializeRecord, or gate itbehind a
--validate-recordsflag.Happy to run any patch against this data directory and report boot time and profile
before/after — the full
.cpuprofileis available if it helps.All reactions