perf(ws): orderbook materialization via model_construct + apply_snapshot single-copy + zero-delta cache preservation#366
Conversation
…a cache #327: `_BookState.to_orderbook` materialized levels via the validating `OrderbookLevel(...)` / `Orderbook(...)` constructors, re-running the per-level `DollarDecimal` / `FixedPointCount` validators on data that is already SDK-canonical (`Decimal` price -> `Decimal` quantity, sourced from `_levels_to_dict` or arithmetic on already-validated Decimals). Switching to `model_construct` for both the per-level and outer model removes 2N+1 Pydantic validations on every cache miss while preserving the wire-order contract via the existing `sorted(...)` walk. #344: public `OrderbookManager.apply_snapshot` previously routed through `_apply_snapshot_inplace` (which identity-adopts `msg.msg.yes/no` into the new `_BookState`) and then overwrote `state.yes` / `state.no` with `dict(msg.msg.yes)` / `dict(msg.msg.no)` to enforce the defensive-copy contract — two `_BookState.__setattr__` round-trips per side for one useful copy. Extracted the shared bookkeeping into `_install_snapshot` so the public path can install copies directly while the recv-loop `_apply_snapshot_inplace` keeps the v2.6 #296 identity-adoption perf win unchanged. #347: `_apply_delta_inplace` invalidated the #244 materialization cache unconditionally whenever the level already existed, including the `delta=0` case where the price-level dict is identical before and after. The cache invalidation now only fires when the dict actually mutates (`new_qty != existing_qty` or the level is deleted at zero/ negative new quantity), so `get()` keeps returning the same `Orderbook` instance across no-op deltas — which is what the #244 cache was designed to do. Closes #327, #344, #347
Code ReviewOverall: Well-scoped, well-documented set of hot-path fixes. The three issues are addressed surgically, tests are thorough, and the docstrings explain the why clearly. A few findings below. #327 —
|
| Area | Finding | Severity |
|---|---|---|
model_construct assumption |
NullableList validator bypassed — safe today, fragile under future changes |
Low |
Test monkeypatch usage |
Fixture declared but unused; manual try/finally pattern is confusing |
Low |
| Test imports | Module-level preferred over per-function | Nitpick |
_install_snapshot refactor |
Clean, no issues | ✅ |
| Zero-delta logic (#347) | Correct, well-tested | ✅ |
| Single-copy snapshot (#344) | Correct | ✅ |
The core logic and test coverage are solid. The monkeypatch fixture issue is the only concrete thing I'd fix before merge; the rest are minor polish.
|
Round-2 review addressed in d215125: Addressed:
Deferred:
Verification:
|
Round-3 ReviewRound-2 feedback correctly addressed. Both items are resolved cleanly:
Production code — no new issues
One observation on the
|
| Area | Status |
|---|---|
| Round-2 fixes (monkeypatch, imports) | ✅ Addressed |
_install_snapshot refactor (#344) |
✅ Correct |
| Zero-delta cache preservation (#347) | ✅ Correct, well-tested |
model_construct bypass (#327) |
✅ Correct; NullableList guard still deferred |
| mypy pre-existing errors |
No blocking issues with the code or test changes. Ready to merge pending mypy CI confirmation.
Summary
Three correctness/perf fixes on the
OrderbookManagerhot path. Nonechange observable wire behavior: returned
Orderbooksnapshots have thesame shape, the defensive-copy contract on public
apply_snapshotispreserved, and zero-delta messages still round-trip through
apply_delta. What changes is the cost per call and — for #347 — theidentity-stability of the
#244cached view across no-op deltas.Issues closed
_BookState.to_orderbookskips re-validation viamodel_constructapply_snapshotdefensively copies each side dict exactly once_apply_delta_inplaceinvalidates the cached Orderbook view even when delta=0 (no-op mutation) #347 — zero-effect deltas preserve the#244materialization cacheBehavioral changes
OrderbookManager.get(ticker)andapply_delta(...)now return thesame
Orderbookinstance after a delta that nets to no change(e.g.
delta=0at an existing level). Previously the cache wasinvalidated and a fresh equal-value instance was returned. Value
semantics are unchanged.
Tests
tests/ws/test_orderbook.py::TestWave2CorrectnessRegressionstest_issue_327_to_orderbook_no_revalidation— spies on thevalidating
__init__ofOrderbookLevelandOrderbook; assertszero calls across snapshot + delta materialization.
test_issue_344_apply_snapshot_public_single_copy— spies on_BookState.__setattr__and asserts each ofyes/noisassigned exactly once per public
apply_snapshot.test_issue_347_zero_delta_preserves_cache— populates the#244cache, applies
delta=0at an existing level, asserts cacheidentity is preserved across the no-op and that a subsequent
non-zero delta still invalidates (control).
test_issue_347_zero_delta_at_missing_level_preserves_cache—codifies that
delta<=0at a non-existent level is a no-op.Source
Round-3 independent audit closure plan, wave W2.