numinnex commented on code in PR #4073:
URL: https://github.com/apache/iggy/pull/4073#discussion_r3949102908
##########
core/consensus/src/plane_helpers.rs:
##########
@@ -446,18 +456,35 @@ where
P: Pipeline<Entry = PipelineEntry>,
{
let commit = consensus.commit_max();
+ let commit_min = consensus.commit_min();
+ let replica = consensus.replica();
let mut drained = Vec::new();
consensus.with_pipeline_mut(|pipeline| {
+ let mut next = commit_min + 1;
while let Some(head_op) = pipeline.head().map(|entry| entry.header.op)
{
if head_op > commit {
break;
}
+ if head_op != next {
Review Comment:
`head_op != next` also matches `head_op <= commit_min`, and that arm is
terminal. `commit_journal` walks to `commit_max` (`metadata.rs:3645`,
`iggy_partition.rs:3620`), applies this op from the journal, and
`advance_commit_min` steps past it. The only popper is `pop_committed_prepare`,
whose sole caller (`metadata.rs:2843`) sits inside the loop this hold gates, so
nothing ever pops it: prepare slots leak while `is_caught_up_primary` still
reports caught up. Pre-PR this interleaving hit the assert at `impls.rs:1608`.
Suggest pop-and-discard when `head_op <= commit_min`, holding only when
`head_op > commit_min + 1`.
##########
core/consensus/src/plane_helpers.rs:
##########
@@ -482,15 +510,33 @@ where
/// revalidates that the head is still this exact entry before popping and
/// applying it. A driver dropped at an await strands nothing; a sibling driver
/// that committed the op first fails the caller's revalidation and re-peeks.
+///
+/// Bounded below for the reason [`drain_committable_prefix`] is, and reported
+/// rather than asserted for the same one. Holding is safe: a shard pump's
panic is
+/// swallowed by `compio::runtime::spawn`, while `tick_metadata` re-arms
repair on
+/// the level.
pub fn peek_committable_head<B, P>(consensus: &VsrConsensus<B, P>) ->
Option<PrepareHeader>
where
B: MessageBus,
P: Pipeline<Entry = PipelineEntry>,
{
let commit = consensus.commit_max();
- consensus
+ let next = consensus.commit_min() + 1;
+ let head = consensus
.pipeline_head_header()
- .filter(|header| header.op <= commit)
+ .filter(|header| header.op <= commit)?;
+ if head.op != next {
Review Comment:
Same conflation as the drain site, and harder here: `peek_committable_head`
cannot pop, so once `head.op <= commit_min` this returns `None` permanently and
`commit_committable_prefix` never runs again. Wire replies exist only on that
path (`metadata.rs:2815-2967`), so committed metadata ops are applied by the
journal walk and never answered, and each entry's `reply_sender`
(`impls.rs:198`) is neither taken nor dropped, so in-process awaiters get no
`Canceled` either. The pop needs to be a separate call the caller makes before
peeking.
##########
core/shard/src/lib.rs:
##########
@@ -4972,6 +4975,9 @@ where
if !in_scope {
return;
}
+ // The serving peer is answering. Clears the stall clock and the
budget
+ // so a window served in chunks cannot rotate off a healthy stream.
+ self.note_metadata_repair_progress();
Review Comment:
This sits above the divergence return (`:5000`) and
`verify_prepare_integrity` (`:5011`); master had it below both so that only
silence aged the stream. A peer serving forked or corrupt in-window frames now
zeroes `idle_ticks`, so `retry_stalled_metadata_repair` never reaches its stall
path, `burn_metadata_repair_attempt` is never called at all, and
`REPAIR_MAX_STALL_RETRIES` is unreachable. Rotation is not merely reset here,
it cannot run. On the `disagrees` path this is deterministic rather than
transient. Suggest restoring master's position below both returns.
##########
core/shard/src/lib.rs:
##########
@@ -5499,9 +5541,12 @@ where
// from the evicted ring or the flushed segments.
let missing = {
let journal = partition.log.journal();
- first_op_not_covered(&pending, consensus.commit_min(), |op| {
- journal.inner.header_by_op(op)
- })
+ first_op_not_covered(
+ &pending,
+ consensus.commit_min(),
+ consensus.commit_min(),
Review Comment:
This lowers the partition floor from `max(pending.commit_max, commit_min+1)`
to `commit_min+1` unconditionally, and that range is one eviction has already
cleared: `flush_committed_messages` evicts `committed_prefix(commit_max)`
(`iggy_partition.rs:4029`, `:4243`) while `header_by_op` reads the resident vec
only. A primary-elect then parks on a committed, durable op with no fill path,
since this site arms no repair and `group_is_gap_stopped` requires
`probe.normal`. The comment at `:5531-5541` names this exact hazard. Fix needs
both halves: probe `repair_headers_in(floor..=pending.op_head)`, and pass
`repair_retained_from()` minus one as `repair_floor` (the evicted ring is
capped at 4096, so a ring-aware probe alone still parks).
##########
core/consensus/src/plane_helpers.rs:
##########
@@ -435,8 +435,18 @@ where
/// Drain and return committable prepares from the pipeline head.
///
-/// Entries are drained only from the head and only while their op is covered
-/// by the current commit frontier.
+/// Entries are drained from the head, while covered by the commit frontier,
and
+/// only as a contiguous run starting at the next op owed to the state machine.
+///
+/// Callers `advance_commit_min` per entry, so a run starting above
+/// `commit_min + 1` or breaking partway hits that counter's sequential-advance
+/// assert. Pipeline-side twin of `commit_journal`'s gap-stop.
+///
+/// Reported, not asserted: a promoted partition primary reaches it
legitimately,
+/// `RebuildPipeline` seeding above `commit_min` while the walk that catches
it up
+/// runs `COMMIT_WALK_OPS_MAX` ops per call. `commit_journal`'s journal
fallback
Review Comment:
This premise can be false, which matters because the assert downgrade rests
on it. `commit_messages` runs from `commit_partition_entry` before
`advance_commit_min`, and `committed_prefix(commit_max)` (`journal.rs:526`)
returns the contiguous resident prefix up to the cluster frontier, so eviction
(`iggy_partition.rs:4243`) also clears the un-drained remainder past the 64-op
cap. `committed_headers_from` (`journal.rs:892`) has no ring read, so the
fallback can return empty and the walk wedges at `commit_min`. Worth confirming
before treating it as the backstop.
##########
core/consensus/src/plane_helpers.rs:
##########
@@ -446,18 +456,35 @@ where
P: Pipeline<Entry = PipelineEntry>,
{
let commit = consensus.commit_max();
+ let commit_min = consensus.commit_min();
+ let replica = consensus.replica();
let mut drained = Vec::new();
consensus.with_pipeline_mut(|pipeline| {
+ let mut next = commit_min + 1;
while let Some(head_op) = pipeline.head().map(|entry| entry.header.op)
{
if head_op > commit {
break;
}
+ if head_op != next {
+ tracing::warn!(
Review Comment:
`warn!` per ack on a state this same doc block calls legitimate and
transient. The two enclosing call sites already settled this level:
`shard/lib.rs:7565` and `:9734` both chose `debug!` with the comment that it
would otherwise be one line per group per tick. The message is also wrong in
both arms: `commit_journal` closes the backlog case and only a pop closes the
applied-head case, so "until repair refills it" names a remedy that applies to
neither.
##########
core/shard/src/lib.rs:
##########
@@ -5658,22 +5701,108 @@ where
consensus.group(),
)
.await;
- } else {
- // `from_op` past `to_op` without `commit_min` reaching it: the
- // primary-elect window above starts at the merged log's commit
- // point, which can sit above what this replica has walked. The
- // top-of-tick check closes the ordinary case; this closes the
- // one it cannot see.
- tracing::info!(
+ }
+ }
+ }
+
+ /// Re-arm a repair session that spent its stall budget against another
replica.
+ ///
+ /// A session pins its peer and fences every arming site while it stands,
so a
+ /// peer that cannot answer wedges the walk harder than having no session
at
+ /// all. Past the budget the session is dropped and re-armed one step on;
an
+ /// ordinary lost frame is re-requested long before that. Mirrors the
partition
+ /// rotation in [`Self::tick_partitions`].
+ ///
+ /// Two rings, because two things decide who can serve. A `Normal` backup
is
+ /// repairing its committed tail and any replica ahead of it will do, so it
+ /// walks the cluster preferring the primary. A primary-elect is repairing
+ /// toward a merged log, and only the `DoViewChange` senders that named
the op
+ /// can serve it: walking the whole ring lands on a replica that answers
+ /// `RangeEvicted` for a range it never held.
+ #[allow(clippy::future_not_send)]
+ async fn rotate_stalled_metadata_repair<P>(
+ &self,
+ consensus: &VsrConsensus<B, P>,
+ peer: u8,
+ from_op: u64,
+ to_op: u64,
+ ) where
+ B: MessageBus,
+ P: Pipeline<Entry = consensus::PipelineEntry>,
+ {
+ *self.metadata_repair.borrow_mut() = None;
+ self.metadata_repair_attempts.set(0);
Review Comment:
This contradicts both docs that justify the counter living on the shard
rather than the session: `:1565-1571` and `:6665-6670` state it has to outlive
the session, or the rotation that mints a new one would reset the count and
re-target forever without giving up on a peer. Rotation is that reset, and
every other session-drop path zeroes it too (`:4980`, `:5205`, `:5631`,
`:5682`), so it now bounds one round. Either drop this line, or move the field
onto `MetadataRepairSession` and rewrite both docs.
##########
core/shard/src/lib.rs:
##########
@@ -5163,6 +5160,51 @@ where
// `RangeEvicted` again if the primary checkpointed mid
// transfer -- that reraises through the same path, and
each
// round lifts the local floor, so it converges.
+ //
+ // Never as primary-elect. A transfer replaces
snapshot-shaped
+ // state wholesale, and this replica has a merged log
parked
+ // against that state naming ops it has just been told it
+ // cannot serve; installing under it would start the view
over
+ // a log the new state no longer matches. The honest
answer is
+ // that another replica holds the committed prefix, so
leave
+ // the session for the stall rotation to re-target and let
the
+ // view-change timeout escalate if nobody can serve it.
+ if consensus.view_log_is_pending()
+ && consensus.is_primary_for_view(consensus.view())
+ {
+ tracing::warn!(
+ shard = self.id,
+ peer = header.replica,
+ retained_from = header.op,
+ local_commit = consensus.commit_min(),
+ "merged-log repair peer evicted the requested
range; \
+ waiting for another sender rather than
transferring \
+ state mid view change"
+ );
+ return;
+ }
+
+ // The floor must also be ABOVE the op this replica needs.
A
+ // peer behind the requested window walks its serve range
off
+ // the end and answers `RangeEvicted` at the requested
floor
+ // itself, having retained nothing and evicted nothing;
+ // converting on that arms a transfer against a replica
with
+ // less state than this one and fences repair for a full
+ // transfer backoff. Drop the session and let the level
trigger
+ // re-request from the primary instead.
+ if header.op <= consensus.commit_min() + 1 {
+ tracing::warn!(
+ shard = self.id,
+ peer = header.replica,
+ retained_from = header.op,
+ local_commit = consensus.commit_min(),
+ "metadata repair peer retains the requested floor
but served \
+ nothing; re-requesting rather than converting to
state transfer"
+ );
+ *self.metadata_repair.borrow_mut() = None;
+ self.metadata_repair_attempts.set(0);
Review Comment:
Zeroing the budget here means no round is ever charged, and
`gap_repair_peer` (`:10363`) deterministically re-picks the primary. So the
sequence is request, `RangeEvicted`, drop, re-arm the same peer, repeating at
the debounce interval with no bound, and the state-transfer escalation this
guard replaced is now unreachable. Charging the round instead of resetting it
keeps the escalation path alive. The log text also disagrees with the comment
above it, which says the peer retained nothing.
##########
core/shard/src/lib.rs:
##########
@@ -9926,6 +10059,37 @@ where
}
}
+/// Lowest op of a primary-elect's merged log this replica can be held to.
+///
+/// The merged commit point is what the cluster committed, `commit_min` what
this
+/// replica applied; they diverge exactly when the local prefix has a hole. The
Review Comment:
They also diverge on plain apply lag with no hole present, and that is the
common case this `min` actually fires on: a contiguously lagging primary-elect
has `commit_min < pending.commit_max`. So the widened floor buys nothing there
while still taking the eviction exposure at the call site. Suggest wording it
as diverging whenever this replica has not applied the merged commit point,
hole or plain lag.
##########
core/shard/src/lib.rs:
##########
@@ -5658,22 +5701,108 @@ where
consensus.group(),
)
.await;
- } else {
- // `from_op` past `to_op` without `commit_min` reaching it: the
- // primary-elect window above starts at the merged log's commit
- // point, which can sit above what this replica has walked. The
- // top-of-tick check closes the ordinary case; this closes the
- // one it cannot see.
- tracing::info!(
+ }
+ }
+ }
+
+ /// Re-arm a repair session that spent its stall budget against another
replica.
+ ///
+ /// A session pins its peer and fences every arming site while it stands,
so a
+ /// peer that cannot answer wedges the walk harder than having no session
at
+ /// all. Past the budget the session is dropped and re-armed one step on;
an
+ /// ordinary lost frame is re-requested long before that. Mirrors the
partition
+ /// rotation in [`Self::tick_partitions`].
+ ///
+ /// Two rings, because two things decide who can serve. A `Normal` backup
is
+ /// repairing its committed tail and any replica ahead of it will do, so it
+ /// walks the cluster preferring the primary. A primary-elect is repairing
+ /// toward a merged log, and only the `DoViewChange` senders that named
the op
+ /// can serve it: walking the whole ring lands on a replica that answers
+ /// `RangeEvicted` for a range it never held.
+ #[allow(clippy::future_not_send)]
+ async fn rotate_stalled_metadata_repair<P>(
+ &self,
+ consensus: &VsrConsensus<B, P>,
+ peer: u8,
+ from_op: u64,
+ to_op: u64,
+ ) where
+ B: MessageBus,
+ P: Pipeline<Entry = consensus::PipelineEntry>,
+ {
+ *self.metadata_repair.borrow_mut() = None;
+ self.metadata_repair_attempts.set(0);
+
+ if consensus.view_log_is_pending() &&
consensus.is_primary_for_view(consensus.view()) {
+ let sources = metadata_view_repair_sources(consensus, from_op);
+ let Some(next_peer) = sources.into_iter().find(|candidate|
*candidate != peer) else {
Review Comment:
`find` over a peer-independent, stably sorted list is not a ring walk:
`[A,B,C]` yields B when peer is A and A when peer is B, so C is never tried and
the ring property `next_transfer_peer` had is lost. `:5737` also resolves
sources for `from_op`, the scan floor, while the initial arm resolves for
`missing_op` (`:6037`), so rotation can target replicas that never named the
hole. Suggest locating `peer`'s index in `sources` and taking the next
wrapping, and passing `missing_op`.
##########
core/consensus/src/impls.rs:
##########
@@ -1729,6 +1729,33 @@ impl<B: MessageBus, P: Pipeline<Entry = PipelineEntry>>
VsrConsensus<B, P> {
self.recovery_barrier.set(required_commit);
}
+ /// Re-decide the barrier against a log head the cluster just settled.
+ ///
+ /// Boot arms it at the recovered journal head: those ops were acked
before the
+ /// restart, so admitting writes before they re-commit rolls back committed
+ /// history. It otherwise clears only by `commit_max` passing it, which
never
+ /// happens when a view change discards the suffix instead of
re-committing it.
+ /// The boot re-pipeline already ran, so nothing re-prepares those ops,
+ /// `is_caught_up_primary` stays shut, and the primary drops the very
requests
+ /// that would raise `commit_max`.
+ ///
+ /// Call this wherever the head is authoritatively re-decided: a merged
log at
+ /// view start, an adopted `StartView`. `head` lowers the barrier when the
view
+ /// truncated the suffix, keeps it when the suffix survived.
+ ///
+ /// Lowered, never cleared. `is_caught_up_primary` reads it against
`commit_max`
+ /// and a met barrier costs it nothing, but `await_recovery_barrier` reads
it
+ /// against `commit_min`, and adoption raises `commit_max` before walking
the
+ /// suffix into the state machine. Zeroing a met barrier would open that
read
+ /// gate over the unapplied window it exists to hold.
+ pub fn redecide_recovery_barrier(&self, head: u64) {
+ let barrier = self.recovery_barrier.get();
+ if barrier == 0 {
+ return;
+ }
+ self.recovery_barrier.set(barrier.min(head));
Review Comment:
"Lowered, never cleared" does not hold at `head == 0`, where
`barrier.min(0)` is 0. That matters because `barrier_state`
(`core/server/src/http/reads.rs:243`) is `barrier == 0 || commit_min >=
barrier`, so a zeroed barrier bypasses the `commit_min` gate entirely rather
than being merely met. `announced` is the wire `StartViewHeader.op`, returned
verbatim on suffix-decode failure (`:3243`), `validate` has no `op >= 1` check,
and the low-op guard at `:3356` binds only when `msg_view == log_view`. Suggest
`if head == 0 { return; }`. Separately, making the barrier mutable post-boot
breaks `reads.rs:279`, which captures it once outside a loop that then polls
only `commit_min`, so an in-flight reader waits on a value that no longer
exists.
##########
core/shard/src/lib.rs:
##########
@@ -11247,7 +11416,7 @@ mod view_coverage_tests {
committed_elsewhere: Vec::new(),
};
let held = [sealed(100, 1), sealed(99, 7), sealed(98, 1)];
- let missing = first_op_not_covered(&pending, 0, |op| {
+ let missing = first_op_not_covered(&pending, 0, pending.commit_max,
|op| {
Review Comment:
This case and `:11443`, `:11448`, `:11490` pass `pending.commit_max` as
`commit_min`, which collapses `merged_log_scan_floor` back to master's floor,
so four of the five cases assert the old behaviour. The one case that exercises
the widened range stops on its first probe. Nothing covers a held run between
`commit_min + 1` and the hole, which is exactly the span that carries the scan
cost and the eviction exposure.
##########
core/partitions/src/iggy_partition.rs:
##########
@@ -6995,6 +6993,65 @@ mod tests {
);
}
+ /// `AckLevel::NoAck` stores apply on the primary only and never
replicate, so
+ /// which replicas hold an offset is not agreed and a committed delete can
+ /// legitimately find nothing. Erroring here fails the committed apply,
fences
+ /// the partition, and then crash-loops on every replay of the same op.
+ #[compio::test]
+ async fn
given_an_absent_offset_when_committing_a_delete_should_apply_without_fencing() {
+ let mut partition = test_partition();
+ assert!(
+ !partition.consensus().is_follower(),
+ "the role the old existence check errored on is the primary"
+ );
+
+ partition.stage_consumer_offset_delete(1, ConsumerKind::Consumer, 7);
+ partition
+ .apply_staged_consumer_offset_commit(1)
+ .await
+ .expect("a committed delete of an absent consumer offset must
apply");
+
+ partition.stage_consumer_offset_delete(2, ConsumerKind::ConsumerGroup,
9);
+ partition
+ .apply_staged_consumer_offset_commit(2)
+ .await
+ .expect("a committed delete of an absent group offset must apply");
+
+ assert!(
+ partition.fatal().is_none(),
Review Comment:
This assert cannot fail, so the test's "without_fencing" half is not
exercised. `fatal` is written only at `:2682`, `:3464` and `:4361`, and `:4361`
is reachable only through `handle_committed_entries` /
`commit_partition_entry`, which neither new test drives; both enter at
`apply_staged_consumer_offset_commit`. Routing the committed delete through
`handle_committed_entries` would make it real. Worth adding the
`apply_consumer_offset_no_ack` absent-offset arm at `:2278` too, since that is
the client-visible half of the change (silent no-reply becomes a committed
reply).
##########
core/simulator/src/workload/invariants.rs:
##########
@@ -102,6 +141,91 @@ impl Invariants {
self.state_checker.check(sim, seed);
}
+ /// Catch a metadata primary permanently shut behind its own recovery
barrier.
+ ///
+ /// The shape `VsrConsensus::redecide_recovery_barrier` fixes, caught from
the
+ /// outside: a primary that can never clear its barrier drops every
request as
+ /// `NotReady`, which otherwise surfaces only as an unexplained stall.
+ ///
+ /// # Panics
+ /// When a `Normal` metadata primary sits below its barrier for
+ /// [`RECOVERY_BARRIER_WEDGE_TICKS`] consecutive ticks.
+ fn check_recovery_barrier(&mut self, sim: &Simulator, seed: u64,
replica_idx: u8) {
+ let Some(consensus) = sim.replicas[usize::from(replica_idx)].shards[0]
+ .plane
+ .metadata()
+ .consensus
+ .as_ref()
+ else {
+ return;
+ };
+ let gated = consensus.is_primary()
+ && !consensus.has_ceded_primaryship()
+ && consensus.is_normal()
+ && consensus.commit_max() < consensus.recovery_barrier();
Review Comment:
This gates on `commit_max`, the write gate, but the reason
`redecide_recovery_barrier` refuses to clear a met barrier is the
`commit_min`-based read gate at `core/server/src/http/reads.rs:243`. A barrier
met by `commit_max` and never by `commit_min` therefore 503s every HTTP read
indefinitely, and this invariant cannot observe it. Adding a `commit_min <
recovery_barrier()` arm would cover the half the fix exists for.
##########
core/simulator/src/lib.rs:
##########
@@ -135,6 +135,31 @@ pub(crate) struct PartitionConsensusState {
pub commit_min: u64,
}
+/// A pipeline head the commit walk is holding on: covered by the commit
frontier,
+/// but not the op the state machine is next owed.
+///
+/// What `drain_committable_prefix` / `peek_committable_head` refuse to drain.
Legit
+/// and transient right after a partition promotion, whose `RebuildPipeline`
seeds
+/// above an apply backlog the bounded journal walk clears over the following
+/// sweeps; permanent means the ops between are gone and nothing is repairing
them.
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct CommitPrefixHole {
+ pub head_op: u64,
+ pub commit_min: u64,
+ pub commit_max: u64,
+}
+
+impl CommitPrefixHole {
+ fn read(head: Option<PrepareHeader>, commit_min: u64, commit_max: u64) ->
Option<Self> {
+ let head = head?;
+ (head.op <= commit_max && head.op != commit_min + 1).then_some(Self {
Review Comment:
`head.op != commit_min + 1` also matches `head.op <= commit_min`, a head the
walk has already passed, and the panic text at `invariants.rs:217` then asserts
that the ops between are missing, which is the wrong diagnosis for that arm.
Two distinct wedge shapes share one type, one counter and one message.
Splitting them is a prerequisite for a correct threshold in either: the
applied-head arm never self-clears and wants a near-zero threshold, while the
real-hole arm wants a reset on `commit_min` progress. Note a blanket reset on
`commit_min` advance would be wrong on the conflated counter, since in the
applied-head arm the head is frozen while `commit_min` keeps climbing.
##########
core/simulator/src/bin/workload-fuzz.rs:
##########
@@ -574,13 +576,17 @@ fn run_quiesce_phase(
proved nothing about entity state (seed={seed:#x})"
);
let live = usize::from(replicas) - sim.crashed.len();
+ // Either plane satisfies it: a partition-plane run commits almost no
metadata,
+ // so the metadata count alone called every such run vacuous.
+ let compared = convergence.ops_compared +
convergence.partition_ops_compared;
assert!(
- args.min_ops_compared == 0 || live < 2 || convergence.ops_compared >=
args.min_ops_compared,
- "--min-ops-compared {}: {live} replicas live but only {} op(s)
witnessed \
- by more than one, so cross-replica agreement went untested \
- (seed={seed:#x})",
+ args.min_ops_compared == 0 || live < 2 || compared >=
args.min_ops_compared,
Review Comment:
Summing both planes drops the metadata-plane guarantee: a run that compared
zero metadata ops now satisfies `--min-ops-compared 1`, which is the property
that kept the metadata oracle non-vacuous. The clap help at `:135-138` also
still reads "Committed metadata ops", so the operator-facing flag describes
behaviour it no longer has. Suggest keeping a per-plane floor, for example a
second `--min-metadata-ops-compared`, and correcting the help text.
##########
core/shard/src/lib.rs:
##########
@@ -10630,9 +10794,14 @@ const fn header_is_view_entry(local: &PrepareHeader,
canonical: &PrepareHeader)
/// Neither can diverge from the merged log (a committed or compacted op is the
/// quorum's op), and no repair puts the journal entry back, so demanding one
parks
/// the view change forever.
+///
+/// Opens at [`merged_log_scan_floor`]: the merged commit point alone would
declare
+/// the log serveable over a local gap, promoting a replica whose
`CommitJournal`
+/// gap-stops below where `RebuildPipeline` seeds.
fn first_op_not_covered(
pending: &MergedLog,
repair_floor: u64,
+ commit_min: u64,
Review Comment:
Two adjacent same-typed positional params with no param docs, where swapping
them compiles. The PR is its own proof: `:5547` passes `consensus.commit_min()`
twice, while `:5996` passes `snapshot_op` then `commit_min` and a swap there
would silently re-floor the metadata scan. A named `ScanFloor { repair_floor,
commit_min }` or newtypes would also make the duplication at `:5547` reviewable
rather than two identical arguments.
##########
core/partitions/src/iggy_partition.rs:
##########
@@ -2081,10 +2081,27 @@ where
.is_some_and(|&high_water| offset <= high_water)
}
- fn apply_consumer_offset_commit(
- &self,
- pending: PendingConsumerOffsetCommit,
- ) -> Result<(), IggyError> {
+ /// Note a committed delete that found no offset to remove.
+ ///
+ /// Expected wherever the paired `AckLevel::NoAck` store never replicated,
so a
+ /// diagnostic and not a fault. Still logged: on a replica that did serve
the
+ /// store it is the first symptom of a lost apply.
+ fn log_absent_offset_delete(&self, kind: &str, id: u64) {
Review Comment:
"Still logged" does not hold in a default deployment: `debug!` against the
shipped `level = "info"` (`core/server/config.toml:384`, with the
`EnvFilter::new("INFO")` fallback at `server_common/src/log/logger.rs:186`)
means this signal is never emitted, so the rationale for dropping the error is
not backed by an observable replacement. `kind: &str` also hand-writes the
strings `impl Display for ConsumerKind` already produces with `pending.kind` in
scope, `u64::from(id)` widens two `u32` ids, and this bypasses the
`emit_partition_diag` schema the removed WARN used. Taking `ConsumerKind` and
`u32` through `emit_partition_diag`, at WARN when not a follower, closes all
four.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]