Skip to main content

sample_app/
sample_app.rs

1//! Sample app: a miniature order-management gateway built on
2//! `subms-cuckoo-filter`, then a tour of each optional feature. Run the base
3//! with `cargo run --example sample_app`; add `--all-features` (or a subset
4//! like `--features dynamic`) to see the feature sections light up.
5//!
6//! * base                 - an OMS live-order set driven by a drop-copy event stream
7//! * base                 - checkpoint, shard fan-in and session roll on the same set
8//! * variable-fingerprint - widen the fingerprint to cut false positives on a risk pre-check
9//! * dynamic              - an intraday dedup window that grows past its initial sizing
10//! * concurrent-reads     - a reader fleet fanning out over a frozen snapshot
11//! * compressed-buckets   - a smaller serialized footprint at moderate load
12
13use subms_cuckoo_filter::CuckooFilter;
14
15/// One line off a drop-copy stream. The gateway only needs to know whether an
16/// id is live, so a fingerprint set stands in for the order table.
17enum Event<'a> {
18    New(&'a str),
19    Fill(&'a str),
20    Cancel(&'a str),
21}
22
23fn main() {
24    let mut open = oms_gateway();
25    checkpoint_and_restore(&open);
26    shard_fan_in();
27    session_roll(&mut open);
28
29    #[cfg(feature = "variable-fingerprint")]
30    variable_fingerprint_risk_precheck();
31
32    #[cfg(feature = "dynamic")]
33    dynamic_dedup_window();
34
35    #[cfg(feature = "concurrent-reads")]
36    concurrent_reads_market_fanout();
37
38    #[cfg(feature = "compressed-buckets")]
39    compressed_persistence();
40}
41
42/// The system: an order gateway replays a drop-copy stream into a live-order
43/// set. Every inbound amend is gated on `contains` before it costs an
44/// authoritative book lookup; a fill or cancel `delete`s the id. The delete is
45/// the move a bloom filter cannot make, and without it the set would grow all
46/// session and every closed order would keep answering yes.
47fn oms_gateway() -> CuckooFilter {
48    println!("== OMS gateway: live-order set from a drop-copy stream ==");
49    let stream = [
50        Event::New("ORD-1001"),
51        Event::New("ORD-1002"),
52        Event::New("ORD-1003"),
53        Event::New("ORD-1004"),
54        Event::Fill("ORD-1002"),
55        Event::New("ORD-1005"),
56        Event::Cancel("ORD-1003"),
57        Event::Fill("ORD-1005"),
58        Event::New("ORD-1004"), // the session resend replays one we already hold
59    ];
60
61    let mut open = CuckooFilter::with_capacity(10_000);
62    let (mut opened, mut replayed, mut closed) = (0u32, 0u32, 0u32);
63    for event in &stream {
64        match event {
65            // insert_if_absent makes the replay idempotent: a resent NEW for a
66            // live order must not add a second fingerprint.
67            Event::New(id) => {
68                if open.insert_if_absent(id) {
69                    opened += 1;
70                } else {
71                    replayed += 1;
72                }
73            }
74            Event::Fill(id) | Event::Cancel(id) => {
75                if open.delete(id) {
76                    closed += 1;
77                }
78            }
79        }
80    }
81
82    println!(
83        "  {opened} new, {replayed} replayed, {closed} closed -> {} live",
84        open.len()
85    );
86    println!(
87        "  load {:.4}, false-positive rate {:.6}",
88        open.load_factor(),
89        open.estimated_fpp()
90    );
91
92    let amend = "ORD-1002";
93    println!(
94        "  amend for {amend} -> {}",
95        if open.contains(amend) {
96            "book lookup"
97        } else {
98            "reject, already closed"
99        }
100    );
101
102    assert_eq!(open.len(), 2);
103    assert!(!open.contains("ORD-1002"), "a filled order leaves the set");
104    assert!(
105        open.contains("ORD-1001"),
106        "no false negative on a live order"
107    );
108    open
109}
110
111/// Checkpoint the live set to bytes and reload it. A gateway restarting
112/// mid-session rebuilds membership from the last checkpoint instead of
113/// replaying the whole day's drop copy.
114fn checkpoint_and_restore(open: &CuckooFilter) {
115    println!("\n== checkpoint: serialise the live set and reload it ==");
116    let mut buf = Vec::new();
117    open.write_to(&mut buf).expect("in-memory write");
118    let restored = CuckooFilter::parse(&buf).expect("round trip");
119    println!(
120        "  {} bytes on the wire, {} live orders restored",
121        buf.len(),
122        restored.len()
123    );
124    assert!(restored.contains("ORD-1001"));
125    assert_eq!(restored.len(), open.len());
126}
127
128/// Fan-in: two gateway shards each hold their own live-order set, and the
129/// surveillance process merges them into one. `union` re-places every
130/// fingerprint rather than OR-ing bit arrays, so both filters must share a
131/// geometry - which is why both are built with the same capacity.
132fn shard_fan_in() {
133    println!("\n== fan-in: merge two shards' live-order sets ==");
134    let mut shard_a = CuckooFilter::with_capacity(10_000);
135    let mut shard_b = CuckooFilter::with_capacity(10_000);
136    for i in 0..500u32 {
137        shard_a.insert(&format!("A-ORD-{i}"));
138        shard_b.insert(&format!("B-ORD-{i}"));
139    }
140    shard_a.union(&shard_b).expect("same geometry");
141    println!("  merged set holds {} orders", shard_a.len());
142    assert!(shard_a.contains("A-ORD-7"));
143    assert!(shard_a.contains("B-ORD-7"));
144
145    let mismatched = CuckooFilter::with_capacity(1_000_000);
146    println!(
147        "  merging a differently-sized shard -> {:?}",
148        shard_a.union(&mismatched)
149    );
150}
151
152/// Session roll: `clear` zeroes the set at the close and keeps the allocation,
153/// so tomorrow's first order does not pay for a fresh 16 KB array.
154fn session_roll(open: &mut CuckooFilter) {
155    println!("\n== session roll: clear and reuse the allocation ==");
156    let bytes = open.size_in_bytes();
157    open.clear();
158    println!(
159        "  after close: {} live, {} bytes still held",
160        open.len(),
161        bytes
162    );
163    assert!(open.is_empty());
164    assert!(!open.contains("ORD-1001"));
165}
166
167/// `variable-fingerprint` feature: a false positive on this pre-check fires a
168/// costly authoritative risk lookup for a symbol that was never restricted.
169/// Widening the fingerprint from 8 to 16 bits shrinks that rate by orders of
170/// magnitude, paying an extra byte per slot.
171#[cfg(feature = "variable-fingerprint")]
172fn variable_fingerprint_risk_precheck() {
173    use subms_cuckoo_filter::{FingerprintWidth, VariableFpCuckooFilter};
174    println!("\n== variable-fingerprint: cut false positives on a risk pre-check ==");
175    let n = 5_000usize;
176    let mut narrow = VariableFpCuckooFilter::new(n, FingerprintWidth::Eight);
177    let mut wide = VariableFpCuckooFilter::new(n, FingerprintWidth::Sixteen);
178    for i in 0..n {
179        narrow.insert(&format!("RESTRICTED-{i}"));
180        wide.insert(&format!("RESTRICTED-{i}"));
181    }
182    let (mut narrow_fp, mut wide_fp) = (0usize, 0usize);
183    for i in 0..10_000usize {
184        let sym = format!("TRADABLE-{i}");
185        if narrow.contains(&sym) {
186            narrow_fp += 1;
187        }
188        if wide.contains(&sym) {
189            wide_fp += 1;
190        }
191    }
192    println!("  8-bit false positives:  {narrow_fp}");
193    println!("  16-bit false positives: {wide_fp}");
194    assert!(
195        wide_fp < narrow_fp,
196        "wider fingerprint lowers the false-positive rate"
197    );
198}
199
200/// `dynamic` feature: a session dedup window for inbound message IDs whose
201/// volume is not known when the day opens. The base filter rejects inserts at
202/// saturation; the dynamic variant chains a fresh layer as load climbs, so a
203/// late-session ID is never dropped.
204#[cfg(feature = "dynamic")]
205fn dynamic_dedup_window() {
206    use subms_cuckoo_filter::DynamicCuckooFilter;
207    println!("\n== dynamic: an intraday dedup window that grows itself ==");
208    let mut seen = DynamicCuckooFilter::with_threshold(1_000, 0.5);
209    for i in 0..20_000u32 {
210        seen.insert(&format!("MSG-{i}"));
211    }
212    println!(
213        "  20k ids -> {} layers, active load {:.2}",
214        seen.layer_count(),
215        seen.load_factor()
216    );
217    assert!(
218        seen.layer_count() > 1,
219        "the window grew past its initial sizing"
220    );
221    for i in 0..20_000u32 {
222        assert!(
223            seen.contains(&format!("MSG-{i}")),
224            "no id dropped as the window grew"
225        );
226    }
227}
228
229/// `concurrent-reads` feature: the matching engine is the single writer; a
230/// fleet of pricing/risk readers fans out lock-free over a frozen
231/// `Arc<CuckooSnapshot>` of the open-order set. The snapshot is an eager copy,
232/// so writes after the capture never disturb a reader mid-scan.
233#[cfg(feature = "concurrent-reads")]
234fn concurrent_reads_market_fanout() {
235    use std::sync::Arc;
236    use subms_cuckoo_filter::CuckooSnapshot;
237    println!("\n== concurrent-reads: a reader fleet over a frozen open-order set ==");
238    let mut open = CuckooFilter::with_capacity(10_000);
239    for i in 0..1_000u32 {
240        open.insert(&format!("ORD-{i}"));
241    }
242    let snap = CuckooSnapshot::capture(&open);
243
244    // The writer keeps mutating after the snapshot is frozen.
245    open.insert("ORD-LATE");
246    open.delete("ORD-0");
247
248    let mut handles = Vec::new();
249    for _ in 0..4 {
250        let s = Arc::clone(&snap);
251        handles.push(std::thread::spawn(move || {
252            (0..1_000u32)
253                .filter(|i| s.contains(&format!("ORD-{i}")))
254                .count()
255        }));
256    }
257    for h in handles {
258        assert_eq!(
259            h.join().unwrap(),
260            1_000,
261            "every reader sees the whole frozen set"
262        );
263    }
264    println!("  4 readers each matched all 1000 orders in the snapshot");
265    assert!(
266        snap.contains("ORD-0"),
267        "snapshot keeps its pre-freeze state"
268    );
269    assert!(
270        !snap.contains("ORD-LATE"),
271        "snapshot does not see the writer's later insert"
272    );
273}
274
275/// `compressed-buckets` feature: persisting or replicating the filter. The
276/// sorted-run encoding stores only the live fingerprints plus a count byte, so
277/// the serialized footprint at the low-to-moderate load where filters actually
278/// run beats the base filter's fixed four-slot buckets.
279#[cfg(feature = "compressed-buckets")]
280fn compressed_persistence() {
281    use subms_cuckoo_filter::CompressedCuckooFilter;
282    println!("\n== compressed-buckets: smaller serialized footprint at moderate load ==");
283    let mut cf = CompressedCuckooFilter::with_capacity(10_000);
284    for i in 0..3_000u32 {
285        cf.insert(&format!("ORD-{i}"));
286    }
287    let base_fixed_bytes = cf.bucket_count() * 4; // base layout: 4 slot bytes per bucket
288    let mut buf = Vec::new();
289    cf.write_to(&mut buf).expect("in-memory write");
290    println!(
291        "  serialised {} bytes, base fixed-array layout would be {}",
292        buf.len(),
293        base_fixed_bytes + 17
294    );
295    let reloaded = CompressedCuckooFilter::parse(&buf).expect("round trip");
296    assert_eq!(reloaded.len(), cf.len());
297    assert!(
298        cf.occupied_bytes() < base_fixed_bytes,
299        "sorted-run encoding wins at moderate load"
300    );
301    for i in 0..3_000u32 {
302        assert!(cf.contains(&format!("ORD-{i}")));
303    }
304    assert!(
305        cf.delete("ORD-0"),
306        "delete still works on the compressed layout"
307    );
308}