|
| 1 | +# Batched RIB reclamation and BMP replay safety |
| 2 | + |
| 3 | +## Problem and change |
| 4 | + |
| 5 | +Reclaiming retired ingress IDs previously traversed the complete table for |
| 6 | +each ID while holding the withdrawal mutex across the batch. Large retirement |
| 7 | +batches could delay unrelated withdrawals and reclamation. Delayed BMP replay |
| 8 | +also depended on mutable registrations that could disappear before queued |
| 9 | +updates were emitted. |
| 10 | + |
| 11 | +The replacement scans prefix keys once for a batch of permanently retired IDs. |
| 12 | +It deletes matching records under each prefix's mutation lock without cloning |
| 13 | +path attributes. Netom releases its withdrawal lock after each prefix, drops |
| 14 | +all epoch guards before scheduling pauses, and pauses for 1 ms every 512 prefixes. |
| 15 | +This is cooperative pacing, not a hard real-time latency guarantee. A single |
| 16 | +expensive prefix operation or an unrelated session reset can still take longer. |
| 17 | + |
| 18 | +Reusable-session reset remains synchronous and distinct from permanent |
| 19 | +retirement. It must finish before that session's fresh routes are accepted. |
| 20 | +The store fixes concurrent existence-bit/counter changes and serializes bitmap |
| 21 | +writes internally. Reannouncements no longer increment the total record gauge. |
| 22 | + |
| 23 | +## Ownership and ordering invariants |
| 24 | + |
| 25 | +- A retirement writer fence drains admitted RIB writes before unlinking an |
| 26 | + eligible registration. Later writes to that ID are rejected. Numerical |
| 27 | + retired-ID tombstones carry no metadata or route references. IDs never wrap |
| 28 | + and get reused within the same process. |
| 29 | +- Reconnect claims retain their existing atomic registry checks. A claimed live |
| 30 | + session is excluded from GC. Whole-session resets preserve existing ordering; |
| 31 | + family-specific reset behavior is unchanged. |
| 32 | +- Pending cleanup tickets remain owned by the RIB until every store succeeds. |
| 33 | + Retired routes are marked withdrawn in batches before physical removal. A |
| 34 | + failed pass keeps its ticket and can restart idempotently. Progress is not |
| 35 | + persisted across process restart because the RIB itself is memory-only. |
| 36 | +- Per-prefix insert/delete locks cover record mutation, existence bits and |
| 37 | + counters. Empty-to-nonempty reinsertion restores both sets of store counters. |
| 38 | + Physical removal rejects persistent stores rather than silently leaving a |
| 39 | + disk copy that might reappear. |
| 40 | +- BMP dump-buffer entries own immutable snapshots of their original ingress, |
| 41 | + parent peer and router metadata. Shared snapshots use weak cache entries; |
| 42 | + they do not own the RIB, client, input handler or mutable registry. |
| 43 | +- Replay looks up metadata only in its owned snapshot, never in the current |
| 44 | + registration. Session generation changes invalidate cached output headers; |
| 45 | + an older generation cannot reopen a peer after a newer one was emitted. |
| 46 | + The per-client output mutex orders cache transitions and message enqueueing. |
| 47 | +- A Peer Down uses the header previously emitted to that consumer when available. |
| 48 | + Missing identity causes a reconnect, never an unspecified/default header. |
| 49 | + Child-to-parent/path mapping and raw-forwarding coverage are invalidated at |
| 50 | + their corresponding lifecycle boundaries. |
| 51 | +- On writer disconnect the client is removed and its dump buffer is discarded |
| 52 | + immediately. Failed/expired dump walks do not emit successful End-of-RIB |
| 53 | + completion. Existing bounded queues, write timeouts and nonblocking live |
| 54 | + sends remain in effect. |
| 55 | + |
| 56 | +## Costs and limits |
| 57 | + |
| 58 | +Prefix-key snapshots use O(prefix count) memory, not O(route variants); they are |
| 59 | +not a strict constant-memory cursor. Deletion target sets use O(retired IDs). |
| 60 | +The numerical retired-ID bitmap grows with lifetime churn, but does not pin |
| 61 | +session metadata or route attributes. A future change to recycle IDs would |
| 62 | +require a different lifetime scheme, not just removing the overflow check. |
| 63 | + |
| 64 | +Mutation stripes add an uncontended lock to insertions. Reclamation yields |
| 65 | +reduce its throughput in exchange for responsiveness. Reconnect reset and |
| 66 | +withdrawn-attribute compaction can still hold the withdrawal lock for a scan; |
| 67 | +this patch does not make those operations asynchronous. |
| 68 | + |
| 69 | +Replay metadata is conservatively charged to each queued event, even when |
| 70 | +several events share one allocation. Consumers may reach the configured byte |
| 71 | +cap sooner and reconnect. Output generation history and snapshot lookup caches |
| 72 | +are bounded; a consumer exceeding generation-history capacity requests a fresh |
| 73 | +dump rather than forgetting the ordering fence. Already active dump peers can |
| 74 | +exceed the nominal history cap; new identities trigger a reconnect at the cap. |
| 75 | + |
| 76 | +Missing metadata before an event reaches the restreamer's buffering boundary |
| 77 | +still requires a consumer reconnect. This patch does not provide a durable |
| 78 | +archive of all upstream events. It protects owned queued snapshots and rejects |
| 79 | +unsafe identity reconstruction. |
| 80 | + |
| 81 | +## Monitoring |
| 82 | + |
| 83 | +The `/metrics` endpoint exports: |
| 84 | + |
| 85 | +- `netom_rib_gc_pending_ids_total`: outstanding retirement tickets (gauge). |
| 86 | +- `netom_rib_gc_prefixes_scanned_total`: cumulative scanned prefixes. |
| 87 | +- `netom_rib_gc_records_removed_total`: cumulative physically removed records. |
| 88 | +- `netom_rib_gc_completed_batches_total`: successful passes. |
| 89 | +- `netom_rib_gc_failed_batches_total`: failed passes kept for retry. |
| 90 | + |
| 91 | +Progress is logged approximately every five seconds during a long pass. |
| 92 | +Check that the backlog drains while ingestion counters continue advancing; |
| 93 | +watch reconnect frequency, BMP receive queues, RSS and process CPU as well. |
| 94 | +A lower CPU number alone is not evidence of correct reclamation. |
| 95 | + |
| 96 | +## Validation and deployment |
| 97 | + |
| 98 | +Regression coverage includes batch/default-route removal across both families, |
| 99 | +concurrent insertion/deletion, bitmap writers, duplicate announcements, a |
| 100 | +retirement admission barrier, owned route snapshots, deferred and partially |
| 101 | +failed cleanup, replay after registry deletion and same-ID reconnect, rejection |
| 102 | +of old generations, and missing Peer Down identity. |
| 103 | + |
| 104 | +Manual performance case in netom-store: |
| 105 | + |
| 106 | +```sh |
| 107 | +cargo test --release --test remove_mui batch_reclamation_workload -- --ignored --nocapture |
| 108 | +``` |
| 109 | + |
| 110 | +For 2,048 prefixes and 128 IDs, repeated single-ID calls took approximately |
| 111 | +112 ms versus 6.7 ms for one batch on the development machine. This is a small |
| 112 | +algorithm comparison, not a production-table latency or memory guarantee. |
| 113 | + |
| 114 | +Before production rollout, replay representative traffic on an isolated |
| 115 | +instance and compare route sets, BMP identities, update latency and memory. |
| 116 | +The library commit must be pushed before publishing a Netom commit that pins |
| 117 | +it. Deploy by restarting Netom with the new binary; its existing in-process |
| 118 | +collector cannot be replaced or safely killed independently. A restart needs |
| 119 | +an upstream replay and downstream redump. Roll back the binary and restart if |
| 120 | +route/peer identity, progress or latency checks regress. No production service |
| 121 | +was changed during development. |
| 122 | + |
| 123 | +### Development verification (2026-10-22) |
| 124 | + |
| 125 | +- `cargo test --locked --offline --lib`: 364 passed, 31 existing ignored, |
| 126 | + zero failures, using the pinned store revision. |
| 127 | +- Full `netom-store` unit/integration/doc suite: passed; the manual benchmark |
| 128 | + remains ignored by default. |
| 129 | +- `scripts/e2e-addpath-bmp.sh`: passed for raw and rebuilt output, unicast and |
| 130 | + FlowSpec path IDs, reconnect dumps, and exactly-once Peer Down. |
| 131 | +- `CHURN=5000 scripts/addpath-churn.sh`: passed; retired child entries and route |
| 132 | + records returned to zero with the live parent/session retained. |
| 133 | +- `python3 scripts/bmp-peer-churn.py --binary target/debug/netom --cycles 20`: |
| 134 | + all eight scenarios passed, including silent and partial-frame recovery. |
| 135 | +- Changed Rust files pass rustfmt; both repositories pass `git diff --check`. |
| 136 | + |
| 137 | +No production-scale canary or deployment has been performed. The initial |
| 138 | +sandboxed suite was unable to bind test sockets; the results above are from |
| 139 | +rerunning with loopback access. |
| 140 | + |
| 141 | +### PR preparation verification (2026-11-09) |
| 142 | + |
| 143 | +The patch excludes the separate BMP automatic-reconciliation feature. The store dependency is pinned to published |
| 144 | +commit `1a58fe44`, proposed against the consumed `fnm-gc` branch in |
| 145 | +[netom-store PR #2](https://github.com/FastNetMon/netom-store/pull/2). |
| 146 | + |
| 147 | +- Netom library tests: 353 passed, 31 existing ignored. |
| 148 | +- Store default suite: 31 passed; full feature suite: 32 passed. Each retains |
| 149 | + one ignored manual performance test. |
| 150 | +- BMP ADD-PATH live/rebuilt forwarding, FlowSpec, reconnect dump and exactly-once |
| 151 | + Peer Down checks passed against the release build. |
| 152 | +- The 5,000-path churn regression reclaimed every withdrawn child and record. |
| 153 | +- Build-source checks, release metadata tests, changed-file rustfmt checks and |
| 154 | + `git diff --check` passed. Store Clippy passes with warnings denied. |
| 155 | + |
| 156 | +These are development checks. Steady-state memory and deployment-scale replay remain rollout checks. |
0 commit comments