Skip to content

Add mmap-io-backed append-only file, mmap-backed Index, and mmap-backed Partition with direct mapped-buffer reads#315

Draft
Copilot wants to merge 23 commits into
mainfrom
copilot/implement-mmap-storage-layer
Draft

Add mmap-io-backed append-only file, mmap-backed Index, and mmap-backed Partition with direct mapped-buffer reads#315
Copilot wants to merge 23 commits into
mainfrom
copilot/implement-mmap-storage-layer

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 14, 2026

  • Review the existing storage benchmark and storage read/watch APIs to mirror current benchmark coverage
  • Run the repository tests to capture the current baseline before changes
  • Add a new storage benchmark that forks async child readers, excludes process start/stop time, and reports wall-clock completion plus per-reader completion averages
  • Measure write-to-reader visibility latency for each storage variant and reader count, and include those metrics in the benchmark output
  • Run the repository tests again and execute the new benchmark script to verify it works end to end

@albe
Copy link
Copy Markdown
Owner

albe commented May 14, 2026

@copilot You forgot the "mmap" part of the thing. Use mmap-io (@riaskov/mmap-io version) to implement actual OS based memory mapping of a file, with the same instructions I gave earlier

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 14, 2026

@copilot You forgot the "mmap" part of the thing. Use mmap-io (@riaskov/mmap-io version) to implement actual OS based memory mapping of a file, with the same instructions I gave earlier

Implemented with real OS-backed file mapping via @riaskov/mmap-io in src/File/AppendOnlyMmapedFile.js (map/remap/grow now use mmap.map, flush uses mmap.sync), and updated dependency metadata/lockfile in commit e3aeef7.

Copilot AI changed the title Add append-only mmap-backed file abstraction with logical-size marker and readonly remap support Add append-only mmap-io-backed file abstraction with logical-size marker and readonly remap support May 14, 2026
Copilot AI requested a review from albe May 14, 2026 14:46
Copilot finished work on behalf of albe May 14, 2026 14:46
Copilot finished work on behalf of albe May 14, 2026 16:40
Copy link
Copy Markdown
Owner

@albe albe left a comment

Choose a reason for hiding this comment

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

@copilot address the comments

Comment thread src/File/AppendOnlyMmapedFile.js
Comment thread src/File/AppendOnlyMmapedFile.js
Comment thread src/File/AppendOnlyMmapedFile.js
Comment thread src/File/AppendOnlyMmapedFile.js Outdated
Comment thread src/File/AppendOnlyMmapedFile.js Outdated
Comment thread src/File/AppendOnlyMmapedFile.js Outdated
Copilot AI and others added 2 commits May 14, 2026 17:10
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 14, 2026

@copilot address the comments

All review comments addressed in commits 531cc20 and a99e26f:

  • writeFileSizeMarker, flush, and truncate moved to WritableAppendOnlyMmapedFile
  • pageSize uses mmap.PAGESIZE directly and lives in the writable subclass (only consumer)
  • writeBufferSize is clamped to Math.max(this.pageSize, options.writeBufferSize || DEFAULT_WRITE_BUFFER_SIZE)
  • Dirty-range tracking (markDirty, clearDirtyRange, dirtyFrom, dirtyTo) removed entirely

Copilot finished work on behalf of albe May 14, 2026 17:13
Copilot AI requested a review from albe May 14, 2026 17:13
@albe
Copy link
Copy Markdown
Owner

albe commented May 14, 2026

@copilot add an example implementation of an append only mmap file backed Index and place it besides the existing Index implementation.

Copilot AI and others added 2 commits May 14, 2026 17:49
…bleIndex, MmapWritableIndex, MmapReadOnlyIndex)

Agent-Logs-Url: https://github.com/albe/node-event-storage/sessions/2d421c70-53fb-4e29-a82f-e3da3bb18748

Co-authored-by: albe <4259532+albe@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 14, 2026

@copilot add an example implementation of an append only mmap file backed Index and place it besides the existing Index implementation.

