fix(replication): batch record prune audit challenges#170
Conversation
There was a problem hiding this comment.
Pull request overview
This PR reduces audit-responder load spikes during record pruning by batching prune-confirmation AuditChallenges per target peer (multi-key per peer) instead of sending one one-key challenge per (peer, key). It also improves observability and capacity behavior of the shared audit responder by reporting which admission ceiling caused drops and increasing the responder caps.
Changes:
- Batch record prune-confirmation audit challenges by target peer and verify multi-key digest responses per key.
- Add structured audit-responder admission failure reporting (reason + decision-time counters) and update logs accordingly.
- Increase audit responder concurrency caps (global and per-peer) and add tests for the new admission reporting.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/replication/pruning.rs | Coalesce prune audits into one multi-key challenge per peer and verify digest responses per key. |
| src/replication/mod.rs | Return detailed admission failure reasons/counters from audit responder admission and log them; add tests. |
| src/replication/config.rs | Increase global/per-peer audit responder concurrency limits. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let challenge_keys: Vec<XorName> = challenge_material.iter().map(|(key, _)| *key).collect(); | ||
| let (challenge_id, nonce) = { | ||
| let mut rng = rand::thread_rng(); | ||
| (rng.gen::<u64>(), rng.gen::<[u8; 32]>()) | ||
| }; | ||
| let (encoded, key_count) = encode_prune_audit_challenge(&peer, key, challenge_id, nonce)?; | ||
| let Some((encoded, key_count)) = | ||
| encode_prune_audit_challenge(&peer, &challenge_keys, challenge_id, nonce) | ||
| else { |
There was a problem hiding this comment.
Thanks. The latest version intentionally chunks prune-confirmation batches at ReplicationConfig::responsible_audit_key_limit(local_stored_key_count), i.e. the exact same sender-side limit used by normal responsible chunk audit. The receiver-side max_incoming_audit_keys is derived from that same named limit with the existing 2x skew margin. I don't think adding retry-with-smaller-batches is needed here: if a peer rejects a challenge at this size, it would also be rejecting a normal responsible-audit-sized challenge from this sender, and retrying would add more prune-audit traffic on the failure path.
| for key in &challenge_keys { | ||
| report_prune_audit_failure_once(&peer, key, p2p_node, config, report_state).await; | ||
| } |
There was a problem hiding this comment.
Fixed in d681533. On timeout/malformed response, the code now stops attempting failure reports after the first successful per-peer report, avoiding repeated responsibility checks for the rest of the batch.
| PruneAuditStatus::Failed => { | ||
| report_prune_audit_failure_once(&peer, &key, p2p_node, config, report_state).await; | ||
| } |
There was a problem hiding this comment.
Fixed in d681533. The decoded failed-status path now tracks whether an audit failure has already been reported for this peer/batch and skips further report attempts after the first successful one.
| let mut challenge_material = Vec::new(); | ||
| for key in keys { | ||
| if let Some(local_bytes) = local_record_bytes(&key, storage).await { | ||
| challenge_material.push((key, local_bytes)); | ||
| } |
There was a problem hiding this comment.
Fixed in d681533. The batch path now computes and stores only the expected 32-byte audit digest per key before sending the challenge, rather than retaining full local record bytes for the whole batch.
Summary
AuditChallenges by target peer to avoid same-peer one-key request bursts.Why
Production logs showed responsible audit replies dropped with the old opaque message:
The drops were concentrated in same-source, same-millisecond bursts. That shape matched the per-peer audit responder cap and can be produced by prune-confirmation audit traffic, which previously sent one single-key
AuditChallengeper(peer, key)concurrently.Key changes
admit_audit_respondernow reports structured admission failures:global_pool_fullper_peer_cap_fullMAX_CONCURRENT_AUDIT_RESPONSESis doubled from16to32.MAX_AUDIT_RESPONSES_PER_PEERis doubled from2to4.ReplicationConfig::responsible_audit_key_limit(local_stored_key_count).responsible_audit_key_limit(...), making prune-confirmation use the same sender-side limit literally.max_incoming_audit_keys(...)is now explicitly derived fromresponsible_audit_key_limit(...)with the existing 2x margin for challenger/receiver store-size skew.Validation
Ran locally:
Results:
cargo fmt --allpassed.cargo check -qpassed.git diff --checkpassed.Follow-up: clippy/Copilot review handling
src/replication/mod.rs.Follow-up validation after commit
d681533: