From ab491d11c6f2bc8a1212fa3b602afbc16a1a3873 Mon Sep 17 00:00:00 2001 From: floor-licker Date: Mon, 22 Jun 2026 21:09:23 -0400 Subject: [PATCH] docs: clarify websocket snapshot ordering --- README.md | 2 ++ src/book.rs | 9 ++++++--- src/types.rs | 10 ++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d60e73d..9bd7e5a 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ Run the WS hot-path benchmark locally with `cargo bench --bench ws_hot_path`. **Parsing paths:** `polyfill-rs` keeps two parsing layers on purpose. The allocation-sensitive WS `book` path uses `WsBookUpdateProcessor` in `src/ws_hot_path.rs`, which walks a reusable `simd-json` tape and applies fixed-point book levels directly. The generic stream parser in `src/decode.rs` is an ergonomic compatibility path: it parses through `serde_json::Value` so it can tolerate batches, unknown event types, and mixed message shapes. Likewise, several generic numeric/decimal deserializers in `src/decode.rs` accept string-or-number API fields through `serde_json::Value`; they are not the zero-allocation hot path. +**WebSocket snapshot ordering:** Polymarket `book` messages expose a millisecond `timestamp` and optional `hash`, but no monotonic server sequence/version. The book applier treats newer timestamps as newer, rejects older timestamps, suppresses exact same-timestamp/same-hash duplicates, and accepts same-timestamp/different-hash snapshots in websocket arrival order. The hash distinguishes duplicate vs distinct state; it is not a logical ordering key. + **Key Performance Optimizations:** The 21.4% performance improvement comes from HTTP/2 tuning with 512KB stream windows optimized for 469KB payloads, explicit Polymarket request headers, SIMD-backed parsing where the client uses the typed fast-response helper for large REST market responses, and opt-in connection prewarming/keep-alive support. diff --git a/src/book.rs b/src/book.rs index 3fa6030..5c9da00 100644 --- a/src/book.rs +++ b/src/book.rs @@ -601,9 +601,10 @@ impl OrderBook { return Err(PolyfillError::validation("Token ID mismatch")); } - // Use the exchange-provided timestamp as the monotonic snapshot marker. - // If the feed emits two snapshots in the same millisecond, a different hash is treated as - // a distinct newer state. Equal timestamp without a hash is considered duplicate/stale. + // Use the exchange-provided timestamp as the primary snapshot marker. The WS `book` + // message does not expose a monotonic server sequence/version, so same-millisecond + // snapshots with different hashes are ordered by websocket arrival order. The hash + // distinguishes duplicate vs distinct state; it is not a logical ordering key. // Snapshot timestamps are intentionally separate from legacy delta sequence numbers. if !self.should_apply_snapshot(update.timestamp, update.hash.as_deref()) { return Ok(()); @@ -725,6 +726,8 @@ impl OrderBook { #[inline] fn should_apply_snapshot(&self, timestamp: u64, hash: Option<&str>) -> bool { + // Without a server sequence/version, equal-timestamp distinct hashes can only be ordered + // by arrival. Older timestamps are always stale; exact same timestamp/hash is duplicate. if timestamp > self.last_snapshot_timestamp_ms { return true; } diff --git a/src/types.rs b/src/types.rs index c3f4da8..bee884b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -976,16 +976,26 @@ pub enum StreamMessage { } /// Orderbook update message (full snapshot or delta). +/// +/// WebSocket `book` messages expose a millisecond timestamp and optional book hash, but no +/// monotonic server sequence/version. Same-timestamp messages with different hashes are therefore +/// ordered by websocket arrival order by the book applier; the hash is a duplicate/state +/// discriminator, not a logical ordering key. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BookUpdate { pub asset_id: String, pub market: String, + /// Exchange-provided snapshot timestamp in milliseconds. #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")] pub timestamp: u64, #[serde(deserialize_with = "crate::decode::deserializers::vec_from_null")] pub bids: Vec, #[serde(deserialize_with = "crate::decode::deserializers::vec_from_null")] pub asks: Vec, + /// Exchange-provided snapshot hash. + /// + /// Used to suppress exact duplicate same-timestamp snapshots and to allow distinct + /// same-timestamp states. It does not encode ordering. #[serde(default)] pub hash: Option, }