Added in commit 8f87e5e (cleanup in cec0c39):

  • src/Index/MmapReadableIndex.js — base class; reads entries directly from the mmap buffer via ReadableAppendOnlyMmapedFile, same header format as the existing index (nesidx01 magic + JSON metadata). No extra copy or syscall per read.
  • src/Index/MmapWritableIndex.js — appends fixed-size entry records into the mmap file using WritableAppendOnlyMmapedFile. The metadata header is passed as initialData so it is written atomically on first creation. Delayed flush with per-position callbacks mirrors the existing WritableIndex API.
  • src/Index/MmapReadOnlyIndex.js — wraps ReadOnlyAppendOnlyMmapedFile and translates the file-level append/truncate byte-size events into entry-count events (emit('append', prevLen, newLen)).
  • src/MmapIndex.js — facade mirroring src/Index.js; exported from the package as MmapIndex.
  • test/MmapIndex.spec.js — 18 tests covering open/close, add, get, range, find, truncate, metadata persistence, and live reader events.

Copilot AI changed the title Add append-only mmap-io-backed file abstraction with logical-size marker and readonly remap support Add append-only mmap-io-backed file abstraction with logical-size marker, readonly remap support, and mmap-backed Index May 14, 2026
Copilot finished work on behalf of albe May 14, 2026 17:53
Copy link
Copy Markdown
Owner

@albe albe left a comment

Choose a reason for hiding this comment

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

@copilot apply the comment change, then run the index benchmark with the mmap'd index implementation against stable and latest basic version. If mmap is slower for the read benchmark, implement a caching of the read Entry items that fills up and returns a slice of the cached array

Comment thread src/Index/MmapWritableIndex.js Outdated

const buf = Buffer.allocUnsafe(this.EntryClass.size);
entry.toBuffer(buf, 0);
this.file.write(buf);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Add a file API that allows entry.toBuffer() to receive the buffer location of current write position of the file, with a given length (or until the end of the mapped range by default). e.g. entry.toBuffer(this.file.write(this.EntryClass.size), 0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in commit f9516f6. Added a dedicated reserve(size) method to WritableAppendOnlyMmapedFile that reserves bytes at the current write position and returns the mapped subarray for in-place serialisation. MmapWritableIndex.add() now uses entry.toBuffer(this.file.reserve(this.EntryClass.size), 0) — no intermediate Buffer.allocUnsafe per add. The original write(data) keeps its Buffer/string signature unchanged.

Copilot AI and others added 2 commits May 14, 2026 20:57
@albe
Copy link
Copy Markdown
Owner

albe commented May 15, 2026

Bench result with 1000 Writes being consumed across N readers. 1 OP = 1000 reads

Readers Stable ops/s Latest ops/s Mmap ops/s
1 172 161 190
2 145 148 170
4 126 139 153
8 115 134 138
16 104 126 129

Copy link
Copy Markdown
Owner

@albe albe left a comment

Choose a reason for hiding this comment

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

@copilot also implement your earlier suggestion:

Keep logical size in memory during hot write loop and write marker once per flush.

After doing so, rerun the benchmark again.

}

this.file.open();
this.fd = this.file.fd;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Where is the copied reference to fd needed? Only file should hold it

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 22, 2026

@copilot also implement your earlier suggestion:

Keep logical size in memory during hot write loop and write marker once per flush.
...

Implemented in e4ab266: WritableAppendOnlyMmapedFile.grow() no longer writes the file-size marker (or marks pending) during growth, so marker updates stay flush-driven while logical size remains in memory during the write loop.

Reran the storage benchmark (bench/bench-storage.js) after the change:

  • stable: 175 / 148 / 128 / 116 / 105 ops/s (readers 1/2/4/8/16)
  • latest: 164 / 145 / 138 / 130 / 122 ops/s
  • mmap: 183 / 164 / 152 / 143 / 130 ops/s

Copilot finished work on behalf of albe May 22, 2026 14:35
Copilot AI requested a review from albe May 22, 2026 14:35
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