Skip to main content

operator/write_dispatcher/
options.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::time::Duration;
16
17use serde::{Deserialize, Serialize};
18
19/// Exponential backoff for retryable distributed writes.
20#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
21#[serde(default)]
22pub struct WriteRetryOptions {
23    /// Maximum additional attempts after the original write. Zero disables retries.
24    pub max_retries: usize,
25    /// Initial backoff before jitter is applied.
26    #[serde(with = "humantime_serde")]
27    pub initial_backoff: Duration,
28    /// Maximum backoff before jitter is applied.
29    #[serde(with = "humantime_serde")]
30    pub max_backoff: Duration,
31    /// Multiplier applied to the backoff after each retry.
32    pub backoff_multiplier: f32,
33}
34
35impl Default for WriteRetryOptions {
36    fn default() -> Self {
37        Self {
38            max_retries: 3,
39            initial_backoff: Duration::from_millis(1500),
40            max_backoff: Duration::from_secs(10),
41            backoff_multiplier: 2.0,
42        }
43    }
44}
45
46impl WriteRetryOptions {
47    /// Checks that the configured backoff is positive, bounded, and nondecreasing.
48    pub fn validate(&self) -> Result<(), &'static str> {
49        if self.initial_backoff.is_zero() {
50            return Err("write_retry.initial_backoff must be positive");
51        }
52        if self.max_backoff < self.initial_backoff {
53            return Err("write_retry.max_backoff must be at least initial_backoff");
54        }
55        if !self.backoff_multiplier.is_finite() || self.backoff_multiplier < 1.0 {
56            return Err("write_retry.backoff_multiplier must be finite and at least 1");
57        }
58        Ok(())
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_validate_write_retry_options() {
68        for options in [
69            WriteRetryOptions::default(),
70            WriteRetryOptions {
71                max_retries: 0,
72                ..Default::default()
73            },
74            WriteRetryOptions {
75                backoff_multiplier: 1.0,
76                ..Default::default()
77            },
78        ] {
79            options.validate().unwrap();
80        }
81        for options in [
82            WriteRetryOptions {
83                initial_backoff: Duration::ZERO,
84                ..Default::default()
85            },
86            WriteRetryOptions {
87                max_backoff: Duration::from_secs(1),
88                ..Default::default()
89            },
90            WriteRetryOptions {
91                backoff_multiplier: 0.5,
92                ..Default::default()
93            },
94            WriteRetryOptions {
95                backoff_multiplier: f32::NAN,
96                ..Default::default()
97            },
98            WriteRetryOptions {
99                backoff_multiplier: f32::INFINITY,
100                ..Default::default()
101            },
102        ] {
103            assert!(options.validate().is_err(), "{options:?}");
104        }
105    }
106}