Skip to content

fix(sdk): fill encrypt segments without re-copying the partial buffer (DSPX-5232) - #1095

Draft
dmihalcik-virtru wants to merge 2 commits into
mainfrom
DSPX-5232-segment-buffer
Draft

dmihalcik-virtru wants to merge 2 commits into
mainfrom
DSPX-5232-segment-buffer

Conversation

@dmihalcik-virtru

@dmihalcik-virtru dmihalcik-virtru commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

What / why

DSPX-5232, part 1 of 2 (CRC-32 is #1096, stacked on this).

writeStream concatenated every input chunk onto the partial segment (concatUint8([currentBuffer, value])) and sliced the remainder off with another copy. Filling a segment of size S from chunks of size c therefore copied about S²/2c bytes. Browser File.stream() and Node file streams deliver about 64 KiB chunks, so encrypt collapses as segments grow. That blocks raising the default segment size (DSPX-4651). Found in the DSPX-5124 large-TDF analysis (item #8).

  • Each input byte is copied once, into a segment buffer allocated when the segment starts. Full segments queue up and are encrypted under the existing backpressure loop. Segment boundaries and the output format are unchanged.
  • A full segment's buffer goes to the cipher without the extra toArrayBuffer copy. AesGcmCipher.encrypt also drops a redundant copy of its concatenated output.
  • Adds lib/scripts/bench-encrypt.mjs (npm run bench:encrypt). It runs the SDK's own writeStream with key access stubbed out, so no KAS is involved, and runs each segment size in its own child process so peak RSS is per cell. It reports throughput and peak RSS. It isn't published (scripts/ is not in files).
  • Fills in spec/DSPX-5232.md.

Results

npm run bench:encrypt -- --lib <main> --lib <this>, Node 24.21.0, Apple M5 Pro, 1 GiB in 64 KiB chunks:

segment main MiB/s main peak RSS this PR MiB/s this PR peak RSS
2 MiB 307 508 MiB 421 245 MiB
16 MiB 59 1,408 MiB 465 374 MiB
64 MiB 15 4,861 MiB 448 755 MiB
256 MiB 4 16,895 MiB 437 2,262 MiB

Throughput is now flat in segment size. Peak memory is still linear in segment size, as it was before, but at about 9× the segment instead of 66–88×: 16.5 GiB → 2.2 GiB at 256 MiB. It can't be flat, because the cipher needs one segment's plaintext and ciphertext at once. The remaining per-segment copies (analysis #10) are out of scope.

xtest paired A/B (js@main vs. the PR stack, 1 KiB / 1 MiB / 32 MiB, local platform): no regressions in any of the 7 cells; noise floor ±4.5%. Those payloads are dominated by about 1.1 s of CLI startup, and the CLI reads in 8 MiB chunks, so this fix barely shows there.

How to test

cd lib && npm run build && npm run bench:encrypt
npm test   # includes tests/mocha/encrypt-segmentation.spec.ts

encrypt-segmentation.spec.ts round-trips these chunking patterns and checks the manifest's segment sizes: empty input, one byte at a time, chunks straddling boundaries, exact-segment chunks, one chunk holding several segments, and a large chunk after a partial segment.

Risk

Touches the encrypt hot loop and one line of AesGcmCipher. The ciphertext format is unchanged. A source that overwrites one buffer for every chunk was already broken with any Web Streams consumer, because the stream pulls again before the reader's read() continuation runs. This PR doesn't change that.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
… (DSPX-5232)

writeStream concatenated every input chunk onto the partial segment, so
filling a segment of size S from chunks of size c copied about S^2/2c bytes.
With 64 KiB chunks (browser File.stream(), Node file streams) encrypt fell
from 311 MiB/s at 2 MiB segments to 17 MiB/s at 64 MiB, with peak RSS of
4.8 GiB, and this blocks raising the default segment size (DSPX-4651).

Copy each input byte once, into a segment buffer allocated per segment, and
hand full segments to the cipher without the extra toArrayBuffer copy.
AesGcmCipher also dropped a redundant copy of its concatenated output.

Adds scripts/bench-encrypt.mjs (npm run bench:encrypt), which sweeps
writeStream over segment sizes in isolated child processes and reports
throughput and peak RSS.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@dmihalcik-virtru
dmihalcik-virtru force-pushed the DSPX-5232-segment-buffer branch from 7a9e2a4 to f961675 Compare October 19, 2026 17:27
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
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