Skip to content

Commit 1af97ff

Browse files
authored
persist: fresh command timestamps during retries (#38060)
Prior to this PR persist commands that accepted a heartbeat timestamp parameter computed that parameter once and used the same value throughout all the retries, if any. When the number of retry attempts is low this is usually not a problem but on heavily utilized clusters retries can go on for multiple minutes. In the extreme case by the time the command succeeds, it commits a timestamp that is too stale. Two instances of this problem were the root cause of incidents 1177 and 1134 were the registration of a read handle was retrying for more than 15 minutes, and by the time it succeeded the handle had already lost its lease. This PR removes the heartbeat argument from the affected methods altogether and instead uses internal fresh calls to persist's `NowFn` configuration on every retry. By doing this the committed heartbeat timestamp is only stale by the network latency of the final consensus op that succeeded. Signed-off-by: Petros Angelatos <petrosagg@gmail.com>
1 parent 72a8db1 commit 1af97ff

4 files changed

Lines changed: 17 additions & 45 deletions

File tree

src/persist-client/src/internal/machine.rs

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,12 @@ where
222222
}
223223
}
224224

225+
/// Registers a leased reader, returning its initial state.
225226
pub async fn register_leased_reader(
226227
&self,
227228
reader_id: &LeasedReaderId,
228229
purpose: &str,
229230
lease_duration: Duration,
230-
heartbeat_timestamp_ms: u64,
231231
use_critical_since: bool,
232232
) -> (LeasedReaderState<T>, RoutineMaintenance) {
233233
let metrics = Arc::clone(&self.applier.metrics);
@@ -239,7 +239,9 @@ where
239239
purpose,
240240
seqno,
241241
lease_duration,
242-
heartbeat_timestamp_ms,
242+
// NOTE: Sample the clock here rather than hoisting it out of the closure so
243+
// that a fresh value is used on every retry of this command.
244+
(cfg.now)(),
243245
use_critical_since,
244246
)
245247
})
@@ -318,20 +320,19 @@ where
318320
(reqs, maintenance)
319321
}
320322

