Skip to content

perf: Extend the JSON-LD fast path to config documents (CSS cold start 16.9 s -> 6.3 s)#6

Draft
jeswr wants to merge 1 commit into
perf/jsonld-fast-pathfrom
perf/jsonld-fast-path-configs
Draft

perf: Extend the JSON-LD fast path to config documents (CSS cold start 16.9 s -> 6.3 s)#6
jeswr wants to merge 1 commit into
perf/jsonld-fast-pathfrom
perf/jsonld-fast-path-configs

Conversation

@jeswr

@jeswr jeswr commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Extends #1's guarded JSON-LD fast path to hand-written config documents — the other half of a Components.js application load. Stacked on #1 (base branch perf/jsonld-fast-path); review this PR for the extension diff only.

Phase-2 profiling showed that after #1, the dominant remaining cost of a Community Solid Server cold start was the generic streaming pipeline re-resolving type-scoped contexts once per typed node in the 145 imported config files (1,405 distinct context resolutions per boot, none repeating — so no context-level cache can absorb them). This PR converts that cost into once-per-(context, term) resolution at analysis time.

What's new in the guarded subset

  1. Root-level @graph — including the named-graph form componentsjs configs use ({"@context", "import": [...], "@graph": [...]}): a root node with properties alongside @graph names the graph after itself; its own quads (e.g. the importrdfs:seeAlso links that drive RdfStreamIncluder) stay in the default graph. Two mirrored subtleties, both probed against the generic parser and pinned by tests: list cells always land in the default graph even inside named graphs, and @graph-only documents emit into the default graph.
  2. Single, non-propagating type-scoped context activation — when a node's @type names a term carrying a scoped context, that context is resolved once per (context, term) with the real jsonld-context-parser (same options as the generic parser's ParsingContext.parseContext, minus the base IRI: the fast path falls back on anything base-relative, so the merged context is shareable across documents). The merged context is analyzed with the same term classification as the root context and applied to that node only; nested nodes revert to the root context (JSON-LD 1.1 non-propagation, verified against the generic parser). Out of subset (→ fallback): multiple scoped types on one node, propagating (@propagate) or unresolvable scoped contexts, scoped contexts declaring @vocab/@base, scoped terms used as property keys.
  3. Fixes latent double list wrapping for explicit {"@list": [...]} values under @container: @list definitions (found by the config corpus oracle).

Correctness evidence

  • Boot-corpus oracle: all 1048 CSS 7.1.9 boot files (828 generated component documents + 220 hand-written configs) parse rdf-isomorphic to the generic pipeline — on both the current dependency stack (jsonld-context-parser 3, rdf-parse 5) and the componentsjs 5.5.1 stack CSS actually boots with (jsonld-context-parser 2.3.2, rdf-parse 2.3.2, via the backport oracle).
  • Coverage: 1047/1048 files fast-path (all 828 components; 219/220 configs). The single holdout is config/ldp/authorization/webacl.json, which uses a base-relative @type ("WebAclMetadataCollector") — the fast path deliberately leaves all base-relative IRI resolution to the generic parser.
  • 30 new unit tests covering the @graph matrix (default/named/@id-only graphs, lists in named graphs, scalar/array/null entries, null-valued root properties) and the scoped-activation matrix (activation, redefinition, non-propagation, coercion + list containers in scopes, multi-scoped/propagating/@vocab/unresolvable scopes, scoped-as-key). Suite: 1012 tests, 100 % statement/branch/function/line coverage.

Performance — CSS cold start (the roadmap number)

Backport of this branch onto the installed componentsjs 5.5.1; node bin/server.js -c config/file.jsonListening, alternating arms, fresh data root per boot (pss-solid-test, 2-core shared, nice -n 18; ratios are the claim):

arm boots (ms) median vs stock
stock componentsjs 5.5.1 16,910 / 17,915 / 16,018 16,910
#1 as merged (phase 2 measurement) 10,261 / 10,192 / 11,181 10,261 1.65×
#1 + this PR 6,330 / 6,537 / 6,170 6,330 2.67×

977/978 boot documents fast-path; −10.6 s off the cold start. This lands CSS cold boot at the 4–6 s boot-latency roadmap target (and composes with #3's faster discovery scan and #4's warm-start persistence, which shave further). A phase-2 side-measurement worth recording: with this PR, an additional normalized-context cache in jsonld-context-parser no longer helps componentsjs boots (0 cache hits — the fast path caches contexts itself), which simplifies the upstreaming story to this repository alone.


Generated by an agent (Claude Fable 5) on @jeswr's behalf — staged on the fork for his review before upstreaming.

Review timing: This draft was prepared with Claude; I (@jeswr) will personally review it before it progresses. I'm currently batching a lot of work in flight, so expect active review Wednesday–Friday (8–10 July).

The initial fast path only covered generator-produced component
documents; hand-written config documents always fell back to the
generic streaming pipeline, which re-resolves type-scoped contexts for
every typed node. Profiling a Community Solid Server cold start showed
those per-node scoped-context activations (1,405 distinct resolutions,
none repeating within a boot) as the dominant remaining parse cost.

This extends the guarded subset with the two features config documents
use:

- Root-level @graph, including the named-graph form (a root node
  carrying properties such as `import` alongside @graph names the graph
  after itself; list cells always land in the default graph, exactly
  mirroring the generic parser's placement).
- Single, non-propagating type-scoped context activation: when a node's
  @type names a term with a scoped context, that scoped context is
  resolved ONCE per (context, term) with the real jsonld-context-parser
  (same options as the generic parser's ParsingContext.parseContext),
  analyzed, cached on the root entry, and applied to that node only,
  with nested nodes reverting to the root context (JSON-LD 1.1
  non-propagation semantics). Multiple scoped types, propagating or
  unresolvable scoped contexts, and scoped terms used as keys still
  fall back.

Also fixes double list wrapping for explicit @list objects under
@container: @list definitions (previously such documents fell back
before reaching the converter, so the case was latent).

Correctness: on the full Community Solid Server 7.1.9 boot corpus (828
component documents + 220 config documents), fast-path output is
rdf-isomorphic to the generic pipeline for all 1048 files on BOTH the
current dependency stack and the componentsjs 5.5.1 stack CSS actually
boots with; 1047/1048 take the fast path (the one holdout uses a
base-relative @type, which the fast path deliberately leaves to the
generic parser). 30 new unit tests cover the @graph and scoped
activation matrices; the suite retains 100% coverage.

Performance (pss-solid-test, 2-core shared box, alternating arms,
fresh data root per boot; backport of this branch onto the installed
componentsjs 5.5.1): CSS cold start drops from a 16.0-17.9 s stock
median of 16.9 s to 6.2-6.5 s (median 6.3 s) - 2.67x, with 977/978
boot documents fast-pathed. Combined with the earlier module-state
work this brings CSS cold boot to the 4-6 s range targeted by the
boot-latency roadmap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant