Repository navigation
perf(ws): adopt orderbook snapshot dicts by identity (#296) - #306
Conversation
…ot_inplace _apply_snapshot_inplace wrapped msg.msg.yes / msg.msg.no in dict(...), silently nullifying the ~5x speedup CHANGELOG v2.5.0 #263 advertises. For a 200-level book that's 400 needless re-hashes and reallocs per snapshot on the recv hot path. The OrderbookSnapshotPayload._levels_to_dict BeforeValidator already produces a typed dict[Decimal, Decimal]; the model is discarded immediately after this call so adopting the dicts by identity is safe and matches the original v2.5 design intent (see comment in kalshi/ws/models/orderbook_delta.py). Adds a regression test (TestSnapshotIdentityAdoption) that pins: - state.yes is msg.msg.yes (no defensive copy on adoption) - subsequent _apply_delta_inplace mutates the adopted dict in place AND invalidates the #244 cached Orderbook Closes #296.
Code Review — PR #306:
|
| Item | Status |
|---|---|
| Optimization correctness | ✅ Sound |
| Safety argument | ✅ Valid |
| Ruff E302 blank line | ❌ Will fail CI — must fix |
| Comment issue references | |
| Test coverage | ✅ Good |
| mypy / types | ✅ No type changes |
Two items to address before merge: the missing blank line (CI-blocking) and trimming the issue numbers from the inline comment. The core change is correct and well-motivated.
|
Round-2 follow-up pushed as 77cf673:
Verified: |
Code ReviewOverviewThis PR removes two Issues1. Comment overstates a caller contract as a code invariant (minor)
# Adopt by identity — validator already produced dict[Decimal, Decimal],
# and the model is discarded immediately after this call so nothing
# outside _BookState can observe later in-place delta mutations."the model is discarded immediately after this call" is a requirement on the caller, not something this function enforces or can know. The The aliasing is safe in the current codebase, but the comment as written will mislead the next reader who asks "what if I hold # Adopt by identity — the validator already produced dict[Decimal, Decimal].
# Callers must not access msg.msg.yes/.no after this call; _BookState takes
# ownership and will mutate the dict in-place on every subsequent delta.2. The docstring for
|
- Reframe inline comment as ownership contract instead of caller behavior - Add ownership-transfer note to _apply_snapshot_inplace docstring Addresses round-2 review on #306
|
Round-2 follow-up (876da84):
|
Code Review — PR #306:
|
| Core fix | ✅ Correct and well-motivated |
| Aliasing hazard for raw subscribers | 🟡 Needs a docstring note on the public API |
| Test comment accuracy | 🟡 Minor — clarify what's being asserted and why |
| Blank lines / inline comment duplication | 🔵 Trivial style |
The aliasing concern is the only one that might surprise real users; the others are polish. Happy to approve once the public-API docstring is updated.
…trim redundant comment - Document on subscribe_orderbook_delta and OrderbookSnapshotPayload that msg.yes/.no are live dicts owned by OrderbookManager after dispatch (raw subscribers must copy or use client.orderbook() for immutability). - Drop the inline ownership comment in _apply_snapshot_inplace; the docstring covers it. - Rephrase the snapshot-identity test comment so it no longer reads as contradicting its own identity assertion. Addresses round-3 review on #306.
|
Round-3 follow-up at b548a5b:
Verified: ruff clean, mypy clean, |
Code ReviewOverviewThis PR removes two CorrectnessThe core change is safe. if all(type(k) is Decimal and type(v) is Decimal for k, v in value.items()):
return value # same object — no rehashWrapping that result in Aliasing Hazard — PR Description Is Slightly InaccurateThe PR description says:
That's not what happens. Looking at the dispatch path in self._orderbook_mgr._apply_snapshot_inplace(snapshot, sid=snapshot.sid)
pre_validated = snapshot
# ...
await self._dispatcher.dispatch(data, pre_validated=pre_validated)The snapshot is enqueued for subscribers — not discarded. This means a subscriber who calls Consider: is this the intended consumer contract? Subscribing via the raw async for msg in stream:
if isinstance(msg, OrderbookSnapshotMessage):
saved_yes = msg.msg.yes # this dict mutates on every delta!will observe aliasing bugs without an obvious cause. The docstring warns "they mutate on every subsequent delta" — but this is a class-level invariant that IDEs and type checkers won't surface. Worth considering whether the subscriber copy should be cheap enough to keep (it's one dict() call on a pre-built map, not the inner loop), or whether this contract is acceptable given that Test CoverageGood: Good: Missing — subscriber-side aliasing test: The documented footgun (subscriber's snapshot dict mutates on delta) has no test. A test like: def test_subscriber_snapshot_dict_is_live_after_delta(self) -> None:
mgr = OrderbookManager()
msg = OrderbookSnapshotMessage.model_validate(...)
mgr._apply_snapshot_inplace(msg)
subscriber_ref = msg.msg.yes # simulate what a raw-stream subscriber stores
mgr._apply_delta_inplace(make_delta(price="0.50", delta="25", side="yes"))
# Aliasing contract: subscriber_ref mutated because it IS state.yes
assert subscriber_ref[Decimal("0.50")] == Decimal("125.00")This would explicitly document the contract and prevent future "safety" refactors from accidentally breaking it by reintroducing a copy. Minor — blank line before new test class: PEP 8 requires two blank lines between top-level class definitions. The new DocumentationThe docstring additions are accurate and well-placed:
Summary
The change is ready to merge with the minor nits above. The aliasing contract question is the only one worth a deliberate decision before merging. |
…doption - New TestSnapshotIdentityAdoption.test_subscriber_held_snapshot_dict_mutates_on_delta documents the live-dict contract for raw subscribe_orderbook_delta consumers: a subscriber who stores msg.msg.yes observes future delta mutations because _BookState adopts the validator-built dict by identity. - Pins the contract called out in the docstrings on subscribe_orderbook_delta and OrderbookSnapshotPayload so a future defensive-copy refactor breaks the test instead of silently regressing perf or the documented behavior. Addresses round-4 review on #306.
|
Round 4 follow-ups (6fb6115):
|
Code Review — PR #306:
|
…ager class doc Addresses round-5 review on #306: - apply_snapshot docstring now flags that msg.msg.yes/.no are adopted by identity (public callers hit the same aliasing as raw subscribers). - OrderbookManager class doc clarifies that the "no mutations leak in" safety applies to returned Orderbook objects only; input snapshot fields are adopted by reference.
Code Review — PR #306: adopt orderbook snapshot dicts by identitySummary: The optimization is legitimate and well-motivated — removing a redundant Breaking public API contract (moderate)
book = mgr.apply_snapshot(snapshot_msg) # InitializeAfter this PR, any caller who retains Suggestion: either (a) keep the
|
…doption to _apply_snapshot_inplace Addresses round-5 review on #306: - Restore the no-aliasing contract on the public apply_snapshot() by defensively copying state.yes/no after the inplace adoption. The recv loop still benefits from identity adoption via the existing _apply_snapshot_inplace bypass (per #199), where the perf win actually matters; the public wrapper was never on the hot path. - Drop the aliasing warnings from apply_snapshot() and OrderbookManager class docstrings since the public API no longer leaks. - Keep the raw-subscriber aliasing note on subscribe_orderbook_delta and OrderbookSnapshotPayload (those paths still see live dicts). - New test_public_apply_snapshot_does_not_alias_input_msg pins the restored contract: a held msg.msg.yes is unaffected by subsequent deltas through the public apply_snapshot/apply_delta entry points.
…doption to _apply_snapshot_inplace Addresses round-5 review on #306: - Restore the no-aliasing contract on the public apply_snapshot() by defensively copying state.yes/no after the inplace adoption. The recv loop still benefits from identity adoption via the existing _apply_snapshot_inplace bypass (per #199), where the perf win actually matters; the public wrapper was never on the hot path. - Drop the aliasing warnings from apply_snapshot() and OrderbookManager class docstrings since the public API no longer leaks. - Keep the raw-subscriber aliasing note on subscribe_orderbook_delta and OrderbookSnapshotPayload (those paths still see live dicts). - New test_public_apply_snapshot_does_not_alias_input_msg pins the restored contract: a held msg.msg.yes is unaffected by subsequent deltas through the public apply_snapshot/apply_delta entry points.
a41f3e7 to
53393c3
Compare
|
Round-5 fix (53393c3): applied the bot's suggested design — keep the defensive
Blank-line nit is the fifth false positive; confirmed ruff clean. |
Review: perf(ws): adopt orderbook snapshot dicts by identity (#296)Overall this is a well-reasoned performance fix with solid test coverage. The two-tier design — identity adoption on the recv-loop hot path, defensive copy on the public API — is the right call. A few things worth addressing before merge. CorrectnessThe core optimization is correct. The Issues1. Redundant local imports in def test_public_apply_snapshot_does_not_alias_input_msg(self) -> None:
from decimal import Decimal # already top-of-file
from kalshi.ws.models.orderbook_delta import (
OrderbookDeltaMessage, OrderbookSnapshotMessage, # already top-of-file
)
from kalshi.ws.orderbook import OrderbookManager # already top-of-fileAll four symbols are imported at the module level ( 2. Missing blank line before The class has only one blank line separating it from the previous test method. PEP 8 and the rest of this file use two blank lines before top-level class definitions. Add the second blank line. 3. Aliasing behavior is a behavioral change — CHANGELOG omitted Raw Minor observation (no action needed)The Summary
|
Code Review — PR #306: perf(ws): adopt orderbook snapshot dicts by identityOverviewThis PR removes the redundant Correctness ✅The core logic is sound:
Issues1. Missing blank lines before The diff shows only one blank line between the end of the previous class and the new one. PEP 8 and ruff require two blank lines between top-level class definitions: assert len(book.no) == 1
# ← only one blank line here
class TestSnapshotIdentityAdoption:This will fail 2. Redundant inline imports in def test_public_apply_snapshot_does_not_alias_input_msg(self) -> None:
from decimal import Decimal
from kalshi.ws.models.orderbook_delta import (
OrderbookDeltaMessage,
OrderbookSnapshotMessage,
)
from kalshi.ws.orderbook import OrderbookManagerAll four of these are already available at module scope for the other tests in the same file. Inline imports suggest they were copy-pasted from a standalone script. Move them to the module-level imports to match the file's existing style and avoid the confusion of "why are these special here?" 3. Minor clarity nit in self._apply_snapshot_inplace(msg, sid=sid)
ticker = msg.msg.market_ticker
state = self._books[ticker]
state.yes = dict(state.yes) # state.yes IS msg.msg.yes at this point
state.no = dict(state.no)After 4. No CHANGELOG entry for the aliasing contract change The behavioral change — that raw What's Good
SummaryTwo items need fixing before merge: the missing blank line (will break ruff) and the redundant inline imports. The CHANGELOG gap is worth addressing. The core logic is correct and well-tested. |
Addresses round-6 review on #306: - Inline imports inside test_public_apply_snapshot_does_not_alias_input_msg are already module-level; drop them. - state.yes = dict(state.yes) reads as a self-copy; rewrite as dict(msg.msg.yes) to make "copy the caller's input back out" the visible intent. - Skipped blank-line nit (5th false positive; ruff clean for the sixth time). - CHANGELOG entry deferred to v2.6.0 release packaging (handled by orchestrator per the audit-closure plan, not in-PR per project convention).
|
Round-6 fix (5250428):
Ready for merge. |
Code Review — PR #306:
|
| Area | Verdict |
|---|---|
| Core correctness | ✅ Correct |
| Recv-loop perf win | ✅ Real and meaningful |
| Public API safety | ✅ Defensive copy preserves contract |
| Test coverage | ✅ Comprehensive |
| CHANGELOG | ❌ Behavioral change not documented |
| Style | Minor: missing blank line before test class |
| Docstrings | ✅ Aliasing contract clearly documented |
Recommend: Add a CHANGELOG entry calling out the aliasing behavior change for raw subscribe_orderbook_delta consumers before merging. Everything else is in good shape.
|
Round-7 review: zero new blocking items.
Merging. |
Closes #296.
What
_apply_snapshot_inplacewrappedmsg.msg.yes/msg.msg.noindict(...), silently nullifying the ~5× speedup CHANGELOG v2.5.0 #263 advertises. For a 200-level book that's 400 needless re-hashes and reallocs per snapshot on the recv hot path.Why it's safe
OrderbookSnapshotPayload._levels_to_dictalready produces a typeddict[Decimal, Decimal]. The_BookStatebecomes the canonical owner of the dict after_apply_snapshot_inplaceand mutates it in place on every delta.Subscriber-side aliasing: raw consumers of
subscribe_orderbook_deltawho receive anOrderbookSnapshotMessageand hold.msg.yes/.nowill observe future mutations — docstrings on bothsubscribe_orderbook_deltaandOrderbookSnapshotPayloaddocument this. Useclient.orderbook(ticker)when an immutable snapshot view is needed.Tests
New
TestSnapshotIdentityAdoptionpins:state.yes is msg.msg.yes(identity, no defensive copy)_apply_delta_inplacemutates the adopted dict in placestate.invalidate()still firesmsg.msg.yesobserves a subsequent delta (aliasing contract)Part of the post-v2.5 audit closure (Wave W1).