perf(ordered-collection): back SnapshotableQueue with DoublyLinkedList#27421
perf(ordered-collection): back SnapshotableQueue with DoublyLinkedList#27421anthony-murphy wants to merge 2 commits into
Conversation
|
Hi! Thank you for opening this PR. Want me to review it? Based on the diff (61 lines, 3 files), I've queued these reviewers:
How this works
|
|
|
||
| export class SnapshotableArray<T> extends Array { | ||
| protected data: T[] = []; | ||
| export class SnapshotableArray<T> { |
There was a problem hiding this comment.
Deep Review: The class name SnapshotableArray was chosen in #1395 (vladsud, 2020-03-05) specifically because it was an Array subclass. This PR removes the extends Array — the PR description acknowledges it ("SnapshotableArray no longer extends Array — it is now a plain class") but the name stays. Both blind solution-space proposers independently expected the class to still be array-backed, so a future reader is likely to hit the same surprise.
The symbol is internal to the package (not exported via api-report; only consumer is SnapshotableQueue in consensusQueue.ts), so renaming in-PR is cheap — e.g. SnapshotableCollection or SnapshotableList. If the rename is deferred, a one-line doc comment noting the legacy name predates the linked-list backing would save the next reader from re-deriving "why is this called Array?".
| this.data = from; | ||
| for (const value of from) { | ||
| this.data.push(value); | ||
| } |
There was a problem hiding this comment.
Deep Review: asArray() changes from return this.data (alias) to materializing a fresh T[] by iterating the list; loadFrom changes from this.data = from (alias) to per-element push. Both surfaces newly depend on DoublyLinkedList iteration order matching insertion order, and that's the sole load-bearing invariant in this area — the snapshot wire format. #1395 has no inline review history on these methods and no targeted unit test locks the contract.
There is strong transitive coverage: DoublyLinkedList iteration order is unit-tested (list.spec.ts:50-57, :71-80) and the ordered-collection fuzz model compares asArray() across clients (fuzzUtils.ts:150-159, :182-196). That lowers — but does not erase — the value of a local lock.
Suggest one ~10-line unit test in this PR: loadFrom([a,b,c]) → remove() → assert asArray() returns [b,c] and size() === 2. Locks the new iteration-based snapshot contract against future DoublyLinkedList or refactor regressions, and directly substantiates the "byte-identical wire format" claim. Alternatively, if the existing fuzz consistency check (fuzzUtils.ts:150-196) already exercises a loadFrom → partial remove() → asArray() cycle after a snapshot/load, calling that out in the PR description is sufficient.
Deep ReviewReviewed commit Readiness: 8/10 — ALMOST READY Small, well-scoped perf change: Path to Ready
Context for Reviewers
For human reviewer
Review history (1 prior review)
|
Description
Replaces the array backing
SnapshotableArray(the base class forSnapshotableQueue) withDoublyLinkedList<T>.SnapshotableArrayno longer extendsArray— it is now a plain class.add(value)→list.push(value).remove()inConsensusQueue→list.shift()!.data(O(1) instead ofArray.shift()'s O(n)).asArray()materializes from the list (preserves snapshot wire format byte-for-byte).sizedelegates tolist.length.loadFrompushes each value in order (preserves ordering).Why
SnapshotableQueue.remove()is on the hot path forConsensusOrderedCollection.acquire. Queue depth grows with backlog of unacquired items; in a job-queue scenario everyacquirepaid O(n) for the array shift. The DoublyLinkedList makes it O(1) without changing the snapshot format.Notes
Snapshot wire format is byte-identical (still a raw
T[]on disk viaasArray()).SnapshotableArrayis not exported, and no in-repo callers used Array prototype methods on it. Pure perf prep — noreSubmitSquashed, no squash logic, no tests touched, no api-report changes.