#[non_exhaustive]pub struct RetentionPolicy {
pub terminal_max_age: Duration,
pub batch_size: u32,
pub idempotency_key_max_age: Option<Duration>,
pub max_batches: Option<u32>,
}Expand description
How long terminal tasks are kept, and how aggressively they are removed.
#[non_exhaustive]: build it with new and the with_*
setters, which cover every field. STABILITY.md §4 lists the
configuration structs that carry this marking so a new option on any of
them is additive; this one was missed by the 0.12.0 conversion that
introduced the rule, and adding
idempotency_key_max_age is what found
it.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.terminal_max_age: DurationTerminal tasks whose last update is older than this are eligible.
Measured against updated_at, which the store maintains, and evaluated
by the database rather than the caller — an application clock that runs
fast would otherwise delete work that is younger than it looks.
batch_size: u32Rows per DELETE. Default 1,000.
The point of batching is the lock, not the throughput. One statement deleting a million rows holds locks and grows a transaction for as long as it takes; a thousand statements deleting a thousand rows each let every other query through in between.
idempotency_key_max_age: Option<Duration>Idempotency keys older than this are deleted. None keeps them for
ever, which is what every release before this one did.
§Why a key has to expire at all
A key is released when the send it guards fails, and otherwise it stays — deliberately, because it has to outlive the task it names or a late retry would execute a second time. Nothing else ever removed one. A busy deployment therefore accumulated a row per keyed send for the life of the database, and the index that makes the claim atomic grew with it.
§What expiring one costs
Exactly what the key was preventing: a retry arriving after the key expires re-executes the send. That is the trade every idempotency key has, and the reason the default is a full day rather than something tidier — it has to exceed the longest window in which a client might still retry, and clients retry in seconds.
§It is never allowed below terminal_max_age
A key expiring while the task it names is still retained is the bad
case: the retry does not replay, it creates a second task alongside
the first, and the caller ends up with two ids for one logical send.
So the sweep uses
effective_idempotency_key_max_age,
which never returns less than
terminal_max_age — the invariant holds by
construction rather than by the operator having read this paragraph.
max_batches: Option<u32>Stop after this many batches, leaving the rest for the next call.
None runs until nothing is left.
Set it to bound how long one sweep can run when the first sweep after switching retention on has years of backlog to work through.
Implementations§
Source§impl RetentionPolicy
impl RetentionPolicy
Sourcepub const fn new(terminal_max_age: Duration) -> Self
pub const fn new(terminal_max_age: Duration) -> Self
A policy keeping terminal tasks for terminal_max_age, with default
batching.
Sourcepub const fn with_batch_size(self, batch_size: u32) -> Self
pub const fn with_batch_size(self, batch_size: u32) -> Self
Sets the rows-per-DELETE batch size. Zero is treated as one.
Sourcepub const fn with_idempotency_key_max_age(
self,
max_age: Option<Duration>,
) -> Self
pub const fn with_idempotency_key_max_age( self, max_age: Option<Duration>, ) -> Self
Sets how long an idempotency key is kept; None keeps them for ever.
See idempotency_key_max_age for what
expiring one costs and why the sweep will not honour a value below
terminal_max_age.
Sourcepub fn effective_idempotency_key_max_age(&self) -> Option<Duration>
pub fn effective_idempotency_key_max_age(&self) -> Option<Duration>
The key age the sweep actually uses: never below
terminal_max_age.
A key must outlive the task it names. Deleting one while its task is
still retained turns the next retry into a second task rather than a
replay, so a policy that asks for that is clamped rather than obeyed —
the same shape as effective_batch_size — which is pub(crate) and
feature-gated, so it cannot be linked from a public doc comment —
where a nonsensical value is floored at the nearest sensible one
instead of being honoured into a defect.
Sourcepub const fn with_max_batches(self, max_batches: u32) -> Self
pub const fn with_max_batches(self, max_batches: u32) -> Self
Bounds how many batches a single sweep runs.
Trait Implementations§
Source§impl Clone for RetentionPolicy
impl Clone for RetentionPolicy
Auto Trait Implementations§
impl Freeze for RetentionPolicy
impl RefUnwindSafe for RetentionPolicy
impl Send for RetentionPolicy
impl Sync for RetentionPolicy
impl Unpin for RetentionPolicy
impl UnsafeUnpin for RetentionPolicy
impl UnwindSafe for RetentionPolicy
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request