323+
/// Appends `batch` if the shard upper matches its lower.
321324
pub async fn compare_and_append(
322325
&self,
323326
batch: &HollowBatch<T>,
324327
writer_id: &WriterId,
325328
debug_info: &HandleDebugState,
326-
heartbeat_timestamp_ms: u64,
327329
) -> CompareAndAppendRes<T> {
328330
let idempotency_token = IdempotencyToken::new();
329331
loop {
330332
let res = self
331333
.compare_and_append_idempotent(
332334
batch,
333335
writer_id,
334-
heartbeat_timestamp_ms,
335336
&idempotency_token,
336337
debug_info,
337338
None,
@@ -375,7 +376,6 @@ where
375376
&self,
376377
batch: &HollowBatch<T>,
377378
writer_id: &WriterId,
378-
heartbeat_timestamp_ms: u64,
379379
idempotency_token: &IdempotencyToken,
380380
debug_info: &HandleDebugState,
381381
// Only exposed for testing. In prod, this always starts as None, but
@@ -488,7 +488,9 @@ where
488488
state.compare_and_append(
489489
batch,
490490
writer_id,
491-
heartbeat_timestamp_ms,
491+
// NOTE: Sample the clock here rather than hoisting it out of the closure
492+
// so that a fresh value is used on every retry of this command.
493+
(cfg.now)(),
492494
lease_duration_ms,
493495
idempotency_token,
494496
debug_info,
@@ -627,22 +629,18 @@ where
627629
}
628630
}
629631

632+
/// Downgrades the reader's since capability, also heartbeating its lease.
630633
pub async fn downgrade_since(
631634
&self,
632635
reader_id: &LeasedReaderId,
633636
outstanding_seqno: SeqNo,
634637
new_since: &Antichain<T>,
635-
heartbeat_timestamp_ms: u64,
636638
) -> (SeqNo, Since<T>, RoutineMaintenance) {
637639
let metrics = Arc::clone(&self.applier.metrics);
638-
self.apply_unbatched_idempotent_cmd(&metrics.cmds.downgrade_since, |seqno, _cfg, state| {
639-
state.downgrade_since(
640-
reader_id,
641-
seqno,
642-
outstanding_seqno,
643-
new_since,
644-
heartbeat_timestamp_ms,
645-
)
640+
self.apply_unbatched_idempotent_cmd(&metrics.cmds.downgrade_since, |seqno, cfg, state| {
641+
// NOTE: Sample the clock here rather than hoisting it out of the closure so that a
642+
// fresh value is used on every retry of this command.
643+
state.downgrade_since(reader_id, seqno, outstanding_seqno, new_since, (cfg.now)())
646644
})
647645
.await
648646
}
@@ -1500,12 +1498,7 @@ pub mod datadriven {
15001498
let reader_id = args.expect("reader_id");
15011499
let (_, since, routine) = datadriven
15021500
.machine
1503-
.downgrade_since(
1504-
&reader_id,
1505-
seqno,
1506-
&since,
1507-
(datadriven.machine.applier.cfg.now)(),
1508-
)
1501+
.downgrade_since(&reader_id, seqno, &since)
15091502
.await;
15101503
datadriven.routine.push(routine);
15111504
Ok(format!(
@@ -2195,7 +2188,6 @@ pub mod datadriven {
21952188
&reader_id,
21962189
"tests",
21972190
READER_LEASE_DURATION.get(&datadriven.client.cfg),
2198-
(datadriven.client.cfg.now)(),
21992191
false,
22002192
)
22012193
.await;
@@ -2313,7 +2305,6 @@ pub mod datadriven {
23132305
.expect("unknown batch")
23142306
.clone();
23152307
let token = args.optional("token").unwrap_or_else(IdempotencyToken::new);
2316-
let now = (datadriven.client.cfg.now)();
23172308

23182309
let (id, maintenance) = datadriven
23192310
.machine
@@ -2330,7 +2321,6 @@ pub mod datadriven {
23302321
.compare_and_append_idempotent(
23312322
&batch.batch,
23322323
&writer_id,
2333-
now,
23342324
&token,
23352325
&HandleDebugState::default(),
23362326
indeterminate,
@@ -2494,7 +2484,6 @@ pub mod tests {
24942484
&batch.into_hollow_batch(),
24952485
&write.writer_id,
24962486
&HandleDebugState::default(),
2497-
(write.cfg.now)(),
24982487
)
24992488
.await
25002489
.unwrap();

src/persist-client/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,11 @@ impl PersistClient {
357357
let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
358358

359359
let reader_id = LeasedReaderId::new();
360-
let heartbeat_ts = (self.cfg.now)();
361360
let (reader_state, maintenance) = machine
362361
.register_leased_reader(
363362
&reader_id,
364363
&diagnostics.handle_purpose,
365364
READER_LEASE_DURATION.get(&self.cfg),
366-
heartbeat_ts,
367365
use_critical_since,
368366
)
369367
.await;

src/persist-client/src/read.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,6 @@ where
722722
}
723723

724724
let before_heartbeat = Instant::now();
725-
let heartbeat_ms = (machine.applier.cfg.now)();
726725
let current_seqno = machine.seqno();
727726
let result = leased_seqnos.modify(|s| {
728727
if s.expired {
@@ -737,7 +736,7 @@ where
737736
let actual_since = match result {
738737
Ok(held_seqno) => {
739738
let (seqno, actual_since, maintenance) = machine
740-
.downgrade_since(&reader_id, held_seqno, &held_since, heartbeat_ms)
739+
.downgrade_since(&reader_id, held_seqno, &held_since)
741740
.await;
742741
leased_seqnos.modify(|s| {
743742
s.applied_since.clone_from(&actual_since.0);
@@ -990,13 +989,11 @@ where
990989
let new_reader_id = LeasedReaderId::new();
991990
let machine = self.machine.clone();
992991
let gc = self.gc.clone();
993-
let heartbeat_ts = (self.cfg.now)();
994992
let (reader_state, maintenance) = machine
995993
.register_leased_reader(
996994
&new_reader_id,
997995
purpose,
998996
READER_LEASE_DURATION.get(&self.cfg),
999-
heartbeat_ts,
1000997
false,
1001998
)
1002999
.await;

src/persist-client/src/write.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -301,15 +301,9 @@ where
301301
let desc = Description::new(lower.clone(), target.clone(), since);
302302
let batch = HollowBatch::empty(desc);
303303

304-
let heartbeat_timestamp = (self.cfg.now)();
305304
let res = self
306305
.machine
307-
.compare_and_append(
308-
&batch,
309-
&self.writer_id,
310-
&self.debug_state,
311-
heartbeat_timestamp,
312-
)
306+
.compare_and_append(&batch, &self.writer_id, &self.debug_state)
313307
.await;
314308

315309
use CompareAndAppendRes::*;
@@ -749,15 +743,9 @@ where
749743
}
750744
}
751745

752-
let heartbeat_timestamp = (self.cfg.now)();
753746
let res = self
754747
.machine
755-
.compare_and_append(
756-
&combined_batch,
757-
&self.writer_id,
758-
&self.debug_state,
759-
heartbeat_timestamp,
760-
)
748+
.compare_and_append(&combined_batch, &self.writer_id, &self.debug_state)
761749
.await;
762750

763751
match res {

0 commit comments

Comments
 (0)