diff --git a/src/backoff.rs b/src/backoff.rs index d55f4d8..3f67dd2 100644 --- a/src/backoff.rs +++ b/src/backoff.rs @@ -5,6 +5,9 @@ use rand::{thread_rng, Rng}; use std::time::Duration; +/// When a request is retried, we can backoff for some time to avoid saturating the network. +/// +/// `Backoff` is an object which determines how long to wait for. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Backoff { kind: BackoffKind, @@ -64,10 +67,12 @@ impl Backoff { } } + /// True if we should not backoff at all (usually indicates that we should not retry a request). pub fn is_none(&self) -> bool { self.kind == BackoffKind::None } + /// Don't wait. Usually indicates that we should not retry a request. pub const fn no_backoff() -> Backoff { Backoff { kind: BackoffKind::None, @@ -79,6 +84,11 @@ impl Backoff { } } + // Exponential backoff means that the retry delay should multiply a constant + // after each attempt, up to a maximum value. After each attempt, the new retry + // delay should be: + // + // new_delay = min(max_delay, base_delay * 2 ** attempts) pub const fn no_jitter_backoff( base_delay_ms: u64, max_delay_ms: u64, @@ -94,6 +104,11 @@ impl Backoff { } } + // Adds Jitter to the basic exponential backoff. Returns a random value between + // zero and the calculated exponential backoff: + // + // temp = min(max_delay, base_delay * 2 ** attempts) + // new_delay = random_between(0, temp) pub fn full_jitter_backoff( base_delay_ms: u64, max_delay_ms: u64, @@ -114,6 +129,11 @@ impl Backoff { } } + // Equal Jitter limits the random value should be equal or greater than half of + // the calculated exponential backoff: + // + // temp = min(max_delay, base_delay * 2 ** attempts) + // new_delay = random_between(temp / 2, temp) pub fn equal_jitter_backoff( base_delay_ms: u64, max_delay_ms: u64, @@ -134,6 +154,11 @@ impl Backoff { } } + // Decorrelated Jitter is always calculated with the previous backoff + // (the initial value is base_delay): + // + // temp = random_between(base_delay, previous_delay * 3) + // new_delay = min(max_delay, temp) pub fn decorrelated_jitter_backoff( base_delay_ms: u64, max_delay_ms: u64, @@ -152,33 +177,13 @@ impl Backoff { } } +/// The pattern for computing backoff times. #[derive(Debug, Clone, PartialEq, Eq)] enum BackoffKind { - // NoBackoff means that we don't want any retry here. None, - // Exponential backoff means that the retry delay should multiply a constant - // after each attempt, up to a maximum value. After each attempt, the new retry - // delay should be: - // - // new_delay = min(max_delay, base_delay * 2 ** attempts) NoJitter, - // Adds Jitter to the basic exponential backoff. Returns a random value between - // zero and the calculated exponential backoff: - // - // temp = min(max_delay, base_delay * 2 ** attempts) - // new_delay = random_between(0, temp) FullJitter, - // Equal Jitter limits the random value should be equal or greater than half of - // the calculated exponential backoff: - // - // temp = min(max_delay, base_delay * 2 ** attempts) - // new_delay = random_between(temp / 2, temp) EqualJitter, - // Decorrelated Jitter is always calculated with the previous backoff - // (the initial value is base_delay): - // - // temp = random_between(base_delay, previous_delay * 3) - // new_delay = min(max_delay, temp) DecorrelatedJitter, } diff --git a/src/lib.rs b/src/lib.rs index 2b28e60..4545223 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,9 @@ pub use crate::raw::{Client as RawClient, ColumnFamily}; #[doc(inline)] pub use crate::timestamp::{Timestamp, TimestampExt}; #[doc(inline)] -pub use crate::transaction::{Client as TransactionClient, Snapshot, Transaction}; +pub use crate::transaction::{ + Client as TransactionClient, Snapshot, Transaction, TransactionOptions, +}; #[doc(inline)] pub use config::Config; #[doc(inline)] diff --git a/src/request.rs b/src/request.rs index e5d1713..a520300 100644 --- a/src/request.rs +++ b/src/request.rs @@ -165,7 +165,9 @@ pub trait KvRequest: Request + Clone + Sync + Send + 'static + Sized { #[derive(Clone, Debug, new, Eq, PartialEq)] pub struct RetryOptions { + /// How to retry when there is a region error and we need to resolve regions with PD. pub region_backoff: Backoff, + /// How to retry when a key is locked. pub lock_backoff: Backoff, } diff --git a/src/transaction/client.rs b/src/transaction/client.rs index 48ae732..325e57f 100644 --- a/src/transaction/client.rs +++ b/src/transaction/client.rs @@ -107,10 +107,11 @@ impl Client { /// ``` pub async fn begin_optimistic(&self) -> Result { let timestamp = self.current_timestamp().await?; - Ok(self.new_transaction( - timestamp, - TransactionOptions::new_optimistic(self.config.try_one_pc), - )) + let mut options = TransactionOptions::new_optimistic(); + if self.config.try_one_pc { + options = options.try_one_pc(); + } + Ok(self.new_transaction(timestamp, options)) } /// Creates a new [`Transaction`](Transaction) in pessimistic mode. @@ -132,12 +133,30 @@ impl Client { /// ``` pub async fn begin_pessimistic(&self) -> Result { let timestamp = self.current_timestamp().await?; - Ok(self.new_transaction( - timestamp, - TransactionOptions::new_pessimistic(self.config.try_one_pc), - )) + let mut options = TransactionOptions::new_pessimistic(); + if self.config.try_one_pc { + options = options.try_one_pc(); + } + Ok(self.new_transaction(timestamp, options)) } + /// Creates a new customized [`Transaction`](Transaction). + /// + /// # Examples + /// ```rust,no_run + /// use tikv_client::{Config, TransactionClient, TransactionOptions}; + /// use futures::prelude::*; + /// # futures::executor::block_on(async { + /// let client = TransactionClient::new(vec!["192.168.0.100"]).await.unwrap(); + /// let mut transaction = client + /// .begin_with_options(TransactionOptions::default().async_commit()) + /// .await + /// .unwrap(); + /// // ... Issue some commands. + /// let commit = transaction.commit(); + /// let result = commit.await.unwrap(); + /// # }); + /// ``` pub async fn begin_with_options(&self, options: TransactionOptions) -> Result { let timestamp = self.current_timestamp().await?; Ok(self.new_transaction(timestamp, options)) diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 264655f..bf37880 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -11,8 +11,7 @@ pub use client::Client; pub(crate) use lock::{resolve_locks, HasLocks}; pub use snapshot::Snapshot; -pub use transaction::Transaction; -use transaction::TransactionOptions; +pub use transaction::{Transaction, TransactionOptions}; mod buffer; mod client; diff --git a/src/transaction/transaction.rs b/src/transaction/transaction.rs index 68df1b2..5e25ae6 100644 --- a/src/transaction/transaction.rs +++ b/src/transaction/transaction.rs @@ -526,6 +526,7 @@ impl Drop for Transaction { } } +/// Optimistic or pessimistic transaction. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum TransactionKind { Optimistic, @@ -533,62 +534,81 @@ pub enum TransactionKind { Pessimistic(u64), } +/// Options for configuring a transaction. #[derive(Clone, Eq, PartialEq, Debug)] pub struct TransactionOptions { + /// Optimistic or pessimistic (default) transaction. kind: TransactionKind, + /// Try using 1pc rather than 2pc (default is to always use 2pc). try_one_pc: bool, + /// Try to use async commit (default is not to). async_commit: bool, + /// Is the transaction read only? (Default is no). read_only: bool, + /// How to retry in the event of certain errors. retry_options: RetryOptions, } impl Default for TransactionOptions { fn default() -> TransactionOptions { - Self::new_pessimistic(false) + Self::new_pessimistic() } } impl TransactionOptions { - pub fn new_optimistic(try_one_pc: bool) -> TransactionOptions { + /// Default options for an optimistic transaction. + pub fn new_optimistic() -> TransactionOptions { TransactionOptions { kind: TransactionKind::Optimistic, - try_one_pc, + try_one_pc: false, async_commit: false, read_only: false, retry_options: RetryOptions::default_optimistic(), } } - pub fn new_pessimistic(try_one_pc: bool) -> TransactionOptions { + /// Default options for a pessimistic transaction. + pub fn new_pessimistic() -> TransactionOptions { TransactionOptions { kind: TransactionKind::Pessimistic(0), - try_one_pc, + try_one_pc: false, async_commit: false, read_only: false, retry_options: RetryOptions::default_pessimistic(), } } + /// Try to use async commit. pub fn async_commit(mut self) -> TransactionOptions { self.async_commit = true; self } + /// Try to use 1pc. + pub fn try_one_pc(mut self) -> TransactionOptions { + self.try_one_pc = true; + self + } + + /// Make the transaction read only. pub fn read_only(mut self) -> TransactionOptions { self.read_only = true; self } + /// Don't automatically resolve locks and retry if keys are locked. pub fn no_resolve_locks(mut self) -> TransactionOptions { self.retry_options.lock_backoff = Backoff::no_backoff(); self } + /// Don't automatically resolve regions with PD if we have outdated region information. pub fn no_resolve_regions(mut self) -> TransactionOptions { self.retry_options.region_backoff = Backoff::no_backoff(); self } + /// Set RetryOptions. pub fn retry_options(mut self, options: RetryOptions) -> TransactionOptions { self.retry_options = options; self