Skip to content

perf(storage): avoid extension discovery for internal JSON resources - #2212

Draft
jeswr wants to merge 8 commits into
CommunitySolidServer:mainfrom
jeswr:perf/extension-mapper-avoid-readdir
Draft

perf(storage): avoid extension discovery for internal JSON resources#2212
jeswr wants to merge 8 commits into
CommunitySolidServer:mainfrom
jeswr:perf/extension-mapper-avoid-readdir

Conversation

@jeswr

@jeswr jeswr commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Problem

Internal account storage uses JSON resources, but the file identifier mapper was not given that content type. Resolving an internal resource therefore scanned its parent directory to discover the extension, making account-index lookups O(n).

On a production instance with roughly 20,000 accounts this caused sustained 232% Node.js CPU and about 2,000 getdents calls every two seconds.

Change

  • Add ContainerContentTypeMapper to supply configured document and metadata types within a container.
  • Configure /.internal/ resources as JSON with Turtle metadata for suffix and subdomain setups.
  • Keep extension discovery unchanged for resources whose type is genuinely unknown.
  • Preserve existing internal filenames and handle encoded internal paths consistently with the file mapper.

Production result

Metric Before After
Node.js CPU 232% 49%
Index directory scans / 3s 88 0
getdents / 2s ~1,938 ~0
Pod root read 3.08s 164ms

Performance check

For 500 lookups in a simulated 20,000-entry account directory, five runs gave:

Metric main This PR
Directory scans 500 0
Elapsed time 126.9–140.7ms 3.5–3.7ms

The unit test also asserts that internal lookups, including an encoded %2Einternal path, never call readdir.

Tests

  • Mapper and extension-mapper unit tests: 32 passed.
  • Account, subdomain, and v6 migration integration tests: 72 passed.
  • Full unit suite: 359 suites and 2,384 tests passed with 100% coverage.
  • Conditions integration suite: 16 passed; cleanup stops the server first and retries transient filesystem errors.
  • Build, TypeScript, and lint checks passed.

jeswr and others added 6 commits August 18, 2026 21:06
…ions

ExtensionBasedMapper.mapUrlToDocumentPath did a full readdir(folder) on every
document read without a known content-type, to discover the file's extension.
For large directories - e.g. the internal account-index storage with tens of
thousands of flat entries - this is O(folder size) per read, dominating CPU
under auth/OIDC load: every login findByEmail / client lookup scanned the whole
index directory (positive AND negative lookups).

- Probe the exact file and the common `$.<ext>` variants via `stat` (O(1)) before
  falling back to readdir, so positive lookups avoid the scan.
- Skip the readdir fallback entirely for the reserved `/.internal/` storage, whose
  resources are always JSON, so negative index lookups (e.g. a login for a
  non-existent email) are O(1) too. Pod resources keep the readdir fallback since
  they may use arbitrary extensions.

Behaviour-preserving (verified by algorithm-equivalence tests across exact match,
common/uncommon extension, empty name, directory entries; internal reads confirmed
JSON-only). Measured on a production instance: node CPU 232% -> ~48% and stable,
index directory scans ~90/3s -> 0, pod root read 3.08s -> ~80ms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 05:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves performance of file-based account storage lookups by ensuring internal /.internal/ resources are mapped with known document/metadata content types (JSON + Turtle), avoiding directory scans for extension discovery when resolving internal resources.

Changes:

  • Introduces ContainerContentTypeMapper to inject configured content types for resources under a specific container.
  • Updates suffix and subdomain identifier configurations to wrap the existing extension-based mappers with ContainerContentTypeMapper for /.internal/.
  • Adds unit coverage to ensure internal lookups (including encoded %2Einternal) do not trigger directory reads.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/unit/storage/mapping/ContainerContentTypeMapper.test.ts Adds unit tests validating correct content-type injection, rejection of mismatched content types, and no readdir calls for internal resources.
src/storage/mapping/ContainerContentTypeMapper.ts Adds a wrapper mapper that enforces configured document/metadata content types within a target container before delegating to the underlying mapper.
src/index.ts Exports the new mapper from the public entrypoint.
config/util/identifiers/suffix.json Wraps ExtensionBasedMapper with ContainerContentTypeMapper to treat /.internal/ as JSON with Turtle metadata.
config/util/identifiers/subdomain.json Wraps SubdomainExtensionBasedMapper with ContainerContentTypeMapper to apply the same /.internal/ mapping in subdomain setups.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jeswr jeswr closed this Aug 19, 2026
@jeswr jeswr reopened this Aug 19, 2026
@jeswr
jeswr marked this pull request as draft August 19, 2026 11:49
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.

2 participants