Add some docs and move try_one_pc out of options ctor

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron 2020-12-22 11:23:48 +13:00
parent 1c7e843b54
commit f90a82fe35
6 changed files with 84 additions and 37 deletions

View File

@ -5,6 +5,9 @@
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use std::time::Duration; 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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Backoff { pub struct Backoff {
kind: BackoffKind, 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 { pub fn is_none(&self) -> bool {
self.kind == BackoffKind::None self.kind == BackoffKind::None
} }
/// Don't wait. Usually indicates that we should not retry a request.
pub const fn no_backoff() -> Backoff { pub const fn no_backoff() -> Backoff {
Backoff { Backoff {
kind: BackoffKind::None, 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( pub const fn no_jitter_backoff(
base_delay_ms: u64, base_delay_ms: u64,
max_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( pub fn full_jitter_backoff(
base_delay_ms: u64, base_delay_ms: u64,
max_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( pub fn equal_jitter_backoff(
base_delay_ms: u64, base_delay_ms: u64,
max_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( pub fn decorrelated_jitter_backoff(
base_delay_ms: u64, base_delay_ms: u64,
max_delay_ms: u64, max_delay_ms: u64,
@ -152,33 +177,13 @@ impl Backoff {
} }
} }
/// The pattern for computing backoff times.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
enum BackoffKind { enum BackoffKind {
// NoBackoff means that we don't want any retry here.
None, 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, 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, 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, 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, DecorrelatedJitter,
} }

View File

@ -107,7 +107,9 @@ pub use crate::raw::{Client as RawClient, ColumnFamily};
#[doc(inline)] #[doc(inline)]
pub use crate::timestamp::{Timestamp, TimestampExt}; pub use crate::timestamp::{Timestamp, TimestampExt};
#[doc(inline)] #[doc(inline)]
pub use crate::transaction::{Client as TransactionClient, Snapshot, Transaction}; pub use crate::transaction::{
Client as TransactionClient, Snapshot, Transaction, TransactionOptions,
};
#[doc(inline)] #[doc(inline)]
pub use config::Config; pub use config::Config;
#[doc(inline)] #[doc(inline)]

View File

@ -165,7 +165,9 @@ pub trait KvRequest: Request + Clone + Sync + Send + 'static + Sized {
#[derive(Clone, Debug, new, Eq, PartialEq)] #[derive(Clone, Debug, new, Eq, PartialEq)]
pub struct RetryOptions { pub struct RetryOptions {
/// How to retry when there is a region error and we need to resolve regions with PD.
pub region_backoff: Backoff, pub region_backoff: Backoff,
/// How to retry when a key is locked.
pub lock_backoff: Backoff, pub lock_backoff: Backoff,
} }

View File

@ -107,10 +107,11 @@ impl Client {
/// ``` /// ```
pub async fn begin_optimistic(&self) -> Result<Transaction> { pub async fn begin_optimistic(&self) -> Result<Transaction> {
let timestamp = self.current_timestamp().await?; let timestamp = self.current_timestamp().await?;
Ok(self.new_transaction( let mut options = TransactionOptions::new_optimistic();
timestamp, if self.config.try_one_pc {
TransactionOptions::new_optimistic(self.config.try_one_pc), options = options.try_one_pc();
)) }
Ok(self.new_transaction(timestamp, options))
} }
/// Creates a new [`Transaction`](Transaction) in pessimistic mode. /// Creates a new [`Transaction`](Transaction) in pessimistic mode.
@ -132,12 +133,30 @@ impl Client {
/// ``` /// ```
pub async fn begin_pessimistic(&self) -> Result<Transaction> { pub async fn begin_pessimistic(&self) -> Result<Transaction> {
let timestamp = self.current_timestamp().await?; let timestamp = self.current_timestamp().await?;
Ok(self.new_transaction( let mut options = TransactionOptions::new_pessimistic();
timestamp, if self.config.try_one_pc {
TransactionOptions::new_pessimistic(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<Transaction> { pub async fn begin_with_options(&self, options: TransactionOptions) -> Result<Transaction> {
let timestamp = self.current_timestamp().await?; let timestamp = self.current_timestamp().await?;
Ok(self.new_transaction(timestamp, options)) Ok(self.new_transaction(timestamp, options))

View File

@ -11,8 +11,7 @@
pub use client::Client; pub use client::Client;
pub(crate) use lock::{resolve_locks, HasLocks}; pub(crate) use lock::{resolve_locks, HasLocks};
pub use snapshot::Snapshot; pub use snapshot::Snapshot;
pub use transaction::Transaction; pub use transaction::{Transaction, TransactionOptions};
use transaction::TransactionOptions;
mod buffer; mod buffer;
mod client; mod client;

View File

@ -526,6 +526,7 @@ impl Drop for Transaction {
} }
} }
/// Optimistic or pessimistic transaction.
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum TransactionKind { pub enum TransactionKind {
Optimistic, Optimistic,
@ -533,62 +534,81 @@ pub enum TransactionKind {
Pessimistic(u64), Pessimistic(u64),
} }
/// Options for configuring a transaction.
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
pub struct TransactionOptions { pub struct TransactionOptions {
/// Optimistic or pessimistic (default) transaction.
kind: TransactionKind, kind: TransactionKind,
/// Try using 1pc rather than 2pc (default is to always use 2pc).
try_one_pc: bool, try_one_pc: bool,
/// Try to use async commit (default is not to).
async_commit: bool, async_commit: bool,
/// Is the transaction read only? (Default is no).
read_only: bool, read_only: bool,
/// How to retry in the event of certain errors.
retry_options: RetryOptions, retry_options: RetryOptions,
} }
impl Default for TransactionOptions { impl Default for TransactionOptions {
fn default() -> TransactionOptions { fn default() -> TransactionOptions {
Self::new_pessimistic(false) Self::new_pessimistic()
} }
} }
impl TransactionOptions { impl TransactionOptions {
pub fn new_optimistic(try_one_pc: bool) -> TransactionOptions { /// Default options for an optimistic transaction.
pub fn new_optimistic() -> TransactionOptions {
TransactionOptions { TransactionOptions {
kind: TransactionKind::Optimistic, kind: TransactionKind::Optimistic,
try_one_pc, try_one_pc: false,
async_commit: false, async_commit: false,
read_only: false, read_only: false,
retry_options: RetryOptions::default_optimistic(), 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 { TransactionOptions {
kind: TransactionKind::Pessimistic(0), kind: TransactionKind::Pessimistic(0),
try_one_pc, try_one_pc: false,
async_commit: false, async_commit: false,
read_only: false, read_only: false,
retry_options: RetryOptions::default_pessimistic(), retry_options: RetryOptions::default_pessimistic(),
} }
} }
/// Try to use async commit.
pub fn async_commit(mut self) -> TransactionOptions { pub fn async_commit(mut self) -> TransactionOptions {
self.async_commit = true; self.async_commit = true;
self 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 { pub fn read_only(mut self) -> TransactionOptions {
self.read_only = true; self.read_only = true;
self self
} }
/// Don't automatically resolve locks and retry if keys are locked.
pub fn no_resolve_locks(mut self) -> TransactionOptions { pub fn no_resolve_locks(mut self) -> TransactionOptions {
self.retry_options.lock_backoff = Backoff::no_backoff(); self.retry_options.lock_backoff = Backoff::no_backoff();
self self
} }
/// Don't automatically resolve regions with PD if we have outdated region information.
pub fn no_resolve_regions(mut self) -> TransactionOptions { pub fn no_resolve_regions(mut self) -> TransactionOptions {
self.retry_options.region_backoff = Backoff::no_backoff(); self.retry_options.region_backoff = Backoff::no_backoff();
self self
} }
/// Set RetryOptions.
pub fn retry_options(mut self, options: RetryOptions) -> TransactionOptions { pub fn retry_options(mut self, options: RetryOptions) -> TransactionOptions {
self.retry_options = options; self.retry_options = options;
self self