From 7419c9b02b4ab9d26f18c245a612b65fdb6bf1d0 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 25 Oct 2018 18:23:53 +0800 Subject: [PATCH 01/29] TiKV Rust Client Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 190 ++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 text/2018-10-25-tikv-client-rust.md diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md new file mode 100644 index 00000000..38c260d0 --- /dev/null +++ b/text/2018-10-25-tikv-client-rust.md @@ -0,0 +1,190 @@ +# Summary + +Introduce a full featured, official TiKV (and PD) Rust client. It will be intended to be used as a reference implementation, or to provide C-compatible binding for future clients. + +# Motivation + +Currently, users of TiKV must use [TiDB's Go Client](https://github.com/pingcap/tidb/blob/master/store/tikv/client.go), which is not well packaged or documented. We would like to ensure that users can easily use TiKV and PD without needing to use TiDB. + +We think this would help encourage community participation in the TiKV project and associated libraries. During talks with several potential corporate users we discovered that there was an interest in using TiKV to resolve concerns such as caching and raw key-value stores. + +# Detailed design +## Supported Targets + +We will target the `stable` channel of Rust starting in the Rust 2018 edition. We choose to begin with Rust 2018 so we do not need to concern ourselves with an upgrade path. + +We will also support the most recent `nightly` version of Rust, but users should not feel the need to reach for stable unless they are already using it. + +## Naming + +While the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not perscribe any particular crate name convention. + +We choose to name the crate `tikv_client` to conform to the constraints presented to us by the Rust compiler. Cargo permits `tikv-client` and `tikv_client`, but `rustc` does not permit `tikv-client` so we choose to use `tikv_client` to reduce mental overhead. + +Choosing to seperate `tikv` and `client` helps potentially unfamiliar users to immediately understand the intent of the package. `tikvclient`, while understandable, is not immediately parsable by a human. + +All structures and functions will otherwise follow the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/), some of which will be enforced by `clippy`. + +## Installation. + +To utilize the client programmatically, users will be able to add the `tikv-client` crate to their `Cargo.toml`'s dependencies. Then they must use the crate with `use tikv_client;`. Unfortunately due to Rust’s naming conventions this inconsistency is in place. + +To utilize the command line client, users will be able to install the binary via `cargo install tikv-client`. They will then be able to access the client through the binary `tikv-client`. If they wish for a different name they can alias it in their shell. + +## Usage + +## Key structs and traits +Raw Kv Client +```rust +Client { + fn new(config: &Config) -> KvFuture; + fn get(&self, key: K, cf: C) -> KvFuture + where + K: Into, + C: Into>; + fn batch_get(&self, keys: I, cf: C) -> KvFuture> + where + I: IntoIterator, + K: Into, + C: Into>; + fn put(&self, pair: P, cf: C) -> KvFuture<()> + where + P: Into, + C: Into>; + fn batch_put(&self, pairs: I, cf: C) -> KvFuture<()> + where + I: IntoIterator, + P: Into, + C: Into>; + fn delete(&self, key: K, cf: C) -> KvFuture<()> + where + K: Into, + C: Into>; + fn batch_delete(&self, keys: I, cf: C) -> KvFuture<()> + where + I: IntoIterator, + K: Into, + C: Into>; + fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> + where + R: Into, + C: Into>; + fn batch_scan( + &self, + ranges: I, + each_limit: u32, + key_only: bool, + cf: C, + ) -> KvFuture> + where + I: IntoIterator, + R: Into, + C: Into>; + fn delete_range(&self, range: R, cf: C) -> KvFuture<()> + where + R: Into, + C: Into>; +} +``` +Transactional Kv Client +```rust +Client { + fn new(config: &Config) -> KvFuture; + fn begin(&self) -> KvFuture; + fn begin_with_timestamp(&self, _timestamp: Timestamp) -> KvFuture; + fn snapshot(&self) -> KvFuture; + fn current_timestamp(&self) -> Timestamp; +} + +Transaction { + fn commit(&mut self) -> KvFuture<()>; + fn rollback(&mut self) -> KvFuture<()>; + fn lock_keys(&mut self, keys: I) -> KvFuture<()> + where + I: IntoIterator, + K: Into; + fn is_readonly(&self) -> bool; + fn start_ts(&self) -> Timestamp; + fn snapshot(&self) -> KvFuture; + fn get(&self, key: K) -> KvFuture + where + K: Into; + fn batch_get(&self, keys: I) -> KvFuture> + where + I: IntoIterator, + K: Into; + fn seek(&self, key: K) -> KvFuture + where + K: Into; + fn seek_reverse(&self, key: K) -> KvFuture + where + K: Into; + fn set

(&mut self, pair: P) -> KvFuture<()> + where + P: Into; + fn delete(&mut self, key: K) -> KvFuture<()> + where + K: Into; +} + +Snapshot { + fn get(&self, key: K) -> KvFuture + where + K: Into; + fn batch_get(&self, keys: I) -> KvFuture> + where + I: IntoIterator, + K: Into; + fn seek(&self, key: K) -> KvFuture + where + K: Into; + fn seek_reverse(&self, key: K) -> KvFuture + where + K: Into; +} + +``` + +## Programming model + +The client instance is thread safe and all the interfaces return futures so users can use the client in either asynchronous or synchronous way. A dedicated event loop thread is created at per client instance basis to drive the reactor to make progress. + +## Tooling + +The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmarking with criterion in the future. For public functions which process user input, we will seek to use fuzz testing such as `quickcheck` to find subtle bugs. + +The CI will validate all code is warning free, passes `rustfmt`, and passes a `clippy` check without lint warnings. + +All code that reaches the `master` branch should not output errors when the following commands are run: + +```shell +cargo fmt --all -- --check +cargo clippy --all -- -D clippy +cargo test --all -- --nocapture +cargo bench --all -- --test +``` + +# Drawbacks + +Choosing not to create a Rust TiKV client would mean the current state of clients remains the same. + +It is likely that in the future we would end up creating a client in some other form due to customer demand. + +# Alternatives + +## Package the Go client + +Choosing to do this would likely be considerably much less work. The code is already written, so most of the work would be documenting and packaging. Unfortunately, Go does not share the same performance characteristics and FFI capabilities as Rust, so it is a poor core binding for future libraries. Due to the limited abstractions available in Go (it does not have a Linear Type System) we may not be able to create the semantic abstractions possible in a Rust client, reducing the quality of implementations referencing the client. + +## Choose another language + +We can choose another language such as C, C++, or Python. + +A C client would be the most portable and allow future users and customers to bind to the library as they wish. This quality is maintained in Rust, so it is not an advantage for C. Choosing to implement this client in C or C++ means we must take extra steps to support multiple packaging systems, string libraries, and other choices which would not be needed in languages like Ruby, Node.js, or Python. + +Choosing to use Python or Ruby for this client would likely be considerably less work than C/C++ as there is a reduced error surface. These languages do not offer good FFI bindings, and often require starting up a language runtime. We suspect that if we implement a C/C++/Rust client, future dynamic language libraries will be able to bind to the Rust client, allowing them to be written quickly and easily. + +# Unresolved questions + +There are some concerns about integration testing. While we can use the mock `Pd` available in TiKV to mock PD, we do not currently have something similar for TiKV. We suspect implementing a mock TiKV will be the easiest method. + From 64aad0b28b0e1f237f07af502db58e5af461ceb4 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Wed, 31 Oct 2018 23:58:21 +0800 Subject: [PATCH 02/29] Add more about API usage Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 292 +++++++++++++++++++++++----- 1 file changed, 244 insertions(+), 48 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 38c260d0..a3a41e37 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -1,6 +1,6 @@ # Summary -Introduce a full featured, official TiKV (and PD) Rust client. It will be intended to be used as a reference implementation, or to provide C-compatible binding for future clients. +Introduce a full featured, official TiKV (and PD) Rust client. It is intended to be used as a reference implementation, or to provide C-compatible binding for future clients. # Motivation @@ -9,7 +9,7 @@ Currently, users of TiKV must use [TiDB's Go Client](https://github.com/pingcap/ We think this would help encourage community participation in the TiKV project and associated libraries. During talks with several potential corporate users we discovered that there was an interest in using TiKV to resolve concerns such as caching and raw key-value stores. # Detailed design -## Supported Targets +## Supported targets We will target the `stable` channel of Rust starting in the Rust 2018 edition. We choose to begin with Rust 2018 so we do not need to concern ourselves with an upgrade path. @@ -17,7 +17,7 @@ We will also support the most recent `nightly` version of Rust, but users should ## Naming -While the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not perscribe any particular crate name convention. +The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not perscribe any particular crate name convention. We choose to name the crate `tikv_client` to conform to the constraints presented to us by the Rust compiler. Cargo permits `tikv-client` and `tikv_client`, but `rustc` does not permit `tikv-client` so we choose to use `tikv_client` to reduce mental overhead. @@ -25,7 +25,7 @@ Choosing to seperate `tikv` and `client` helps potentially unfamiliar users to i All structures and functions will otherwise follow the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/), some of which will be enforced by `clippy`. -## Installation. +## Installation To utilize the client programmatically, users will be able to add the `tikv-client` crate to their `Cargo.toml`'s dependencies. Then they must use the crate with `use tikv_client;`. Unfortunately due to Rust’s naming conventions this inconsistency is in place. @@ -33,42 +33,85 @@ To utilize the command line client, users will be able to install the binary via ## Usage -## Key structs and traits -Raw Kv Client +## Two types of APIs +TiKV provides two types of APIs for developers: +- The Raw Key-Value API + + If your application scenario does not need distributed transactions or MVCC (Multi-Version Concurrency Control) and only need to guarantee the atomicity towards one key, you can use the Raw Key-Value API. + +- The Transactional Key-Value API + + If your application scenario requires distributed ACID transactions and the atomicity of multiple keys within a transaction, you can use the Transactional Key-Value API. + +Generally the Raw Key-Value API has higher throughput and lower latency compare to the Transactional Key-Value API. If distributed ACID transactions is not required, Raw Key-Value API is preferred over Transactional Key-Value API for better performance and ease of use. + +The client provides two types of APIs in two separate modules for developers to choose from. + +### The common data types + +- Key: raw binary data +- Value: raw binary data +- KvPair: Key-value pair type +- KeyRange: Half-open interval of keys +- Config: Configuration for client + +### Try the Raw Key-Value API + +To use the Raw Key-Value API, take the following steps: + +1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config + +```rust + let config = Config::new(vec!["127.0.0.1:2379"]); +``` + +2. Create a Raw Key-Value client. + +```rust + let client = RawClient::new(&config); +``` + +3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw Key-Value API contains following methods + ```rust -Client { - fn new(config: &Config) -> KvFuture; fn get(&self, key: K, cf: C) -> KvFuture where K: Into, C: Into>; + fn batch_get(&self, keys: I, cf: C) -> KvFuture> where I: IntoIterator, K: Into, C: Into>; + fn put(&self, pair: P, cf: C) -> KvFuture<()> where P: Into, C: Into>; + fn batch_put(&self, pairs: I, cf: C) -> KvFuture<()> where I: IntoIterator, P: Into, C: Into>; + fn delete(&self, key: K, cf: C) -> KvFuture<()> where K: Into, C: Into>; + fn batch_delete(&self, keys: I, cf: C) -> KvFuture<()> where I: IntoIterator, K: Into, C: Into>; + fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> where R: Into, C: Into>; + fn batch_scan( &self, ranges: I, @@ -80,69 +123,222 @@ Client { I: IntoIterator, R: Into, C: Into>; + fn delete_range(&self, range: R, cf: C) -> KvFuture<()> where R: Into, C: Into>; +``` + +#### Usage example of the Raw Key-Value API + +```rust +extern crate futures; +extern crate tikv_client; + +use futures::future::Future; +use tikv_client::raw::Client; +use tikv_client::*; + +fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]); + let raw = raw::RawClient::new(&config) + .wait() + .expect("Could not connect to tikv"); + + let key: Key = b"Company".to_vec().into(); + let value: Value = b"PingCAP".to_vec().into(); + + raw.put((Clone::clone(&key), Clone::clone(&value)), None) + .wait() + .expect("Could not put kv pair to tikv"); + println!("Successfully put {:?}:{:?} to tikv", key, value); + + let value = raw + .get(Clone::clone(&key), None) + .wait() + .expect("Could not get value"); + println!("Found val: {:?} for key: {:?}", value, key); + + raw.delete(Clone::clone(&key), None) + .wait() + .expect("Could not delete value"); + println!("Key: {:?} deleted", key); + + raw.get(key, None) + .wait() + .expect_err("Get returned value for not existing key"); } ``` -Transactional Kv Client + +The result is like: + +```bash +Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv +Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) +Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted +``` + +Raw Key-Value client is a client of the TiKV server and only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. + +### Try the Transactional Key-Value API + +The Transactional Key-Value API is more complicated than the Raw Key-Value API. Some transaction related concepts are listed as follows. + +- Client + + Like the Raw Key-Value client, a Client is client to a TiKV cluster. + +- Snapshot + + A Snapshot is the state of a Client at a particular point of time, which provides some readonly methods. The multiple times read from a same Snapshot is guaranteed consistent. + +- Transaction + + Like the Transaction in SQL, a Transaction symbolizes a series of read and write operations performed within the Client. Internally, a Transaction consists of a Snapshot for reads, and a buffer for all writes. The default isolation level of a Transaction is Snapshot Isolation. + +To use the Transactional Key-Value API, take the following steps: + +1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config + ```rust -Client { - fn new(config: &Config) -> KvFuture; + let config = Config::new(vec!["127.0.0.1:2379"]); +``` + +2. Create a Transactional Key-Value client. + +```rust + let client = TxnClient::new(&config); +``` + +4. (Optional) Modify data using a Transaction. + + The lifecycle of a Transaction is: _begin → {get, set, delete, scan} → {commit, rollback}_. + +5. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following methods: + + ```rust fn begin(&self) -> KvFuture; - fn begin_with_timestamp(&self, _timestamp: Timestamp) -> KvFuture; - fn snapshot(&self) -> KvFuture; - fn current_timestamp(&self) -> Timestamp; -} -Transaction { - fn commit(&mut self) -> KvFuture<()>; - fn rollback(&mut self) -> KvFuture<()>; - fn lock_keys(&mut self, keys: I) -> KvFuture<()> - where - I: IntoIterator, - K: Into; - fn is_readonly(&self) -> bool; - fn start_ts(&self) -> Timestamp; - fn snapshot(&self) -> KvFuture; fn get(&self, key: K) -> KvFuture where K: Into; - fn batch_get(&self, keys: I) -> KvFuture> - where - I: IntoIterator, - K: Into; - fn seek(&self, key: K) -> KvFuture - where - K: Into; - fn seek_reverse(&self, key: K) -> KvFuture - where - K: Into; + fn set

(&mut self, pair: P) -> KvFuture<()> where P: Into; + fn delete(&mut self, key: K) -> KvFuture<()> where K: Into; -} -Snapshot { - fn get(&self, key: K) -> KvFuture - where - K: Into; - fn batch_get(&self, keys: I) -> KvFuture> - where - I: IntoIterator, - K: Into; fn seek(&self, key: K) -> KvFuture where - K: Into; - fn seek_reverse(&self, key: K) -> KvFuture - where - K: Into; + K: Into; Begin() -> Txn + + fn commit(self) -> KvFuture<()>; + + fn rollback(self) -> KvFuture<()>; + ``` + +### Usage example of the Transactional Key-Value API + +```rust +extern crate futures; +extern crate tikv_client; + +use futures::{Async, Future, Stream}; +use tikv_client::transaction::{Client, Mutator, Retriever, TxnClient}; +use tikv_client::*; + +fn puts(client: &TxnClient, pairs: P) +where + P: IntoIterator, + I: Into, +{ + let mut txn = client.begin().wait().expect("Could not begin transaction"); + let _: Vec<()> = pairs + .into_iter() + .map(Into::into) + .map(|p| { + txn.set(p).wait().expect("Could not set key value pair"); + }).collect(); + txn.commit().wait().expect("Could not commit transaction"); +} + +fn get(client: &TxnClient, key: Key) -> Value { + let txn = client.begin().wait().expect("Could not begin transaction"); + txn.get(key).wait().expect("Could not get value") } +fn scan(client: &TxnClient, start: Key, limit: usize) { + let txn = client.begin().wait().expect("Could not begin transaction"); + let mut scanner = txn.seek(start).wait().expect("Could not seek to start key"); + let mut limit = limit; + loop { + if limit == 0 { + break; + } + match scanner.poll() { + Ok(Async::Ready(None)) => return, + Ok(Async::Ready(Some(pair))) => { + limit -= 1; + println!("{:?}", pair); + } + _ => break, + } + } +} + +fn dels

(client: &TxnClient, pairs: P) +where + P: IntoIterator, +{ + let mut txn = client.begin().wait().expect("Could not begin transaction"); + let _: Vec<()> = pairs + .into_iter() + .map(|p| { + txn.delete(p).wait().expect("Could not delete key"); + }).collect(); + txn.commit().wait().expect("Could not commit transaction"); +} + +fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]); + let txn = TxnClient::new(&config) + .wait() + .expect("Could not connect to tikv"); + + // set + let key1: Key = b"key1".to_vec().into(); + let value1: Value = b"value1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + let value2: Value = b"value2".to_vec().into(); + puts(&txn, vec![(key1, value1), (key2, value2)]); + + // get + let key1: Key = b"key1".to_vec().into(); + let value1 = get(&txn, Clone::clone(&key1)); + println!("{:?}", (key1, value1)); + + + // scan + let key1: Key = b"key1".to_vec().into(); + scan(&txn, key1, 10); + + // delete + let key1: Key = b"key1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + dels(&txn, vec![key1, key2]); +} +``` + +The result is like: + +```bash +(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) +(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) +(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) ``` ## Programming model From 57cc178dc8bc582ea6784fc91eab96cf09800d59 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Fri, 2 Nov 2018 15:04:11 +0800 Subject: [PATCH 03/29] Fix some gramma mistakes Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index a3a41e37..e7ee64c5 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -37,7 +37,7 @@ To utilize the command line client, users will be able to install the binary via TiKV provides two types of APIs for developers: - The Raw Key-Value API - If your application scenario does not need distributed transactions or MVCC (Multi-Version Concurrency Control) and only need to guarantee the atomicity towards one key, you can use the Raw Key-Value API. + If your application scenario does not need distributed transactions or MVCC (Multi-Version Concurrency Control) and only needs to guarantee the atomicity towards one key, you can use the Raw Key-Value API. - The Transactional Key-Value API @@ -55,7 +55,7 @@ The client provides two types of APIs in two separate modules for developers to - KeyRange: Half-open interval of keys - Config: Configuration for client -### Try the Raw Key-Value API +### Raw Key-Value API Basic Usage To use the Raw Key-Value API, take the following steps: @@ -191,7 +191,7 @@ The Transactional Key-Value API is more complicated than the Raw Key-Value API. - Snapshot - A Snapshot is the state of a Client at a particular point of time, which provides some readonly methods. The multiple times read from a same Snapshot is guaranteed consistent. + A Snapshot is the state of a Client at a particular point of time, which provides some readonly methods. The multiple reads of the same Snapshot is guaranteed consistent. - Transaction @@ -343,11 +343,11 @@ The result is like: ## Programming model -The client instance is thread safe and all the interfaces return futures so users can use the client in either asynchronous or synchronous way. A dedicated event loop thread is created at per client instance basis to drive the reactor to make progress. +The client instance is thread safe and all the interfaces return futures so users can use the client in either asynchronous or synchronous way. A dedicated event loop thread is created at a per client instance basis to drive the reactor to make progress. ## Tooling -The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmarking with criterion in the future. For public functions which process user input, we will seek to use fuzz testing such as `quickcheck` to find subtle bugs. +The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmark with criterion in the future. For public functions which process user input, we will seek to use fuzz testing such as `quickcheck` to find subtle bugs. The CI will validate all code is warning free, passes `rustfmt`, and passes a `clippy` check without lint warnings. From e82ac4e338fbb2bb84a26ea28828ed9597fea468 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Fri, 2 Nov 2018 21:36:57 +0800 Subject: [PATCH 04/29] Fix indentation Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 432 ++++++++++++++-------------- 1 file changed, 216 insertions(+), 216 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index e7ee64c5..645b9463 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -43,7 +43,7 @@ TiKV provides two types of APIs for developers: If your application scenario requires distributed ACID transactions and the atomicity of multiple keys within a transaction, you can use the Transactional Key-Value API. -Generally the Raw Key-Value API has higher throughput and lower latency compare to the Transactional Key-Value API. If distributed ACID transactions is not required, Raw Key-Value API is preferred over Transactional Key-Value API for better performance and ease of use. +Generally, the Raw Key-Value API has higher throughput and lower latency compared to the Transactional Key-Value API. If distributed ACID transactions are not required, Raw Key-Value API is preferred over Transactional Key-Value API for better performance and ease of use. The client provides two types of APIs in two separate modules for developers to choose from. @@ -61,122 +61,122 @@ To use the Raw Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config -```rust - let config = Config::new(vec!["127.0.0.1:2379"]); -``` + ```rust + let config = Config::new(vec!["127.0.0.1:2379"]); + ``` 2. Create a Raw Key-Value client. -```rust - let client = RawClient::new(&config); -``` + ```rust + let client = RawClient::new(&config); + ``` 3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw Key-Value API contains following methods -```rust - fn get(&self, key: K, cf: C) -> KvFuture - where - K: Into, - C: Into>; - - fn batch_get(&self, keys: I, cf: C) -> KvFuture> - where - I: IntoIterator, - K: Into, - C: Into>; - - fn put(&self, pair: P, cf: C) -> KvFuture<()> - where - P: Into, - C: Into>; - - fn batch_put(&self, pairs: I, cf: C) -> KvFuture<()> - where - I: IntoIterator, - P: Into, - C: Into>; - - fn delete(&self, key: K, cf: C) -> KvFuture<()> - where - K: Into, - C: Into>; - - fn batch_delete(&self, keys: I, cf: C) -> KvFuture<()> - where - I: IntoIterator, - K: Into, - C: Into>; - - fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> - where - R: Into, - C: Into>; - - fn batch_scan( - &self, - ranges: I, - each_limit: u32, - key_only: bool, - cf: C, - ) -> KvFuture> - where - I: IntoIterator, - R: Into, - C: Into>; - - fn delete_range(&self, range: R, cf: C) -> KvFuture<()> - where - R: Into, - C: Into>; -``` + ```rust + fn get(&self, key: K, cf: C) -> KvFuture + where + K: Into, + C: Into>; + + fn batch_get(&self, keys: I, cf: C) -> KvFuture> + where + I: IntoIterator, + K: Into, + C: Into>; + + fn put(&self, pair: P, cf: C) -> KvFuture<()> + where + P: Into, + C: Into>; + + fn batch_put(&self, pairs: I, cf: C) -> KvFuture<()> + where + I: IntoIterator, + P: Into, + C: Into>; + + fn delete(&self, key: K, cf: C) -> KvFuture<()> + where + K: Into, + C: Into>; + + fn batch_delete(&self, keys: I, cf: C) -> KvFuture<()> + where + I: IntoIterator, + K: Into, + C: Into>; + + fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> + where + R: Into, + C: Into>; + + fn batch_scan( + &self, + ranges: I, + each_limit: u32, + key_only: bool, + cf: C, + ) -> KvFuture> + where + I: IntoIterator, + R: Into, + C: Into>; + + fn delete_range(&self, range: R, cf: C) -> KvFuture<()> + where + R: Into, + C: Into>; + ``` #### Usage example of the Raw Key-Value API ```rust -extern crate futures; -extern crate tikv_client; - -use futures::future::Future; -use tikv_client::raw::Client; -use tikv_client::*; - -fn main() { - let config = Config::new(vec!["127.0.0.1:3379"]); - let raw = raw::RawClient::new(&config) - .wait() - .expect("Could not connect to tikv"); - - let key: Key = b"Company".to_vec().into(); - let value: Value = b"PingCAP".to_vec().into(); - - raw.put((Clone::clone(&key), Clone::clone(&value)), None) - .wait() - .expect("Could not put kv pair to tikv"); - println!("Successfully put {:?}:{:?} to tikv", key, value); - - let value = raw - .get(Clone::clone(&key), None) - .wait() - .expect("Could not get value"); - println!("Found val: {:?} for key: {:?}", value, key); - - raw.delete(Clone::clone(&key), None) - .wait() - .expect("Could not delete value"); - println!("Key: {:?} deleted", key); - - raw.get(key, None) - .wait() - .expect_err("Get returned value for not existing key"); -} + extern crate futures; + extern crate tikv_client; + + use futures::future::Future; + use tikv_client::raw::Client; + use tikv_client::*; + + fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]); + let raw = raw::RawClient::new(&config) + .wait() + .expect("Could not connect to tikv"); + + let key: Key = b"Company".to_vec().into(); + let value: Value = b"PingCAP".to_vec().into(); + + raw.put((Clone::clone(&key), Clone::clone(&value)), None) + .wait() + .expect("Could not put kv pair to tikv"); + println!("Successfully put {:?}:{:?} to tikv", key, value); + + let value = raw + .get(Clone::clone(&key), None) + .wait() + .expect("Could not get value"); + println!("Found val: {:?} for key: {:?}", value, key); + + raw.delete(Clone::clone(&key), None) + .wait() + .expect("Could not delete value"); + println!("Key: {:?} deleted", key); + + raw.get(key, None) + .wait() + .expect_err("Get returned value for not existing key"); + } ``` The result is like: ```bash -Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv -Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) -Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted + Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv + Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) + Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted ``` Raw Key-Value client is a client of the TiKV server and only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. @@ -201,144 +201,144 @@ To use the Transactional Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config -```rust - let config = Config::new(vec!["127.0.0.1:2379"]); -``` + ```rust + let config = Config::new(vec!["127.0.0.1:2379"]); + ``` 2. Create a Transactional Key-Value client. -```rust - let client = TxnClient::new(&config); -``` + ```rust + let client = TxnClient::new(&config); + ``` -4. (Optional) Modify data using a Transaction. +3. (Optional) Modify data using a Transaction. The lifecycle of a Transaction is: _begin → {get, set, delete, scan} → {commit, rollback}_. -5. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following methods: +4. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following methods: ```rust - fn begin(&self) -> KvFuture; + fn begin(&self) -> KvFuture; - fn get(&self, key: K) -> KvFuture - where - K: Into; + fn get(&self, key: K) -> KvFuture + where + K: Into; - fn set

(&mut self, pair: P) -> KvFuture<()> - where - P: Into; + fn set

(&mut self, pair: P) -> KvFuture<()> + where + P: Into; - fn delete(&mut self, key: K) -> KvFuture<()> - where - K: Into; + fn delete(&mut self, key: K) -> KvFuture<()> + where + K: Into; - fn seek(&self, key: K) -> KvFuture - where - K: Into; Begin() -> Txn + fn seek(&self, key: K) -> KvFuture + where + K: Into; Begin() -> Txn - fn commit(self) -> KvFuture<()>; + fn commit(self) -> KvFuture<()>; - fn rollback(self) -> KvFuture<()>; + fn rollback(self) -> KvFuture<()>; ``` ### Usage example of the Transactional Key-Value API ```rust -extern crate futures; -extern crate tikv_client; - -use futures::{Async, Future, Stream}; -use tikv_client::transaction::{Client, Mutator, Retriever, TxnClient}; -use tikv_client::*; - -fn puts(client: &TxnClient, pairs: P) -where - P: IntoIterator, - I: Into, -{ - let mut txn = client.begin().wait().expect("Could not begin transaction"); - let _: Vec<()> = pairs - .into_iter() - .map(Into::into) - .map(|p| { - txn.set(p).wait().expect("Could not set key value pair"); - }).collect(); - txn.commit().wait().expect("Could not commit transaction"); -} - -fn get(client: &TxnClient, key: Key) -> Value { - let txn = client.begin().wait().expect("Could not begin transaction"); - txn.get(key).wait().expect("Could not get value") -} - -fn scan(client: &TxnClient, start: Key, limit: usize) { - let txn = client.begin().wait().expect("Could not begin transaction"); - let mut scanner = txn.seek(start).wait().expect("Could not seek to start key"); - let mut limit = limit; - loop { - if limit == 0 { - break; - } - match scanner.poll() { - Ok(Async::Ready(None)) => return, - Ok(Async::Ready(Some(pair))) => { - limit -= 1; - println!("{:?}", pair); + extern crate futures; + extern crate tikv_client; + + use futures::{Async, Future, Stream}; + use tikv_client::transaction::{Client, Mutator, Retriever, TxnClient}; + use tikv_client::*; + + fn puts(client: &TxnClient, pairs: P) + where + P: IntoIterator, + I: Into, + { + let mut txn = client.begin().wait().expect("Could not begin transaction"); + let _: Vec<()> = pairs + .into_iter() + .map(Into::into) + .map(|p| { + txn.set(p).wait().expect("Could not set key value pair"); + }).collect(); + txn.commit().wait().expect("Could not commit transaction"); + } + + fn get(client: &TxnClient, key: Key) -> Value { + let txn = client.begin().wait().expect("Could not begin transaction"); + txn.get(key).wait().expect("Could not get value") + } + + fn scan(client: &TxnClient, start: Key, limit: usize) { + let txn = client.begin().wait().expect("Could not begin transaction"); + let mut scanner = txn.seek(start).wait().expect("Could not seek to start key"); + let mut limit = limit; + loop { + if limit == 0 { + break; + } + match scanner.poll() { + Ok(Async::Ready(None)) => return, + Ok(Async::Ready(Some(pair))) => { + limit -= 1; + println!("{:?}", pair); + } + _ => break, } - _ => break, } } -} - -fn dels

(client: &TxnClient, pairs: P) -where - P: IntoIterator, -{ - let mut txn = client.begin().wait().expect("Could not begin transaction"); - let _: Vec<()> = pairs - .into_iter() - .map(|p| { - txn.delete(p).wait().expect("Could not delete key"); - }).collect(); - txn.commit().wait().expect("Could not commit transaction"); -} - -fn main() { - let config = Config::new(vec!["127.0.0.1:3379"]); - let txn = TxnClient::new(&config) - .wait() - .expect("Could not connect to tikv"); - - // set - let key1: Key = b"key1".to_vec().into(); - let value1: Value = b"value1".to_vec().into(); - let key2: Key = b"key2".to_vec().into(); - let value2: Value = b"value2".to_vec().into(); - puts(&txn, vec![(key1, value1), (key2, value2)]); - - // get - let key1: Key = b"key1".to_vec().into(); - let value1 = get(&txn, Clone::clone(&key1)); - println!("{:?}", (key1, value1)); - - - // scan - let key1: Key = b"key1".to_vec().into(); - scan(&txn, key1, 10); - - // delete - let key1: Key = b"key1".to_vec().into(); - let key2: Key = b"key2".to_vec().into(); - dels(&txn, vec![key1, key2]); -} + + fn dels

(client: &TxnClient, pairs: P) + where + P: IntoIterator, + { + let mut txn = client.begin().wait().expect("Could not begin transaction"); + let _: Vec<()> = pairs + .into_iter() + .map(|p| { + txn.delete(p).wait().expect("Could not delete key"); + }).collect(); + txn.commit().wait().expect("Could not commit transaction"); + } + + fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]); + let txn = TxnClient::new(&config) + .wait() + .expect("Could not connect to tikv"); + + // set + let key1: Key = b"key1".to_vec().into(); + let value1: Value = b"value1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + let value2: Value = b"value2".to_vec().into(); + puts(&txn, vec![(key1, value1), (key2, value2)]); + + // get + let key1: Key = b"key1".to_vec().into(); + let value1 = get(&txn, Clone::clone(&key1)); + println!("{:?}", (key1, value1)); + + + // scan + let key1: Key = b"key1".to_vec().into(); + scan(&txn, key1, 10); + + // delete + let key1: Key = b"key1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + dels(&txn, vec![key1, key2]); + } ``` The result is like: ```bash -(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) -(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) -(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) + (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) + (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) + (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) ``` ## Programming model @@ -354,10 +354,10 @@ The CI will validate all code is warning free, passes `rustfmt`, and passes a `c All code that reaches the `master` branch should not output errors when the following commands are run: ```shell -cargo fmt --all -- --check -cargo clippy --all -- -D clippy -cargo test --all -- --nocapture -cargo bench --all -- --test + cargo fmt --all -- --check + cargo clippy --all -- -D clippy + cargo test --all -- --nocapture + cargo bench --all -- --test ``` # Drawbacks From 43ed16172a9a809f59817031a0d32669e0bca3fe Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Mon, 5 Nov 2018 22:43:18 +0800 Subject: [PATCH 05/29] Fixed a typo Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 645b9463..0717f519 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -13,7 +13,7 @@ We think this would help encourage community participation in the TiKV project a We will target the `stable` channel of Rust starting in the Rust 2018 edition. We choose to begin with Rust 2018 so we do not need to concern ourselves with an upgrade path. -We will also support the most recent `nightly` version of Rust, but users should not feel the need to reach for stable unless they are already using it. +We will also support the most recent `nightly` version of Rust, but users should not feel the need to reach for nightly unless they are already using it. ## Naming From 5368649c4add7a569b48e75c7589ffd244c68df0 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 8 Nov 2018 22:55:18 +0800 Subject: [PATCH 06/29] Do not move key for get, scan and delete methods Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 57 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 0717f519..6754e234 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -76,13 +76,12 @@ To use the Raw Key-Value API, take the following steps: ```rust fn get(&self, key: K, cf: C) -> KvFuture where - K: Into, + K: AsRef, C: Into>; - fn batch_get(&self, keys: I, cf: C) -> KvFuture> + fn batch_get(&self, keys: K, cf: C) -> KvFuture> where - I: IntoIterator, - K: Into, + K: AsRef<[Key]>, C: Into>; fn put(&self, pair: P, cf: C) -> KvFuture<()> @@ -98,35 +97,33 @@ To use the Raw Key-Value API, take the following steps: fn delete(&self, key: K, cf: C) -> KvFuture<()> where - K: Into, + K: AsRef, C: Into>; - fn batch_delete(&self, keys: I, cf: C) -> KvFuture<()> + fn batch_delete(&self, keys: K, cf: C) -> KvFuture<()> where - I: IntoIterator, - K: Into, + K: AsRef<[Key]>, C: Into>; fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> where - R: Into, + R: AsRef, C: Into>; - fn batch_scan( + fn batch_scan( &self, - ranges: I, + ranges: R, each_limit: u32, key_only: bool, cf: C, ) -> KvFuture> where - I: IntoIterator, - R: Into, + R: AsRef<[KeyRange]>, C: Into>; fn delete_range(&self, range: R, cf: C) -> KvFuture<()> where - R: Into, + R: AsRef, C: Into>; ``` @@ -154,20 +151,25 @@ To use the Raw Key-Value API, take the following steps: .expect("Could not put kv pair to tikv"); println!("Successfully put {:?}:{:?} to tikv", key, value); - let value = raw - .get(Clone::clone(&key), None) - .wait() - .expect("Could not get value"); + let value = raw.get(&key, None).wait().expect("Could not get value"); println!("Found val: {:?} for key: {:?}", value, key); - raw.delete(Clone::clone(&key), None) + raw.delete(&key, None) .wait() .expect("Could not delete value"); println!("Key: {:?} deleted", key); - raw.get(key, None) + raw.get(&key, None) .wait() .expect_err("Get returned value for not existing key"); + + let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; + + let values = raw + .batch_get(&keys, None) + .wait() + .expect("Could not get values"); + println!("Found values: {:?} for keys: {:?}", values, keys); } ``` @@ -222,7 +224,7 @@ To use the Transactional Key-Value API, take the following steps: fn get(&self, key: K) -> KvFuture where - K: Into; + K: AsRef; fn set

(&mut self, pair: P) -> KvFuture<()> where @@ -230,11 +232,11 @@ To use the Transactional Key-Value API, take the following steps: fn delete(&mut self, key: K) -> KvFuture<()> where - K: Into; + K: AsRef; fn seek(&self, key: K) -> KvFuture where - K: Into; Begin() -> Txn + K: AsRef; fn commit(self) -> KvFuture<()>; @@ -266,12 +268,12 @@ To use the Transactional Key-Value API, take the following steps: txn.commit().wait().expect("Could not commit transaction"); } - fn get(client: &TxnClient, key: Key) -> Value { + fn get(client: &TxnClient, key: &Key) -> Value { let txn = client.begin().wait().expect("Could not begin transaction"); txn.get(key).wait().expect("Could not get value") } - fn scan(client: &TxnClient, start: Key, limit: usize) { + fn scan(client: &TxnClient, start: &Key, limit: usize) { let txn = client.begin().wait().expect("Could not begin transaction"); let mut scanner = txn.seek(start).wait().expect("Could not seek to start key"); let mut limit = limit; @@ -318,13 +320,12 @@ To use the Transactional Key-Value API, take the following steps: // get let key1: Key = b"key1".to_vec().into(); - let value1 = get(&txn, Clone::clone(&key1)); + let value1 = get(&txn, &key1); println!("{:?}", (key1, value1)); - // scan let key1: Key = b"key1".to_vec().into(); - scan(&txn, key1, 10); + scan(&txn, &key1, 10); // delete let key1: Key = b"key1".to_vec().into(); From 0cfcaffac4dd2442937eff24d7b0aed88a88acd5 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Fri, 9 Nov 2018 21:22:47 +0800 Subject: [PATCH 07/29] Remove KeyRange and use RangeBounds trait instead Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 6754e234..e37d107f 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -107,10 +107,10 @@ To use the Raw Key-Value API, take the following steps: fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> where - R: AsRef, + R: RangeBounds, C: Into>; - fn batch_scan( + fn batch_scan( &self, ranges: R, each_limit: u32, @@ -118,12 +118,13 @@ To use the Raw Key-Value API, take the following steps: cf: C, ) -> KvFuture> where - R: AsRef<[KeyRange]>, + R: AsRef<[B]>, + B: RangeBounds, C: Into>; fn delete_range(&self, range: R, cf: C) -> KvFuture<()> where - R: AsRef, + R: RangeBounds, C: Into>; ``` @@ -170,6 +171,13 @@ To use the Raw Key-Value API, take the following steps: .wait() .expect("Could not get values"); println!("Found values: {:?} for keys: {:?}", values, keys); + + let start: Key = b"k1".to_vec().into(); + let end: Key = b"k2".to_vec().into(); + raw.scan(&start..&end, 10, false, None); + + let ranges = [&start..&end, &start..&end]; + raw.batch_scan(&ranges, 10, false, None); } ``` From aea8d8528ede77d66cabcf4e6207d52222b78978 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Wed, 14 Nov 2018 09:24:27 +0800 Subject: [PATCH 08/29] Use builder style interface for raw kv Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 152 +++++++++++++--------------- 1 file changed, 73 insertions(+), 79 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index e37d107f..aaeb2fee 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -62,75 +62,54 @@ To use the Raw Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config ```rust - let config = Config::new(vec!["127.0.0.1:2379"]); + let config = Config::new(vec!["127.0.0.1:2379"]); ``` 2. Create a Raw Key-Value client. ```rust - let client = RawClient::new(&config); + let client = RawClient::new(&config); ``` 3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw Key-Value API contains following methods ```rust - fn get(&self, key: K, cf: C) -> KvFuture - where - K: AsRef, - C: Into>; + pub trait Client { + type AClient: Client; - fn batch_get(&self, keys: K, cf: C) -> KvFuture> - where - K: AsRef<[Key]>, - C: Into>; + fn get(&self, key: impl AsRef) -> Get; - fn put(&self, pair: P, cf: C) -> KvFuture<()> - where - P: Into, - C: Into>; + fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; - fn batch_put(&self, pairs: I, cf: C) -> KvFuture<()> - where - I: IntoIterator, - P: Into, - C: Into>; + fn put(&self, pair: impl Into) -> Put; - fn delete(&self, key: K, cf: C) -> KvFuture<()> - where - K: AsRef, - C: Into>; + fn batch_put( + &self, + pairs: impl IntoIterator>, + ) -> BatchPut; - fn batch_delete(&self, keys: K, cf: C) -> KvFuture<()> - where - K: AsRef<[Key]>, - C: Into>; + fn delete(&self, key: impl AsRef) -> Delete; - fn scan(&self, range: R, limit: u32, key_only: bool, cf: C) -> KvFuture> - where - R: RangeBounds, - C: Into>; + fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; - fn batch_scan( + fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; + + fn batch_scan( &self, - ranges: R, + ranges: Ranges, each_limit: u32, - key_only: bool, - cf: C, - ) -> KvFuture> + ) -> BatchScan where - R: AsRef<[B]>, - B: RangeBounds, - C: Into>; + Ranges: AsRef<[Bounds]>, + Bounds: RangeBounds; - fn delete_range(&self, range: R, cf: C) -> KvFuture<()> - where - R: RangeBounds, - C: Into>; + fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; + } ``` #### Usage example of the Raw Key-Value API -```rust + ```rust extern crate futures; extern crate tikv_client; @@ -147,47 +126,62 @@ To use the Raw Key-Value API, take the following steps: let key: Key = b"Company".to_vec().into(); let value: Value = b"PingCAP".to_vec().into(); - raw.put((Clone::clone(&key), Clone::clone(&value)), None) + raw.put((Clone::clone(&key), Clone::clone(&value))) + .cf("test_cf") .wait() .expect("Could not put kv pair to tikv"); println!("Successfully put {:?}:{:?} to tikv", key, value); - let value = raw.get(&key, None).wait().expect("Could not get value"); + let value = raw + .get(&key) + .cf("test_cf") + .wait() + .expect("Could not get value"); println!("Found val: {:?} for key: {:?}", value, key); - raw.delete(&key, None) + raw.delete(&key) + .cf("test_cf") .wait() .expect("Could not delete value"); println!("Key: {:?} deleted", key); - raw.get(&key, None) + raw.get(&key) + .cf("test_cf") .wait() .expect_err("Get returned value for not existing key"); let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; - let values = raw - .batch_get(&keys, None) + let _values = raw + .batch_get(&keys) + .cf("test_cf") .wait() .expect("Could not get values"); - println!("Found values: {:?} for keys: {:?}", values, keys); let start: Key = b"k1".to_vec().into(); let end: Key = b"k2".to_vec().into(); - raw.scan(&start..&end, 10, false, None); + raw.scan(&start..&end, 10) + .cf("test_cf") + .key_only() + .wait() + .expect("Could not scan"); let ranges = [&start..&end, &start..&end]; - raw.batch_scan(&ranges, 10, false, None); + raw.batch_scan(&ranges, 10) + .cf("test_cf") + .key_only() + .wait() + .expect("Could not batch scan"); } -``` + ``` The result is like: -```bash + ```bash Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted -``` + ``` Raw Key-Value client is a client of the TiKV server and only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. @@ -212,13 +206,13 @@ To use the Transactional Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config ```rust - let config = Config::new(vec!["127.0.0.1:2379"]); + let config = Config::new(vec!["127.0.0.1:2379"]); ``` 2. Create a Transactional Key-Value client. ```rust - let client = TxnClient::new(&config); + let client = TxnClient::new(&config); ``` 3. (Optional) Modify data using a Transaction. @@ -228,32 +222,32 @@ To use the Transactional Key-Value API, take the following steps: 4. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following methods: ```rust - fn begin(&self) -> KvFuture; + fn begin(&self) -> KvFuture; - fn get(&self, key: K) -> KvFuture - where - K: AsRef; + fn get(&self, key: K) -> KvFuture + where + K: AsRef; - fn set

(&mut self, pair: P) -> KvFuture<()> - where - P: Into; + fn set

(&mut self, pair: P) -> KvFuture<()> + where + P: Into; - fn delete(&mut self, key: K) -> KvFuture<()> - where - K: AsRef; + fn delete(&mut self, key: K) -> KvFuture<()> + where + K: AsRef; - fn seek(&self, key: K) -> KvFuture - where - K: AsRef; + fn seek(&self, key: K) -> KvFuture + where + K: AsRef; - fn commit(self) -> KvFuture<()>; + fn commit(self) -> KvFuture<()>; - fn rollback(self) -> KvFuture<()>; + fn rollback(self) -> KvFuture<()>; ``` ### Usage example of the Transactional Key-Value API -```rust + ```rust extern crate futures; extern crate tikv_client; @@ -340,15 +334,15 @@ To use the Transactional Key-Value API, take the following steps: let key2: Key = b"key2".to_vec().into(); dels(&txn, vec![key1, key2]); } -``` + ``` The result is like: -```bash + ```bash (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) -``` + ``` ## Programming model @@ -362,12 +356,12 @@ The CI will validate all code is warning free, passes `rustfmt`, and passes a `c All code that reaches the `master` branch should not output errors when the following commands are run: -```shell + ```shell cargo fmt --all -- --check cargo clippy --all -- -D clippy cargo test --all -- --nocapture cargo bench --all -- --test -``` + ``` # Drawbacks From 510791f89535f224d27b7941471a126ac7395303 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Wed, 14 Nov 2018 23:13:16 +0800 Subject: [PATCH 09/29] Use impl Trait whenever possible Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index aaeb2fee..b4aca551 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -224,21 +224,13 @@ To use the Transactional Key-Value API, take the following steps: ```rust fn begin(&self) -> KvFuture; - fn get(&self, key: K) -> KvFuture - where - K: AsRef; + fn get(&self, key: impl AsRef) -> KvFuture; - fn set

(&mut self, pair: P) -> KvFuture<()> - where - P: Into; + fn set(&mut self, pair: impl Into) -> KvFuture<()>; - fn delete(&mut self, key: K) -> KvFuture<()> - where - K: AsRef; + fn delete(&mut self, key: impl AsRef) -> KvFuture<()>; - fn seek(&self, key: K) -> KvFuture - where - K: AsRef; + fn seek(&self, key: impl AsRef) -> KvFuture { fn commit(self) -> KvFuture<()>; @@ -255,10 +247,7 @@ To use the Transactional Key-Value API, take the following steps: use tikv_client::transaction::{Client, Mutator, Retriever, TxnClient}; use tikv_client::*; - fn puts(client: &TxnClient, pairs: P) - where - P: IntoIterator, - I: Into, + fn puts(client: &TxnClient, pairs: impl IntoIterator>) { let mut txn = client.begin().wait().expect("Could not begin transaction"); let _: Vec<()> = pairs @@ -294,9 +283,7 @@ To use the Transactional Key-Value API, take the following steps: } } - fn dels

(client: &TxnClient, pairs: P) - where - P: IntoIterator, + fn dels(client: &TxnClient, pairs: impl IntoIterator) { let mut txn = client.begin().wait().expect("Could not begin transaction"); let _: Vec<()> = pairs From bf8ddaba3c449cd4ea0f25e9b47f4530a8b579a9 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 15 Nov 2018 10:24:11 +0800 Subject: [PATCH 10/29] Update example code Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 81 +++++++++++++++-------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index b4aca551..5be72986 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -9,6 +9,7 @@ Currently, users of TiKV must use [TiDB's Go Client](https://github.com/pingcap/ We think this would help encourage community participation in the TiKV project and associated libraries. During talks with several potential corporate users we discovered that there was an interest in using TiKV to resolve concerns such as caching and raw key-value stores. # Detailed design + ## Supported targets We will target the `stable` channel of Rust starting in the Rust 2018 edition. We choose to begin with Rust 2018 so we do not need to concern ourselves with an upgrade path. @@ -29,11 +30,10 @@ All structures and functions will otherwise follow the [Rust API Guidelines](htt To utilize the client programmatically, users will be able to add the `tikv-client` crate to their `Cargo.toml`'s dependencies. Then they must use the crate with `use tikv_client;`. Unfortunately due to Rust’s naming conventions this inconsistency is in place. -To utilize the command line client, users will be able to install the binary via `cargo install tikv-client`. They will then be able to access the client through the binary `tikv-client`. If they wish for a different name they can alias it in their shell. - ## Usage ## Two types of APIs + TiKV provides two types of APIs for developers: - The Raw Key-Value API @@ -49,11 +49,11 @@ The client provides two types of APIs in two separate modules for developers to ### The common data types -- Key: raw binary data -- Value: raw binary data -- KvPair: Key-value pair type -- KeyRange: Half-open interval of keys -- Config: Configuration for client +- `Key`: raw binary data +- `Value`: raw binary data +- `KvPair`: Key-value pair type +- `KvFuture`: A specialized Future type for TiKV client +- `Config`: Configuration for client ### Raw Key-Value API Basic Usage @@ -75,35 +75,35 @@ To use the Raw Key-Value API, take the following steps: ```rust pub trait Client { - type AClient: Client; + type Impl: Client; - fn get(&self, key: impl AsRef) -> Get; + fn get(&self, key: impl AsRef) -> Get; - fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; + fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; - fn put(&self, pair: impl Into) -> Put; + fn put(&self, pair: impl Into) -> Put; fn batch_put( &self, pairs: impl IntoIterator>, - ) -> BatchPut; + ) -> BatchPut; - fn delete(&self, key: impl AsRef) -> Delete; + fn delete(&self, key: impl AsRef) -> Delete; - fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; + fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; - fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; + fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; fn batch_scan( &self, ranges: Ranges, each_limit: u32, - ) -> BatchScan + ) -> BatchScan where Ranges: AsRef<[Bounds]>, Bounds: RangeBounds; - fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; + fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; } ``` @@ -126,7 +126,7 @@ To use the Raw Key-Value API, take the following steps: let key: Key = b"Company".to_vec().into(); let value: Value = b"PingCAP".to_vec().into(); - raw.put((Clone::clone(&key), Clone::clone(&value))) + raw.put((key.clone(), value.clone())) .cf("test_cf") .wait() .expect("Could not put kv pair to tikv"); @@ -185,7 +185,7 @@ The result is like: Raw Key-Value client is a client of the TiKV server and only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. -### Try the Transactional Key-Value API +### Transactional Key-Value API Basic Usage The Transactional Key-Value API is more complicated than the Raw Key-Value API. Some transaction related concepts are listed as follows. @@ -195,7 +195,7 @@ The Transactional Key-Value API is more complicated than the Raw Key-Value API. - Snapshot - A Snapshot is the state of a Client at a particular point of time, which provides some readonly methods. The multiple reads of the same Snapshot is guaranteed consistent. + A Snapshot is the state of the TiKV data at a particular point of time, which provides some readonly methods. The multiple reads of the same Snapshot is guaranteed consistent. - Transaction @@ -230,7 +230,9 @@ To use the Transactional Key-Value API, take the following steps: fn delete(&mut self, key: impl AsRef) -> KvFuture<()>; - fn seek(&self, key: impl AsRef) -> KvFuture { + fn scan(&self, range: impl RangeBounds) -> Scanner; + + fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; fn commit(self) -> KvFuture<()>; @@ -264,29 +266,30 @@ To use the Transactional Key-Value API, take the following steps: txn.get(key).wait().expect("Could not get value") } - fn scan(client: &TxnClient, start: &Key, limit: usize) { - let txn = client.begin().wait().expect("Could not begin transaction"); - let mut scanner = txn.seek(start).wait().expect("Could not seek to start key"); - let mut limit = limit; - loop { - if limit == 0 { - break; - } - match scanner.poll() { - Ok(Async::Ready(None)) => return, - Ok(Async::Ready(Some(pair))) => { + fn scan(client: &TxnClient, range: impl RangeBounds, mut limit: usize) { + client + .begin() + .wait() + .expect("Could not begin transaction") + .scan(range) + .take_while(move |_| { + Ok(if limit == 0 { + false + } else { limit -= 1; - println!("{:?}", pair); - } - _ => break, - } - } + true + }) + }).for_each(|pair| { + println!("{:?}", pair); + Ok(()) + }).wait() + .expect("Could not scan keys"); } - fn dels(client: &TxnClient, pairs: impl IntoIterator) + fn dels(client: &TxnClient, keys: impl IntoIterator) { let mut txn = client.begin().wait().expect("Could not begin transaction"); - let _: Vec<()> = pairs + let _: Vec<()> = keys .into_iter() .map(|p| { txn.delete(p).wait().expect("Could not delete key"); From 5cb6085bdabac926196580d0dabf66a4533ef4ef Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 15 Nov 2018 10:26:46 +0800 Subject: [PATCH 11/29] Update crate name to 'tikv-client' Update crate name to 'tikv-client' because I mistakenly published it under this name and could't unpublish it. Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 5be72986..e765cb34 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -20,9 +20,7 @@ We will also support the most recent `nightly` version of Rust, but users should The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not perscribe any particular crate name convention. -We choose to name the crate `tikv_client` to conform to the constraints presented to us by the Rust compiler. Cargo permits `tikv-client` and `tikv_client`, but `rustc` does not permit `tikv-client` so we choose to use `tikv_client` to reduce mental overhead. - -Choosing to seperate `tikv` and `client` helps potentially unfamiliar users to immediately understand the intent of the package. `tikvclient`, while understandable, is not immediately parsable by a human. +We choose to name the crate `tikv-client`. Choosing to seperate `tikv` and `client` helps potentially unfamiliar users to immediately understand the intent of the package. `tikvclient`, while understandable, is not immediately parsable by a human. All structures and functions will otherwise follow the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/), some of which will be enforced by `clippy`. From 584e88aad11024b50af0234d55a8875a4864165a Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 15 Nov 2018 10:53:10 +0800 Subject: [PATCH 12/29] Add example code to create Config with security Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index e765cb34..b80db560 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -61,6 +61,12 @@ To use the Raw Key-Value API, take the following steps: ```rust let config = Config::new(vec!["127.0.0.1:2379"]); + let config_with_security = Config::with_security( + vec!["127.0.0.1:3379"], + PathBuf::from("/path/to/ca.pem"), + PathBuf::from("/path/to/client.pem"), + PathBuf::from("/path/to/client-key.pem"), + ); ``` 2. Create a Raw Key-Value client. @@ -205,6 +211,12 @@ To use the Transactional Key-Value API, take the following steps: ```rust let config = Config::new(vec!["127.0.0.1:2379"]); + let config_with_security = Config::with_security( + vec!["127.0.0.1:3379"], + PathBuf::from("/path/to/ca.pem"), + PathBuf::from("/path/to/client.pem"), + PathBuf::from("/path/to/client-key.pem"), + ); ``` 2. Create a Transactional Key-Value client. From b2396985d08986fd00ad8f6736ba32efc4c1d547 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 15 Nov 2018 23:02:12 +0800 Subject: [PATCH 13/29] Add transaction isolation level example Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 51 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index b80db560..02400b2c 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -225,14 +225,26 @@ To use the Transactional Key-Value API, take the following steps: let client = TxnClient::new(&config); ``` -3. (Optional) Modify data using a Transaction. +3. (Optional) Modify isolation level of a Transaction. + + ``` rust + #[derive(Copy, Clone, Eq, PartialEq, Debug)] + pub enum IsolationLevel { + SnapshotIsolation, + ReadCommitted, + } + + txn.set_isolation_level(IsolationLevel::ReadCommitted); + ``` + +4. (Optional) Modify data using a Transaction. The lifecycle of a Transaction is: _begin → {get, set, delete, scan} → {commit, rollback}_. -4. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following methods: +5. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following most commonly used methods: ```rust - fn begin(&self) -> KvFuture; + fn begin(&self) -> Transaction; fn get(&self, key: impl AsRef) -> KvFuture; @@ -244,43 +256,41 @@ To use the Transactional Key-Value API, take the following steps: fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; + fn set_isolation_level(&mut self, level: IsolationLevel); + fn commit(self) -> KvFuture<()>; fn rollback(self) -> KvFuture<()>; ``` -### Usage example of the Transactional Key-Value API +#### Usage example of the Transactional Key-Value API ```rust extern crate futures; extern crate tikv_client; - use futures::{Async, Future, Stream}; + use std::ops::RangeBounds; + + use futures::{future, Future, Stream}; use tikv_client::transaction::{Client, Mutator, Retriever, TxnClient}; use tikv_client::*; - fn puts(client: &TxnClient, pairs: impl IntoIterator>) - { - let mut txn = client.begin().wait().expect("Could not begin transaction"); - let _: Vec<()> = pairs - .into_iter() - .map(Into::into) - .map(|p| { - txn.set(p).wait().expect("Could not set key value pair"); - }).collect(); + fn puts(client: &TxnClient, pairs: impl IntoIterator>) { + let mut txn = client.begin(); + let _: Vec<()> = future::join_all(pairs.into_iter().map(Into::into).map(|p| txn.set(p))) + .wait() + .expect("Could not set key value pairs"); txn.commit().wait().expect("Could not commit transaction"); } fn get(client: &TxnClient, key: &Key) -> Value { - let txn = client.begin().wait().expect("Could not begin transaction"); + let txn = client.begin(); txn.get(key).wait().expect("Could not get value") } fn scan(client: &TxnClient, range: impl RangeBounds, mut limit: usize) { client .begin() - .wait() - .expect("Could not begin transaction") .scan(range) .take_while(move |_| { Ok(if limit == 0 { @@ -296,9 +306,8 @@ To use the Transactional Key-Value API, take the following steps: .expect("Could not scan keys"); } - fn dels(client: &TxnClient, keys: impl IntoIterator) - { - let mut txn = client.begin().wait().expect("Could not begin transaction"); + fn dels(client: &TxnClient, keys: impl IntoIterator) { + let mut txn = client.begin(); let _: Vec<()> = keys .into_iter() .map(|p| { @@ -327,7 +336,7 @@ To use the Transactional Key-Value API, take the following steps: // scan let key1: Key = b"key1".to_vec().into(); - scan(&txn, &key1, 10); + scan(&txn, key1.., 10); // delete let key1: Key = b"key1".to_vec().into(); From 78d6100d8c7185b72e06471d40f32538df375c87 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Fri, 16 Nov 2018 10:51:29 +0800 Subject: [PATCH 14/29] Clarify that Raw/Transaction APIs are mutually exclusive Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 02400b2c..9fc1a84c 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -41,7 +41,7 @@ TiKV provides two types of APIs for developers: If your application scenario requires distributed ACID transactions and the atomicity of multiple keys within a transaction, you can use the Transactional Key-Value API. -Generally, the Raw Key-Value API has higher throughput and lower latency compared to the Transactional Key-Value API. If distributed ACID transactions are not required, Raw Key-Value API is preferred over Transactional Key-Value API for better performance and ease of use. +Generally, the Raw Key-Value API has higher throughput and lower latency compared to the Transactional Key-Value API. If distributed ACID transactions are not required, Raw Key-Value API is preferred over Transactional Key-Value API for better performance and ease of use. **Please be aware that these two types of APIs are mutually exclusive. Users should __never__ mix and use these two types of APIs on a __same__ TiKV cluster.** The client provides two types of APIs in two separate modules for developers to choose from. @@ -187,7 +187,7 @@ The result is like: Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted ``` -Raw Key-Value client is a client of the TiKV server and only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. +Raw Key-Value client only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. ### Transactional Key-Value API Basic Usage @@ -203,7 +203,7 @@ The Transactional Key-Value API is more complicated than the Raw Key-Value API. - Transaction - Like the Transaction in SQL, a Transaction symbolizes a series of read and write operations performed within the Client. Internally, a Transaction consists of a Snapshot for reads, and a buffer for all writes. The default isolation level of a Transaction is Snapshot Isolation. + Like Transaction in SQL, a Transaction in TiKV symbolizes a series of read and write operations performed within the service. Internally, a Transaction consists of a Snapshot for reads, and a buffer for all writes. The default isolation level of a Transaction is Snapshot Isolation. To use the Transactional Key-Value API, take the following steps: From cbfceb537504f3ec4d477fb31e283e30e77c5344 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Sat, 17 Nov 2018 00:27:05 +0800 Subject: [PATCH 15/29] Do not use trait in APIs Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 71 +++++++++++++---------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 9fc1a84c..daf83406 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -72,65 +72,57 @@ To use the Raw Key-Value API, take the following steps: 2. Create a Raw Key-Value client. ```rust - let client = RawClient::new(&config); + let client = raw::Client::new(&config); ``` 3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw Key-Value API contains following methods ```rust - pub trait Client { - type Impl: Client; + impl Client { + pub fn new(_config: &Config) -> impl Future + Send; - fn get(&self, key: impl AsRef) -> Get; + pub fn get(&self, key: impl AsRef) -> Get; - fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; + pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; - fn put(&self, pair: impl Into) -> Put; + pub fn put(&self, pair: impl Into) -> Put; - fn batch_put( - &self, - pairs: impl IntoIterator>, - ) -> BatchPut; + pub fn batch_put(&self, pairs: impl IntoIterator>) -> BatchPut; - fn delete(&self, key: impl AsRef) -> Delete; + pub fn delete(&self, key: impl AsRef) -> Delete; - fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; + pub fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; - fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; + pub fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; - fn batch_scan( - &self, - ranges: Ranges, - each_limit: u32, - ) -> BatchScan + pub fn batch_scan(&self, ranges: Ranges, each_limit: u32) -> BatchScan where Ranges: AsRef<[Bounds]>, Bounds: RangeBounds; - fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; + pub fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; } ``` -#### Usage example of the Raw Key-Value API +### Usage example of the Raw Key-Value API ```rust extern crate futures; extern crate tikv_client; use futures::future::Future; - use tikv_client::raw::Client; use tikv_client::*; fn main() { let config = Config::new(vec!["127.0.0.1:3379"]); - let raw = raw::RawClient::new(&config) + let raw = raw::Client::new(&config) .wait() .expect("Could not connect to tikv"); let key: Key = b"Company".to_vec().into(); let value: Value = b"PingCAP".to_vec().into(); - raw.put((key.clone(), value.clone())) + raw.put(key.clone(), value.clone()) .cf("test_cf") .wait() .expect("Could not put kv pair to tikv"); @@ -156,7 +148,7 @@ To use the Raw Key-Value API, take the following steps: let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; - let _values = raw + let values = raw .batch_get(&keys) .cf("test_cf") .wait() @@ -222,7 +214,7 @@ To use the Transactional Key-Value API, take the following steps: 2. Create a Transactional Key-Value client. ```rust - let client = TxnClient::new(&config); + let client = transaction::Client::new(&config); ``` 3. (Optional) Modify isolation level of a Transaction. @@ -244,13 +236,13 @@ To use the Transactional Key-Value API, take the following steps: 5. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following most commonly used methods: ```rust - fn begin(&self) -> Transaction; + pub fn begin(&self) -> Transaction; - fn get(&self, key: impl AsRef) -> KvFuture; + pub fn get(&self, key: impl AsRef) -> impl Future + Send; - fn set(&mut self, pair: impl Into) -> KvFuture<()>; + pub fn set(&mut self, pair: impl Into) -> impl Future + Send; - fn delete(&mut self, key: impl AsRef) -> KvFuture<()>; + pub fn delete(&mut self, key: impl AsRef) -> impl Future + Send; fn scan(&self, range: impl RangeBounds) -> Scanner; @@ -258,12 +250,12 @@ To use the Transactional Key-Value API, take the following steps: fn set_isolation_level(&mut self, level: IsolationLevel); - fn commit(self) -> KvFuture<()>; + pub fn commit(self) -> impl Future + Send; - fn rollback(self) -> KvFuture<()>; + pub fn rollback(self) -> impl Future + Send; ``` -#### Usage example of the Transactional Key-Value API +### Usage example of the Transactional Key-Value API ```rust extern crate futures; @@ -272,10 +264,10 @@ To use the Transactional Key-Value API, take the following steps: use std::ops::RangeBounds; use futures::{future, Future, Stream}; - use tikv_client::transaction::{Client, Mutator, Retriever, TxnClient}; + use tikv_client::transaction::{Client, IsolationLevel}; use tikv_client::*; - fn puts(client: &TxnClient, pairs: impl IntoIterator>) { + fn puts(client: &Client, pairs: impl IntoIterator>) { let mut txn = client.begin(); let _: Vec<()> = future::join_all(pairs.into_iter().map(Into::into).map(|p| txn.set(p))) .wait() @@ -283,12 +275,12 @@ To use the Transactional Key-Value API, take the following steps: txn.commit().wait().expect("Could not commit transaction"); } - fn get(client: &TxnClient, key: &Key) -> Value { + fn get(client: &Client, key: &Key) -> Value { let txn = client.begin(); txn.get(key).wait().expect("Could not get value") } - fn scan(client: &TxnClient, range: impl RangeBounds, mut limit: usize) { + fn scan(client: &Client, range: impl RangeBounds, mut limit: usize) { client .begin() .scan(range) @@ -306,8 +298,9 @@ To use the Transactional Key-Value API, take the following steps: .expect("Could not scan keys"); } - fn dels(client: &TxnClient, keys: impl IntoIterator) { + fn dels(client: &Client, keys: impl IntoIterator) { let mut txn = client.begin(); + txn.set_isolation_level(IsolationLevel::ReadCommitted); let _: Vec<()> = keys .into_iter() .map(|p| { @@ -318,7 +311,7 @@ To use the Transactional Key-Value API, take the following steps: fn main() { let config = Config::new(vec!["127.0.0.1:3379"]); - let txn = TxnClient::new(&config) + let txn = Client::new(&config) .wait() .expect("Could not connect to tikv"); @@ -343,7 +336,7 @@ To use the Transactional Key-Value API, take the following steps: let key2: Key = b"key2".to_vec().into(); dels(&txn, vec![key1, key2]); } - ``` +``` The result is like: From 9215f8c6f6b81283b3f948e8df4cb0a5b0b33de1 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Sat, 17 Nov 2018 00:41:00 +0800 Subject: [PATCH 16/29] Fix formatting issue with wrong indention Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 306 ++++++++++++++-------------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index daf83406..a9a66e54 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -106,78 +106,78 @@ To use the Raw Key-Value API, take the following steps: ### Usage example of the Raw Key-Value API - ```rust - extern crate futures; - extern crate tikv_client; - - use futures::future::Future; - use tikv_client::*; - - fn main() { - let config = Config::new(vec!["127.0.0.1:3379"]); - let raw = raw::Client::new(&config) - .wait() - .expect("Could not connect to tikv"); - - let key: Key = b"Company".to_vec().into(); - let value: Value = b"PingCAP".to_vec().into(); - - raw.put(key.clone(), value.clone()) - .cf("test_cf") - .wait() - .expect("Could not put kv pair to tikv"); - println!("Successfully put {:?}:{:?} to tikv", key, value); - - let value = raw - .get(&key) - .cf("test_cf") - .wait() - .expect("Could not get value"); - println!("Found val: {:?} for key: {:?}", value, key); - - raw.delete(&key) - .cf("test_cf") - .wait() - .expect("Could not delete value"); - println!("Key: {:?} deleted", key); - - raw.get(&key) - .cf("test_cf") - .wait() - .expect_err("Get returned value for not existing key"); - - let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; - - let values = raw - .batch_get(&keys) - .cf("test_cf") - .wait() - .expect("Could not get values"); - - let start: Key = b"k1".to_vec().into(); - let end: Key = b"k2".to_vec().into(); - raw.scan(&start..&end, 10) - .cf("test_cf") - .key_only() - .wait() - .expect("Could not scan"); - - let ranges = [&start..&end, &start..&end]; - raw.batch_scan(&ranges, 10) - .cf("test_cf") - .key_only() - .wait() - .expect("Could not batch scan"); - } - ``` +```rust +extern crate futures; +extern crate tikv_client; + +use futures::future::Future; +use tikv_client::*; + +fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]); + let raw = raw::Client::new(&config) + .wait() + .expect("Could not connect to tikv"); + + let key: Key = b"Company".to_vec().into(); + let value: Value = b"PingCAP".to_vec().into(); + + raw.put(key.clone(), value.clone()) + .cf("test_cf") + .wait() + .expect("Could not put kv pair to tikv"); + println!("Successfully put {:?}:{:?} to tikv", key, value); + + let value = raw + .get(&key) + .cf("test_cf") + .wait() + .expect("Could not get value"); + println!("Found val: {:?} for key: {:?}", value, key); + + raw.delete(&key) + .cf("test_cf") + .wait() + .expect("Could not delete value"); + println!("Key: {:?} deleted", key); + + raw.get(&key) + .cf("test_cf") + .wait() + .expect_err("Get returned value for not existing key"); + + let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; + + let values = raw + .batch_get(&keys) + .cf("test_cf") + .wait() + .expect("Could not get values"); + + let start: Key = b"k1".to_vec().into(); + let end: Key = b"k2".to_vec().into(); + raw.scan(&start..&end, 10) + .cf("test_cf") + .key_only() + .wait() + .expect("Could not scan"); + + let ranges = [&start..&end, &start..&end]; + raw.batch_scan(&ranges, 10) + .cf("test_cf") + .key_only() + .wait() + .expect("Could not batch scan"); +} +``` The result is like: - ```bash - Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv - Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) - Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted - ``` +```bash +Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv +Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) +Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted +``` Raw Key-Value client only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. @@ -257,94 +257,94 @@ To use the Transactional Key-Value API, take the following steps: ### Usage example of the Transactional Key-Value API - ```rust - extern crate futures; - extern crate tikv_client; - - use std::ops::RangeBounds; - - use futures::{future, Future, Stream}; - use tikv_client::transaction::{Client, IsolationLevel}; - use tikv_client::*; - - fn puts(client: &Client, pairs: impl IntoIterator>) { - let mut txn = client.begin(); - let _: Vec<()> = future::join_all(pairs.into_iter().map(Into::into).map(|p| txn.set(p))) - .wait() - .expect("Could not set key value pairs"); - txn.commit().wait().expect("Could not commit transaction"); - } - - fn get(client: &Client, key: &Key) -> Value { - let txn = client.begin(); - txn.get(key).wait().expect("Could not get value") - } - - fn scan(client: &Client, range: impl RangeBounds, mut limit: usize) { - client - .begin() - .scan(range) - .take_while(move |_| { - Ok(if limit == 0 { - false - } else { - limit -= 1; - true - }) - }).for_each(|pair| { - println!("{:?}", pair); - Ok(()) - }).wait() - .expect("Could not scan keys"); - } - - fn dels(client: &Client, keys: impl IntoIterator) { - let mut txn = client.begin(); - txn.set_isolation_level(IsolationLevel::ReadCommitted); - let _: Vec<()> = keys - .into_iter() - .map(|p| { - txn.delete(p).wait().expect("Could not delete key"); - }).collect(); - txn.commit().wait().expect("Could not commit transaction"); - } - - fn main() { - let config = Config::new(vec!["127.0.0.1:3379"]); - let txn = Client::new(&config) - .wait() - .expect("Could not connect to tikv"); - - // set - let key1: Key = b"key1".to_vec().into(); - let value1: Value = b"value1".to_vec().into(); - let key2: Key = b"key2".to_vec().into(); - let value2: Value = b"value2".to_vec().into(); - puts(&txn, vec![(key1, value1), (key2, value2)]); - - // get - let key1: Key = b"key1".to_vec().into(); - let value1 = get(&txn, &key1); - println!("{:?}", (key1, value1)); - - // scan - let key1: Key = b"key1".to_vec().into(); - scan(&txn, key1.., 10); - - // delete - let key1: Key = b"key1".to_vec().into(); - let key2: Key = b"key2".to_vec().into(); - dels(&txn, vec![key1, key2]); - } +```rust +extern crate futures; +extern crate tikv_client; + +use std::ops::RangeBounds; + +use futures::{future, Future, Stream}; +use tikv_client::transaction::{Client, IsolationLevel}; +use tikv_client::*; + +fn puts(client: &Client, pairs: impl IntoIterator>) { + let mut txn = client.begin(); + let _: Vec<()> = future::join_all(pairs.into_iter().map(Into::into).map(|p| txn.set(p))) + .wait() + .expect("Could not set key value pairs"); + txn.commit().wait().expect("Could not commit transaction"); +} + +fn get(client: &Client, key: &Key) -> Value { + let txn = client.begin(); + txn.get(key).wait().expect("Could not get value") +} + +fn scan(client: &Client, range: impl RangeBounds, mut limit: usize) { + client + .begin() + .scan(range) + .take_while(move |_| { + Ok(if limit == 0 { + false + } else { + limit -= 1; + true + }) + }).for_each(|pair| { + println!("{:?}", pair); + Ok(()) + }).wait() + .expect("Could not scan keys"); +} + +fn dels(client: &Client, keys: impl IntoIterator) { + let mut txn = client.begin(); + txn.set_isolation_level(IsolationLevel::ReadCommitted); + let _: Vec<()> = keys + .into_iter() + .map(|p| { + txn.delete(p).wait().expect("Could not delete key"); + }).collect(); + txn.commit().wait().expect("Could not commit transaction"); +} + +fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]); + let txn = Client::new(&config) + .wait() + .expect("Could not connect to tikv"); + + // set + let key1: Key = b"key1".to_vec().into(); + let value1: Value = b"value1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + let value2: Value = b"value2".to_vec().into(); + puts(&txn, vec![(key1, value1), (key2, value2)]); + + // get + let key1: Key = b"key1".to_vec().into(); + let value1 = get(&txn, &key1); + println!("{:?}", (key1, value1)); + + // scan + let key1: Key = b"key1".to_vec().into(); + scan(&txn, key1.., 10); + + // delete + let key1: Key = b"key1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + dels(&txn, vec![key1, key2]); +} ``` The result is like: - ```bash - (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) - (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) - (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) - ``` +```bash +(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) +(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) +(Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) +``` ## Programming model From 6b22b3ed3027b9bb85f38ae16602f523d7d40bba Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Sun, 18 Nov 2018 17:44:02 +0800 Subject: [PATCH 17/29] Fix indention and threading model Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index a9a66e54..58c22b1e 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -348,7 +348,7 @@ The result is like: ## Programming model -The client instance is thread safe and all the interfaces return futures so users can use the client in either asynchronous or synchronous way. A dedicated event loop thread is created at a per client instance basis to drive the reactor to make progress. +The client instance implements `Send + Clone` traits and all the interfaces involve rpc requests return futures so users can use the client in either asynchronous or synchronous way. A dedicated event loop thread is created at a per client instance basis to drive the reactor to make progress. ## Tooling @@ -358,12 +358,12 @@ The CI will validate all code is warning free, passes `rustfmt`, and passes a `c All code that reaches the `master` branch should not output errors when the following commands are run: - ```shell - cargo fmt --all -- --check - cargo clippy --all -- -D clippy - cargo test --all -- --nocapture - cargo bench --all -- --test - ``` +```shell +cargo fmt --all -- --check +cargo clippy --all -- -D clippy +cargo test --all -- --nocapture +cargo bench --all -- --test +``` # Drawbacks From 9b892a384173c9e5080a84894f0dadc9f06aefb0 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Sun, 18 Nov 2018 23:21:04 +0800 Subject: [PATCH 18/29] Use concrete future type for transactional api Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 63 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 58c22b1e..95bc0083 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -79,27 +79,18 @@ To use the Raw Key-Value API, take the following steps: ```rust impl Client { - pub fn new(_config: &Config) -> impl Future + Send; - + pub fn new(_config: &Config) -> Connect; pub fn get(&self, key: impl AsRef) -> Get; - pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; - pub fn put(&self, pair: impl Into) -> Put; - pub fn batch_put(&self, pairs: impl IntoIterator>) -> BatchPut; - pub fn delete(&self, key: impl AsRef) -> Delete; - pub fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; - pub fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; - pub fn batch_scan(&self, ranges: Ranges, each_limit: u32) -> BatchScan where Ranges: AsRef<[Bounds]>, Bounds: RangeBounds; - pub fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; } ``` @@ -236,23 +227,37 @@ To use the Transactional Key-Value API, take the following steps: 5. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following most commonly used methods: ```rust - pub fn begin(&self) -> Transaction; - - pub fn get(&self, key: impl AsRef) -> impl Future + Send; - - pub fn set(&mut self, pair: impl Into) -> impl Future + Send; - - pub fn delete(&mut self, key: impl AsRef) -> impl Future + Send; - - fn scan(&self, range: impl RangeBounds) -> Scanner; - - fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; + impl Client { + pub fn new(config: &Config) -> Connect; + pub fn begin(&self) -> Transaction; + pub fn begin_with_timestamp(&self, timestamp: Timestamp) -> Transaction; + pub fn snapshot(&self) -> Snapshot; + pub fn current_timestamp(&self) -> Timestamp; + } - fn set_isolation_level(&mut self, level: IsolationLevel); + impl Transaction { + pub fn commit(self) -> Commit; + pub fn rollback(self) -> Rollback; + pub fn lock_keys(&mut self, keys: impl AsRef<[Key]>) -> LockKeys; + pub fn is_readonly(&self) -> bool; + pub fn start_ts(&self) -> Timestamp; + pub fn snapshot(&self) -> Snapshot; + pub fn set_isolation_level(&mut self, level: IsolationLevel); + pub fn get(&self, key: impl AsRef) -> Get; + pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; + pub fn scan(&self, range: impl RangeBounds) -> Scanner; + pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; + pub fn set(&mut self, key: impl Into, value: impl Into) -> Set; + pub fn delete(&mut self, key: impl AsRef) -> Delete; + } - pub fn commit(self) -> impl Future + Send; + impl Snapshot { + pub fn get(&self, key: impl AsRef) -> Get; + pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; + pub fn scan(&self, range: impl RangeBounds) -> Scanner; + pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; + } - pub fn rollback(self) -> impl Future + Send; ``` ### Usage example of the Transactional Key-Value API @@ -269,9 +274,13 @@ use tikv_client::*; fn puts(client: &Client, pairs: impl IntoIterator>) { let mut txn = client.begin(); - let _: Vec<()> = future::join_all(pairs.into_iter().map(Into::into).map(|p| txn.set(p))) - .wait() - .expect("Could not set key value pairs"); + let _: Vec<()> = future::join_all( + pairs + .into_iter() + .map(Into::into) + .map(|p| txn.set(p.key().clone(), p.value().clone())), + ).wait() + .expect("Could not set key value pairs"); txn.commit().wait().expect("Could not commit transaction"); } From 80457b2df1c03cbca7cb32afec95e3c20a99b8d2 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Mon, 19 Nov 2018 22:24:08 +0800 Subject: [PATCH 19/29] Change example to reflect changes of Config Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 95bc0083..770ae13d 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -60,9 +60,7 @@ To use the Raw Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config ```rust - let config = Config::new(vec!["127.0.0.1:2379"]); - let config_with_security = Config::with_security( - vec!["127.0.0.1:3379"], + let config = Config::new(vec!["127.0.0.1:3379"]).with_security( PathBuf::from("/path/to/ca.pem"), PathBuf::from("/path/to/client.pem"), PathBuf::from("/path/to/client-key.pem"), @@ -101,11 +99,17 @@ To use the Raw Key-Value API, take the following steps: extern crate futures; extern crate tikv_client; +use std::path::PathBuf; + use futures::future::Future; use tikv_client::*; fn main() { - let config = Config::new(vec!["127.0.0.1:3379"]); + let config = Config::new(vec!["127.0.0.1:3379"]).with_security( + PathBuf::from("/path/to/ca.pem"), + PathBuf::from("/path/to/client.pem"), + PathBuf::from("/path/to/client-key.pem"), + ); let raw = raw::Client::new(&config) .wait() .expect("Could not connect to tikv"); @@ -144,6 +148,7 @@ fn main() { .cf("test_cf") .wait() .expect("Could not get values"); + println!("Found values: {:?} for keys: {:?}", values, keys); let start: Key = b"k1".to_vec().into(); let end: Key = b"k2".to_vec().into(); @@ -193,9 +198,7 @@ To use the Transactional Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config ```rust - let config = Config::new(vec!["127.0.0.1:2379"]); - let config_with_security = Config::with_security( - vec!["127.0.0.1:3379"], + let config = Config::new(vec!["127.0.0.1:3379"]).with_security( PathBuf::from("/path/to/ca.pem"), PathBuf::from("/path/to/client.pem"), PathBuf::from("/path/to/client-key.pem"), @@ -267,6 +270,7 @@ extern crate futures; extern crate tikv_client; use std::ops::RangeBounds; +use std::path::PathBuf; use futures::{future, Future, Stream}; use tikv_client::transaction::{Client, IsolationLevel}; @@ -319,7 +323,11 @@ fn dels(client: &Client, keys: impl IntoIterator) { } fn main() { - let config = Config::new(vec!["127.0.0.1:3379"]); + let config = Config::new(vec!["127.0.0.1:3379"]).with_security( + PathBuf::from("/path/to/ca.pem"), + PathBuf::from("/path/to/client.pem"), + PathBuf::from("/path/to/client-key.pem"), + ); let txn = Client::new(&config) .wait() .expect("Could not connect to tikv"); From b68e34b33edb2af5cc3cdeb4508c28b5a9c4cbc6 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Tue, 20 Nov 2018 17:28:32 +0800 Subject: [PATCH 20/29] Remove `Programming Model` section and make it TBD Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 770ae13d..54a89d2d 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -363,10 +363,6 @@ The result is like: (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) ``` -## Programming model - -The client instance implements `Send + Clone` traits and all the interfaces involve rpc requests return futures so users can use the client in either asynchronous or synchronous way. A dedicated event loop thread is created at a per client instance basis to drive the reactor to make progress. - ## Tooling The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmark with criterion in the future. For public functions which process user input, we will seek to use fuzz testing such as `quickcheck` to find subtle bugs. From bd05fde36a68731794c3e6bdcdb6ed617da4415f Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Wed, 21 Nov 2018 23:01:37 +0800 Subject: [PATCH 21/29] Fix a typo Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 54a89d2d..8608e66a 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -18,7 +18,7 @@ We will also support the most recent `nightly` version of Rust, but users should ## Naming -The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not perscribe any particular crate name convention. +The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not prescribe any particular crate name convention. We choose to name the crate `tikv-client`. Choosing to seperate `tikv` and `client` helps potentially unfamiliar users to immediately understand the intent of the package. `tikvclient`, while understandable, is not immediately parsable by a human. From e3c080ce09320a4f4c65ecbe555b6513ffce63bc Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 22 Nov 2018 12:25:42 +0800 Subject: [PATCH 22/29] Format text to make lines fit into 80 characters Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 148 +++++++++++++++++++++------- 1 file changed, 112 insertions(+), 36 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 8608e66a..653e8e81 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -1,32 +1,53 @@ # Summary -Introduce a full featured, official TiKV (and PD) Rust client. It is intended to be used as a reference implementation, or to provide C-compatible binding for future clients. +Introduce a full featured, official TiKV (and PD) Rust client. It is intended +to be used as a reference implementation, or to provide C-compatible binding +for future clients. # Motivation -Currently, users of TiKV must use [TiDB's Go Client](https://github.com/pingcap/tidb/blob/master/store/tikv/client.go), which is not well packaged or documented. We would like to ensure that users can easily use TiKV and PD without needing to use TiDB. +Currently, users of TiKV must use [TiDB's Go +Client](https://github.com/pingcap/tidb/blob/master/store/tikv/client.go), +which is not well packaged or documented. We would like to ensure that users +can easily use TiKV and PD without needing to use TiDB. -We think this would help encourage community participation in the TiKV project and associated libraries. During talks with several potential corporate users we discovered that there was an interest in using TiKV to resolve concerns such as caching and raw key-value stores. +We think this would help encourage community participation in the TiKV project +and associated libraries. During talks with several potential corporate users +we discovered that there was an interest in using TiKV to resolve concerns such +as caching and raw key-value stores. # Detailed design ## Supported targets -We will target the `stable` channel of Rust starting in the Rust 2018 edition. We choose to begin with Rust 2018 so we do not need to concern ourselves with an upgrade path. +We will target the `stable` channel of Rust starting in the Rust 2018 edition. +We choose to begin with Rust 2018 so we do not need to concern ourselves with +an upgrade path. -We will also support the most recent `nightly` version of Rust, but users should not feel the need to reach for nightly unless they are already using it. +We will also support the most recent `nightly` version of Rust, but users +should not feel the need to reach for nightly unless they are already using it. ## Naming -The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do not prescribe any particular crate name convention. +The [Rust API +Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do +not prescribe any particular crate name convention. -We choose to name the crate `tikv-client`. Choosing to seperate `tikv` and `client` helps potentially unfamiliar users to immediately understand the intent of the package. `tikvclient`, while understandable, is not immediately parsable by a human. +We choose to name the crate `tikv-client`. Choosing to seperate `tikv` and +`client` helps potentially unfamiliar users to immediately understand the +intent of the package. `tikvclient`, while understandable, is not immediately +parsable by a human. -All structures and functions will otherwise follow the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/), some of which will be enforced by `clippy`. +All structures and functions will otherwise follow the [Rust API +Guidelines](https://rust-lang-nursery.github.io/api-guidelines/), some of which +will be enforced by `clippy`. ## Installation -To utilize the client programmatically, users will be able to add the `tikv-client` crate to their `Cargo.toml`'s dependencies. Then they must use the crate with `use tikv_client;`. Unfortunately due to Rust’s naming conventions this inconsistency is in place. +To utilize the client programmatically, users will be able to add the +`tikv-client` crate to their `Cargo.toml`'s dependencies. Then they must use +the crate with `use tikv_client;`. Unfortunately due to Rust’s naming +conventions this inconsistency is in place. ## Usage @@ -35,15 +56,25 @@ To utilize the client programmatically, users will be able to add the `tikv-clie TiKV provides two types of APIs for developers: - The Raw Key-Value API - If your application scenario does not need distributed transactions or MVCC (Multi-Version Concurrency Control) and only needs to guarantee the atomicity towards one key, you can use the Raw Key-Value API. + If your application scenario does not need distributed transactions or MVCC +(Multi-Version Concurrency Control) and only needs to guarantee the atomicity +towards one key, you can use the Raw Key-Value API. - The Transactional Key-Value API - If your application scenario requires distributed ACID transactions and the atomicity of multiple keys within a transaction, you can use the Transactional Key-Value API. + If your application scenario requires distributed ACID transactions and the +atomicity of multiple keys within a transaction, you can use the Transactional +Key-Value API. -Generally, the Raw Key-Value API has higher throughput and lower latency compared to the Transactional Key-Value API. If distributed ACID transactions are not required, Raw Key-Value API is preferred over Transactional Key-Value API for better performance and ease of use. **Please be aware that these two types of APIs are mutually exclusive. Users should __never__ mix and use these two types of APIs on a __same__ TiKV cluster.** +Generally, the Raw Key-Value API has higher throughput and lower latency +compared to the Transactional Key-Value API. If distributed ACID transactions +are not required, Raw Key-Value API is preferred over Transactional Key-Value +API for better performance and ease of use. **Please be aware that these two +types of APIs are mutually exclusive. Users should __never__ mix and use these +two types of APIs on a __same__ TiKV cluster.** -The client provides two types of APIs in two separate modules for developers to choose from. +The client provides two types of APIs in two separate modules for developers to +choose from. ### The common data types @@ -57,7 +88,8 @@ The client provides two types of APIs in two separate modules for developers to To use the Raw Key-Value API, take the following steps: -1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config +1. Create an instance of Config to specify endpoints of PD (Placement Driver) +and optional security config ```rust let config = Config::new(vec!["127.0.0.1:3379"]).with_security( @@ -73,7 +105,8 @@ To use the Raw Key-Value API, take the following steps: let client = raw::Client::new(&config); ``` -3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw Key-Value API contains following methods +3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw +Key-Value API contains following methods ```rust impl Client { @@ -81,11 +114,13 @@ To use the Raw Key-Value API, take the following steps: pub fn get(&self, key: impl AsRef) -> Get; pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; pub fn put(&self, pair: impl Into) -> Put; - pub fn batch_put(&self, pairs: impl IntoIterator>) -> BatchPut; + pub fn batch_put(&self, pairs: impl IntoIterator>) -> BatchPut; pub fn delete(&self, key: impl AsRef) -> Delete; pub fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; pub fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; - pub fn batch_scan(&self, ranges: Ranges, each_limit: u32) -> BatchScan + pub fn batch_scan(&self, ranges: Ranges, each_limit: +u32) -> BatchScan where Ranges: AsRef<[Bounds]>, Bounds: RangeBounds; @@ -170,16 +205,22 @@ fn main() { The result is like: ```bash -Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, 103, 67, 65, 80]) to tikv -Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, 112, 97, 110, 121]) +Successfully put Key([67, 111, 109, 112, 97, 110, 121]):Value([80, 105, 110, +103, 67, 65, 80]) to tikv +Found val: Value([80, 105, 110, 103, 67, 65, 80]) for key: Key([67, 111, 109, +112, 97, 110, 121]) Key: Key([67, 111, 109, 112, 97, 110, 121]) deleted ``` -Raw Key-Value client only supports the GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. +Raw Key-Value client only supports the +GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE +commands. The Raw Key-Value client can be safely and concurrently accessed by +multiple threads. Therefore, for one process, one client is enough generally. ### Transactional Key-Value API Basic Usage -The Transactional Key-Value API is more complicated than the Raw Key-Value API. Some transaction related concepts are listed as follows. +The Transactional Key-Value API is more complicated than the Raw Key-Value API. +Some transaction related concepts are listed as follows. - Client @@ -187,15 +228,21 @@ The Transactional Key-Value API is more complicated than the Raw Key-Value API. - Snapshot - A Snapshot is the state of the TiKV data at a particular point of time, which provides some readonly methods. The multiple reads of the same Snapshot is guaranteed consistent. + A Snapshot is the state of the TiKV data at a particular point of time, +which provides some readonly methods. The multiple reads of the same Snapshot +is guaranteed consistent. - Transaction - Like Transaction in SQL, a Transaction in TiKV symbolizes a series of read and write operations performed within the service. Internally, a Transaction consists of a Snapshot for reads, and a buffer for all writes. The default isolation level of a Transaction is Snapshot Isolation. + Like Transaction in SQL, a Transaction in TiKV symbolizes a series of read +and write operations performed within the service. Internally, a Transaction +consists of a Snapshot for reads, and a buffer for all writes. The default +isolation level of a Transaction is Snapshot Isolation. To use the Transactional Key-Value API, take the following steps: -1. Create an instance of Config to specify endpoints of PD (Placement Driver) and optional security config +1. Create an instance of Config to specify endpoints of PD (Placement Driver) +and optional security config ```rust let config = Config::new(vec!["127.0.0.1:3379"]).with_security( @@ -225,9 +272,12 @@ To use the Transactional Key-Value API, take the following steps: 4. (Optional) Modify data using a Transaction. - The lifecycle of a Transaction is: _begin → {get, set, delete, scan} → {commit, rollback}_. + The lifecycle of a Transaction is: _begin → {get, set, delete, scan} → +{commit, rollback}_. -5. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following most commonly used methods: +5. Call the Transactional Key-Value API's methods to access the data on TiKV. +The Transactional Key-Value API contains the following most commonly used +methods: ```rust impl Client { @@ -250,7 +300,8 @@ To use the Transactional Key-Value API, take the following steps: pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; pub fn scan(&self, range: impl RangeBounds) -> Scanner; pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; - pub fn set(&mut self, key: impl Into, value: impl Into) -> Set; + pub fn set(&mut self, key: impl Into, value: impl Into) -> +Set; pub fn delete(&mut self, key: impl AsRef) -> Delete; } @@ -365,11 +416,16 @@ The result is like: ## Tooling -The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmark with criterion in the future. For public functions which process user input, we will seek to use fuzz testing such as `quickcheck` to find subtle bugs. +The `tikv_client` crate will be tested with Travis CI using Rust's standard +testing framework. We will also include benchmark with criterion in the future. +For public functions which process user input, we will seek to use fuzz testing +such as `quickcheck` to find subtle bugs. -The CI will validate all code is warning free, passes `rustfmt`, and passes a `clippy` check without lint warnings. +The CI will validate all code is warning free, passes `rustfmt`, and passes a +`clippy` check without lint warnings. -All code that reaches the `master` branch should not output errors when the following commands are run: +All code that reaches the `master` branch should not output errors when the +following commands are run: ```shell cargo fmt --all -- --check @@ -380,25 +436,45 @@ cargo bench --all -- --test # Drawbacks -Choosing not to create a Rust TiKV client would mean the current state of clients remains the same. +Choosing not to create a Rust TiKV client would mean the current state of +clients remains the same. -It is likely that in the future we would end up creating a client in some other form due to customer demand. +It is likely that in the future we would end up creating a client in some other +form due to customer demand. # Alternatives ## Package the Go client -Choosing to do this would likely be considerably much less work. The code is already written, so most of the work would be documenting and packaging. Unfortunately, Go does not share the same performance characteristics and FFI capabilities as Rust, so it is a poor core binding for future libraries. Due to the limited abstractions available in Go (it does not have a Linear Type System) we may not be able to create the semantic abstractions possible in a Rust client, reducing the quality of implementations referencing the client. +Choosing to do this would likely be considerably much less work. The code is +already written, so most of the work would be documenting and packaging. +Unfortunately, Go does not share the same performance characteristics and FFI +capabilities as Rust, so it is a poor core binding for future libraries. Due to +the limited abstractions available in Go (it does not have a Linear Type +System) we may not be able to create the semantic abstractions possible in a +Rust client, reducing the quality of implementations referencing the client. ## Choose another language We can choose another language such as C, C++, or Python. -A C client would be the most portable and allow future users and customers to bind to the library as they wish. This quality is maintained in Rust, so it is not an advantage for C. Choosing to implement this client in C or C++ means we must take extra steps to support multiple packaging systems, string libraries, and other choices which would not be needed in languages like Ruby, Node.js, or Python. +A C client would be the most portable and allow future users and customers to +bind to the library as they wish. This quality is maintained in Rust, so it is +not an advantage for C. Choosing to implement this client in C or C++ means we +must take extra steps to support multiple packaging systems, string libraries, +and other choices which would not be needed in languages like Ruby, Node.js, or +Python. -Choosing to use Python or Ruby for this client would likely be considerably less work than C/C++ as there is a reduced error surface. These languages do not offer good FFI bindings, and often require starting up a language runtime. We suspect that if we implement a C/C++/Rust client, future dynamic language libraries will be able to bind to the Rust client, allowing them to be written quickly and easily. +Choosing to use Python or Ruby for this client would likely be considerably +less work than C/C++ as there is a reduced error surface. These languages do +not offer good FFI bindings, and often require starting up a language runtime. +We suspect that if we implement a C/C++/Rust client, future dynamic language +libraries will be able to bind to the Rust client, allowing them to be written +quickly and easily. # Unresolved questions -There are some concerns about integration testing. While we can use the mock `Pd` available in TiKV to mock PD, we do not currently have something similar for TiKV. We suspect implementing a mock TiKV will be the easiest method. +There are some concerns about integration testing. While we can use the mock +`Pd` available in TiKV to mock PD, we do not currently have something similar +for TiKV. We suspect implementing a mock TiKV will be the easiest method. From 04569e87c005ff11d6814a6d91b9851ab5aa667c Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 22 Nov 2018 20:53:09 +0800 Subject: [PATCH 23/29] provide a C-compatible binding for future clients Signed-off-by: Xiaoguang Sun --- text/2018-10-25-tikv-client-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 653e8e81..38dda062 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -1,7 +1,7 @@ # Summary Introduce a full featured, official TiKV (and PD) Rust client. It is intended -to be used as a reference implementation, or to provide C-compatible binding +to be used as a reference implementation, or to provide a C-compatible binding for future clients. # Motivation From 1a7c67f5eff8e0c82106d83dd227ef16f6712695 Mon Sep 17 00:00:00 2001 From: Hoverbear Date: Thu, 20 Dec 2018 15:29:58 -0800 Subject: [PATCH 24/29] Fix lints Signed-off-by: Ana Hobden --- text/2018-10-25-tikv-client-rust.md | 92 ++++++++++++++--------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 38dda062..f6e59ad3 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -1,10 +1,12 @@ -# Summary +# TiKV Client (Rust) + +## Summary Introduce a full featured, official TiKV (and PD) Rust client. It is intended to be used as a reference implementation, or to provide a C-compatible binding for future clients. -# Motivation +## Motivation Currently, users of TiKV must use [TiDB's Go Client](https://github.com/pingcap/tidb/blob/master/store/tikv/client.go), @@ -16,18 +18,16 @@ and associated libraries. During talks with several potential corporate users we discovered that there was an interest in using TiKV to resolve concerns such as caching and raw key-value stores. -# Detailed design +## Detailed design -## Supported targets +### Supported targets -We will target the `stable` channel of Rust starting in the Rust 2018 edition. -We choose to begin with Rust 2018 so we do not need to concern ourselves with -an upgrade path. +We will track the `stable` channel of Rust, using the 2018 edition. We will also support the most recent `nightly` version of Rust, but users should not feel the need to reach for nightly unless they are already using it. -## Naming +### Naming The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/naming.html) do @@ -42,29 +42,26 @@ All structures and functions will otherwise follow the [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/), some of which will be enforced by `clippy`. -## Installation +### Installation To utilize the client programmatically, users will be able to add the `tikv-client` crate to their `Cargo.toml`'s dependencies. Then they must use the crate with `use tikv_client;`. Unfortunately due to Rust’s naming conventions this inconsistency is in place. -## Usage +### Usage -## Two types of APIs +#### Two types of APIs TiKV provides two types of APIs for developers: -- The Raw Key-Value API - - If your application scenario does not need distributed transactions or MVCC -(Multi-Version Concurrency Control) and only needs to guarantee the atomicity -towards one key, you can use the Raw Key-Value API. -- The Transactional Key-Value API - - If your application scenario requires distributed ACID transactions and the -atomicity of multiple keys within a transaction, you can use the Transactional -Key-Value API. +- *The Raw Key-Value API:* If your application scenario does not need + distributed transactions or MVCC (Multi-Version Concurrency Control) and only + needs to guarantee the atomicity towards one key, you can use the Raw + Key-Value API. +- *The Transactional Key-Value API:* If your application scenario requires + distributed ACID transactions and the atomicity of multiple keys within a + transaction, you can use the Transactional Key-Value API. Generally, the Raw Key-Value API has higher throughput and lower latency compared to the Transactional Key-Value API. If distributed ACID transactions @@ -76,7 +73,7 @@ two types of APIs on a __same__ TiKV cluster.** The client provides two types of APIs in two separate modules for developers to choose from. -### The common data types +#### The common data types - `Key`: raw binary data - `Value`: raw binary data @@ -84,12 +81,12 @@ choose from. - `KvFuture`: A specialized Future type for TiKV client - `Config`: Configuration for client -### Raw Key-Value API Basic Usage +#### Raw Key-Value API Basic Usage To use the Raw Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) -and optional security config + and optional security config ```rust let config = Config::new(vec!["127.0.0.1:3379"]).with_security( @@ -106,7 +103,7 @@ and optional security config ``` 3. Call the Raw Key-Value client methods to access the data on TiKV. The Raw -Key-Value API contains following methods + Key-Value API contains following methods ```rust impl Client { @@ -114,21 +111,21 @@ Key-Value API contains following methods pub fn get(&self, key: impl AsRef) -> Get; pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; pub fn put(&self, pair: impl Into) -> Put; - pub fn batch_put(&self, pairs: impl IntoIterator>) -> BatchPut; + pub fn batch_put(&self, + pairs: impl IntoIterator> + ) -> BatchPut; pub fn delete(&self, key: impl AsRef) -> Delete; pub fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; pub fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; - pub fn batch_scan(&self, ranges: Ranges, each_limit: -u32) -> BatchScan - where - Ranges: AsRef<[Bounds]>, - Bounds: RangeBounds; + pub fn batch_scan(&self, + ranges: Ranges, + each_limit: u32 + ) -> BatchScan where Ranges: AsRef<[Bounds]>, Bounds: RangeBounds; pub fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; } ``` -### Usage example of the Raw Key-Value API +#### Usage example of the Raw Key-Value API ```rust extern crate futures; @@ -217,7 +214,7 @@ GET/BATCH_GET/PUT/BATCH_PUT/DELETE/BATCH_DELETE/SCAN/BATCH_SCAN/DELETE_RANGE commands. The Raw Key-Value client can be safely and concurrently accessed by multiple threads. Therefore, for one process, one client is enough generally. -### Transactional Key-Value API Basic Usage +#### Transactional Key-Value API Basic Usage The Transactional Key-Value API is more complicated than the Raw Key-Value API. Some transaction related concepts are listed as follows. @@ -242,7 +239,7 @@ isolation level of a Transaction is Snapshot Isolation. To use the Transactional Key-Value API, take the following steps: 1. Create an instance of Config to specify endpoints of PD (Placement Driver) -and optional security config + and optional security config ```rust let config = Config::new(vec!["127.0.0.1:3379"]).with_security( @@ -276,8 +273,8 @@ and optional security config {commit, rollback}_. 5. Call the Transactional Key-Value API's methods to access the data on TiKV. -The Transactional Key-Value API contains the following most commonly used -methods: + The Transactional Key-Value API contains the following most commonly used + methods: ```rust impl Client { @@ -300,8 +297,10 @@ methods: pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; pub fn scan(&self, range: impl RangeBounds) -> Scanner; pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; - pub fn set(&mut self, key: impl Into, value: impl Into) -> -Set; + pub fn set(&mut self, + key: impl Into, + value: impl Into + ) -> Set; pub fn delete(&mut self, key: impl AsRef) -> Delete; } @@ -314,7 +313,7 @@ Set; ``` -### Usage example of the Transactional Key-Value API +#### Usage example of the Transactional Key-Value API ```rust extern crate futures; @@ -414,7 +413,7 @@ The result is like: (Key([107, 101, 121, 49]), Value([118, 97, 108, 117, 101, 49])) ``` -## Tooling +### Tooling The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmark with criterion in the future. @@ -434,7 +433,7 @@ cargo test --all -- --nocapture cargo bench --all -- --test ``` -# Drawbacks +## Drawbacks Choosing not to create a Rust TiKV client would mean the current state of clients remains the same. @@ -442,9 +441,9 @@ clients remains the same. It is likely that in the future we would end up creating a client in some other form due to customer demand. -# Alternatives +## Alternatives -## Package the Go client +### Package the Go client Choosing to do this would likely be considerably much less work. The code is already written, so most of the work would be documenting and packaging. @@ -454,7 +453,7 @@ the limited abstractions available in Go (it does not have a Linear Type System) we may not be able to create the semantic abstractions possible in a Rust client, reducing the quality of implementations referencing the client. -## Choose another language +### Choose another language We can choose another language such as C, C++, or Python. @@ -472,9 +471,8 @@ We suspect that if we implement a C/C++/Rust client, future dynamic language libraries will be able to bind to the Rust client, allowing them to be written quickly and easily. -# Unresolved questions +## Unresolved questions There are some concerns about integration testing. While we can use the mock `Pd` available in TiKV to mock PD, we do not currently have something similar for TiKV. We suspect implementing a mock TiKV will be the easiest method. - From e4ef825156c218321945a301d621b8984ac59ed2 Mon Sep 17 00:00:00 2001 From: disksing Date: Mon, 24 Dec 2018 18:00:39 +0800 Subject: [PATCH 25/29] add schedule limit design (#13) * add schedule limit design Signed-off-by: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: disksing Co-Authored-By: disksing * Update 2018-09-17-schedule-limit.md Signed-off-by: disksing * Fix lints Signed-off-by: Hoverbear * Update text/2018-09-17-schedule-limit.md Signed-off-by: A. Hobden Co-Authored-By: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: A. Hobden Co-Authored-By: disksing * use Region instead of region Signed-off-by: disksing * Update text/2018-09-17-schedule-limit.md Signed-off-by: Caitin <34535727+caitinchen@users.noreply.github.com> Co-Authored-By: disksing Signed-off-by: Ana Hobden --- media/schedule-limit-new.png | Bin 0 -> 14042 bytes media/schedule-limit-old.png | Bin 0 -> 15817 bytes text/2018-09-17-schedule-limit.md | 176 ++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 media/schedule-limit-new.png create mode 100644 media/schedule-limit-old.png create mode 100644 text/2018-09-17-schedule-limit.md diff --git a/media/schedule-limit-new.png b/media/schedule-limit-new.png new file mode 100644 index 0000000000000000000000000000000000000000..cc8d43ef889c25db29123a184a0e7f1f54ec035d GIT binary patch literal 14042 zcmd6Oc|6qJ`?r0klr2JX7a}4{jZhSoY-L{)vhT*k7)yl;gM?cdl4Kc1wlVgt2-%sz zShE{OmN6L1^BMPjf1ls;eV*U%_s8>kJ+J4F@!8I~&biLH&h@_D*EwDr>g#Z_39`}9 z&~Vvy$2u=yf$`BDA#O7qY9$gc0oROW3G&uR|_pK)cm7C<`ML zRZ9AY&I)6b33e!HnKZdaT7&L9XP4Vatv`^x5h33yz>hy*-aaBt>B=515KYFt+skrg zqwjxWT|k6QWVJt<@LfD)ZrBUU93yAXhV5_PiIs1~{K4m}>`i&P@j6GPZlW@5auU2Y z0n^R6bI|C!u9#n4y|<_pA4-7mTmuR_x9F*a?LQXz-SFdTKA#N4dPuDxxVDsN<$iQ? zx>>*>M7eH-U8SM7$f{D}rhnVeRz}*|A#Ws`L#*$0%y$1MeUNe&Q;)FW+C*Jw^-_(H zq@jRAY-clMyk^-)#6UQQZ`)*@G)*_v(Grz`fc z^0E?UW@Zg^S*jmt>6vS_Zvh24#v)W(|NSxr52wDI;`-|&a0P~l`uIOD^bm$I-|suZ zVQTX2{5_^>v!%KN;Nq%4ugxQL(a{PF&!3uu?+!kqyoJ`6Ts#QdwW;L0!Bxlfh%i?E zxkPK>JU>P7fIPV`Q$?tkduV&bN2gI3!pUx#iJANIlpV{zUH9?3LWCtDbZ;{(^XFls ziD&cp3!BI9C?^&(YYz}J>>|Z9@lt0f3IbhC4nE#4q(650=Qzg;(zro-NBV|X}Pxu)i1XGsjpGNQoN zOu~}9kPUWwqc&ZWQkLsqI-@VtQMU9e2e!8n{pi(SYaUiMSB1;4ec-HEd+I>SN@FSm zd&XbX8II0Os4Fu%)$YH*bs4m5%bkUxOuo81PG%SARM$7}c#9~Vk3@mEEPQb5E_FM~ zFHan~i-a3&E;IV~uQASJr@Rtb#=rCi26`M$)WW~RjTRi zFu~_)9!SYGs8WAl{BdR@iB|F!j4x+Kl_ikvUS3aNqiSWCfTHaP1nXS@!q@Rf>fT4%PO4AYfmicaiGjB*EroHQL zaF(%u5nQTNPV5e`X^Fmy8}fs`lw~Kb>-v@(yr}uaT#a~4{x)O9h`yoj4ZS!SPh7oo zBUrFegiQapJSJi_Gb*v@aXMJk>gLe5tK-?Y*^2G6spCD{r`H0!?Z`Wx795X+KQ!Dq zx9FB8T7}Hc0%xf0B+ai5iuEpzWBYhf5zx_0@4*? zCUF~!eqM)OzdB9Aq>md^nk~BZr}v-Usz4krmxY;%Vvd))EW7*5Ldq|TN!N|EBUA<0 zDZ(nt^Qc+i$Y#Gkb=%yK?=kP;VcS}vcb{+X`j7Qae+D;;wqkBHygix>4q^K)J%%bT zoA{J_oo-oXM}l_nUf&FCR6oq~+r;SlUItZQs>nm9S8I)bvaSxz+?TL>)PH-_$~U-w1r+tLu*ji8ny^eMs_3jYsn*=U$$qBPM5A1W5$R&HeL4q9q3wY|A8EnbxZZ6x^B$a#=>U3oXn&^ z_@T$nQF1NhB@@}l9M}I|7@a1p?#snZFRb% zW8O!EX^x&ajC%<-dl@pxm!fg1V@TZoNQt%<+8io+qOGh2;cXL6MkRE&WEIAn8*_UyF`zsi!KV+XQ>_VtM=bk+c_sl43}Tmw)zmZrQH5Ecu^KvGZItM>>o& zbDH$3&9sDMnpaLin3y!HN23|k6;uoqWQ_Rje1MmG9jKNjX zL5gu&Gil`xqR{5x#)HoVEk8)N!7vbPL5&k6yt*#f(ESV23*+Wp$3aFACrOVY^1Jq$cw!jQs zb`B1DjixvIdTFrQah_H_#lDY&)j~oqkGB2!d-t(`$xsw29I8uVM`VZEmqTw@*YJsHqyVw=)1qFH5ch(0zz_N`O@S=Y9~J3whj(8w z@URnOG@S#iui=zD8(|L@8UB1q4*^z7G*mYBz1d5q3VDdaO3-=5AJ2(6e+jL53!jGT zg}JaX{{DXD6BwotJ@bhK)ibCHN(TDG338i%uKsyd+=hqtk9Z@lQgBt0*b4V_Fwxa% z)>b$LR}(UF78`^GbD(P!vi2>{q-Z42lI62*JYa1@OF$I1JPTNbzm13?)TWPAwf4lD z7qYW9AIe}w2S9Cw8no8XkR8D3O0-Yp&y~cMYeiRps_0k6yh91gR%2 zm{)1Bn9xX`{O`Op!~fGF9Ts3PxZ#X~$NGXvQ`(}=Og@2TFZ%j442;%?<%B>en zbtz`gBP?}d|`so`TtN?kB%*|9U_a#yBz9SJ*I(k3E3 zatIgI;F3IMC^HXLKF07}b~{WjHkF#vEh&zQigMiDn4`7^G(voUaUVSF*Og0lw98q! zMnmlZK(Cy?%Qfu#3DQm0!=M|>$l{ERgV(1e-b!SKCLPCL)cI51?+7tC4CVm)K`fH5hyvjsb9jJD7N;#s!;_6 z1+yK}#Sh39k}}?|MV1wp#^ZBxSBF?jJh_BH?;d2_sM;M)MjvKg_kZf&){IudquB=V zL5t~Ii0(ef-SP)*UQ4#n)e2y=fnLGaM@YZE$2_`HWY?g6P-v9bcDNkvK3w;o|J*4v`fX%@K#OzA|kuKthZtODGgOLuo60(*~HosVv4vc zG&hR6uE;Tr3ZT)p`-Q<-Y0IM!E1&p*H`B5L1ohgK{G?A;>h6e-K8IrErM8G)bh|5VRG4if^h({1VK#4IiCN*lOjA1_B{YXGLZSD?$;aXw=-?={x-+j3(TNaMn^}t zRHhlp@I%=fR2~lq?QGoZf;QEC`En6(Wdb;ciV^7;E0Jsl*`+rMdVOrGvX76qcfeCMs zPkZjQ!i6$9>O2HmFKk+A$LY`8mYv&er5Ac@W(VV?E*4*T*spJ|XS-`mb2*7%73B+L zjas$cCIj318AqAL_SUB~$epKM&(uH$u(R}{D1)fk&Iw^V>mjKIHWuorEiepEhLLI= zyOnSkYeEAVeovMClGql$ul~$NOChxR+iY)^7BC`}M8MmBS-aVa$<9#mw!PB~n^0nZ zdxukxhAB02ZD(WeGjU`30^<#zVj7I}&%2RL-@i6TB{OZ4CWjMh{W}6S_)~?aCNo5v zQZL;;L2Z|p9TGVcJ;Z4s0&~;Yjp^(bt*sZ^{Z(LcynZ>x4B93`U33))%Z#&^W%1D8 zZVkHfF(OorrTK_4%R+B!u%73=eMf?ube5Aig_MtRO5Vr2c=Co@00;zpsCT#6M@2Vp@kM5|XatcxH$fphH!OVSy&~St*-Cx;|zusW@@doOQDq#3ZK^TV$2WA*z1k3;*|g#acKLp3RQ)u@00C06;0+yq z0;F20$oM{0MOpca#?R8Qqr-qIS{UGf+taW6_a8Aj)0co)x06comEHyey+_Xv9pcrn zPek`}WHkKj%3X{`<$I5G;Aig*4B7poWG3wxqc=q+uYRjvcWJ{DkIv~-q~phUA{^S_ z7hsfaPus~*9<7hcH?0JhPjZOdz%%^OV- z8sqH6vYJ;1xL#esKd*@!9J=+@rw(+IjqHB#J%&HG6FeE({`SJs5jA?Lh9A;Fs2Lld zwjGWs*{^O-(hnyqz3r-N^Yf*IZScWXm0o%hHLj)Uji}yQz1lJ`Lm+z&QtPAQRzv@w zyEw8ndcB-()0Pft@fUY z#FPPdZW+cAQz-HI$ zoXql7DVDU5u?YpKytYZTk~Kqx;FX>77Hhcpjvvwh7>U%g#DZ7LL+YeWhpx_y>%ZNq zWw_WUYh2woy4Hvu-1B>j5~*LryhsyfMdJzE#8t{=)eKiI?&YJUviZ-v4?4F-f(N%7 z1X*C%L*Dy#J~mqKQQIGe{>)44t5-VDU#R5vwR~ITMh5)iFFf6Z#!kDg)Yv4Ya`Szv zg&@da)vE2IL;I+0TaIKneEO>NnwKx<<;ym9HvEy74}unt8r-eZy^xCgkWmP>qIAE1 z*RAF#VBjs^y0hWDLP7d3dOtTblElAG& zZbbvP_ZdW#fJquy=DrT^HR-XVZmEHRqlz$3B-e`*iOXlpGvImzR}ITA+J<4^)#bw{ zjxmIf{$16k9sI(``hh^u+RmsXrz#)f#dfx~4h4$_iK^O;hoUD&BKU;C)rrd;pS@&IB1u(pKy}CEWu*#Ji=T5dGVv z_T@XkxRF{mv=)lM`<|SLhUK(*-o@H-QE*5=?cP~jt|$=(a18PWYp0#5%8ko~+ZM@& zk>-)OkGZy`Z1rr#h4-^EPzE}@rUdiISfR?oR#~L{!FS@4)VlHw{cvKC7OuoQLY7&NB8i|v;QTjAP=z4z#SBn{>c9Y<}Fz<&8JLuO* z10j+DMFFUe6H1|>w{sI;V9OFedgc=B+j2Z-;P4H@wiMncMSoz(#BtT7i8|;*8ykdm z*a4|4{#P;`@%f7lb0hKHfGOF8J$MRO#8c75br+PYk6O;^xCtojvjHgHnW_XU?or?P z!)LEt@yXBv+RNTT=<&j*Ba`m^Srg<3)2BtDyL+D%kc2-TQ~b;JK5zD}y9r$;C(6Nc z{ZGwWAq}u;8>IG8ZsZXf@rl+n)8po6uJmGqj+iGs5Fl)?)ABKUk=6QLMcwosC%j+xK~Y4_+M>CGy*MKb zd-oP+P!@C_d#)`miFP8|u}hHl+0-}O{j9q%xOcLo90SaKs;nE2iW4loim)^EZ;I^F z817F__x{oEzvghYhWUhkSojwnpZLzzrN(iP!#cxPjO_Qyho${*2uV{*mE3tNn0KLA zkZ_TK2+?;?m(-qW0iIx-xpoz^%hy`bvxCqIXi+JroHr;$OG3)$Pp3$k5!Rx(&-+>*u`(*qGh#GyB^_7U&Q!^F>GpjC(fP zQ;e7|5(McWQ=wSrPULyk+2tmyoL&}FUPIndGX+u#??x#Ub7_%Hh2MR4LImTiSNe$g zTYrCvo7$w96oa;B>d>vYc)fbXC(`aScuj2 zZY;kX`u$dC9yD)sWM7jJjrR!f?w{1|3taxVgO|U-W)_UGx?62M<+UmQtBN1+=%D8H z)oVw7A23=)J)!#H4att=5~VjiY@Kb?o-d(Om-TLx*>>Vjr^L4`M>cO*klNQC;ck=K zE0d^b$T(*5+Bk1$Sjc)cH2u=26LR=jL5-ycjJAw;vp+FUzL7uoFc6tEg>%Q{9?ex+ zVCpy6UxFl*?)zRaof{02qMMe^?@}NWkkgx+CkpLw`Jh8P7IOkZV4xV4=!r=4W1InJst4_85lsbc z78pD?-)G4fk^l#Dwg%p~v{tV^a#(cml`tzYxFql{Wt2yH%>s8X`>AkE@oA9$ki?{K z1;Fhkj6I?Puhdb(yVk|p4aVzqm zoMbSR9>dQ}ykr%qVX<4d|IosCBK7px%-&}|k2!gD*<@X$hA*uD2>AIWn?ZR7m-9u( z@iSR~+S$(a3GF!umXals|g^>mHAW<$LgWh3L^WWWS+$ zX7>#~s!^_Tg54Hp7R-mPII2m@HX;>vQ#JD5?xo&#tox=F{xGt{$Q!Gx^2_L!DQGkm zZq`|($y?oqg2!M797Un+cd%;Oljh4%=#oy>YlZE8&Z1)fH}UuehCf8^;RD_7222DJ z53i5xSXPj8gd#Im$#Ny_Als>l_0(ZUnDefaoYljiIJ+mO7qaWDJEj6aa^GGP9)(x~ zD?b@FlvbGg(8Jg>$+hS#Ux7H2V~!^&zxNL_yUc*1U(T&%sH6%bU>wlCuu{m0oi;;W zqp$qF2z}TaJ2r4Ni4~wvp}eTZ7=>T@uk?177Hf*ih*b_#w(Z!}G|YtgVpUxtLR0qp zA6G+pxc~%Zi>!$(1^4LI1(JU7>KpI$Os-K=mY7OU=nI5)o@wTmMb^_W4M9LG!^O~mES`13 zo%`Wi#PJ$k)AjFOxl&`DpdpygyDaQj;}1 z>D%?N-Kg!9oc2X|honP#rZ3Zdy7ICS%ytTYoC#Q;0*FGZM5RoP(#YmO9m2HxcoH3~ z?T_anR-SrV2G|-tP6_=VE;d~NQ=SDqKK_*ai{JK{*coLH0URAQR=|_$2kv{cAW{sxd%un^{`XZ#R21H1d&1T`bC`Q^^+_Z(tob;?xeE*vz1&y4wNT zt-G!J=r}p!+h=WOmkA$b+zHy8_tiUJY+DBcHpL;qFeVOxgsoM)${Dp_#q8i?{y(Sc zrm8pzjX<1(k_s;y6aC#X)yJu!19DE5jvQ zdmO?b#WWDu4Wx5xq9IHJ$9!y(K5HH1$zyt-^p?tjqfUrs$kSNW*OxbIWD68Vk7k28hwz#^xs0V?^UU2l0Iyr=Kg*m7CFx! zeZxFm+&CWCv&Wdly0x&_mMyamiBk^&@P3w}{ar1g@v1Z8_-mAmaj^v{?z$%Fc<2B7 z-JTXg)ib^758wXjQ*aqVDK^N=n$Z9Kcvi}|;qsbVm5Qxv3?Pw~ z%+^;pCX^L?Ajz2(44e;WtkX0`KxY;lm5`y_)H;UpQ}vj}jXf8|>*BFjROVs+5^;*) z`06pcC6Me|)tOF0vyvi-9p~wQfe$EP&GHVHYTJ0$cb;94)nq}u@R2JYN70;hy))4h zotSej%#Il6OlGBOy*n)?&X|b zlaS5lEcLUUA-Bxwh9n!fq`=Ke=$fi|KZfRa7vFI+)dk==}-{vV$hi{Jf8v zU)E{g8-?zDFIpvw-urm@`~RHMVaY0ut~rNM3!l!Q>97u8h_SC~ZY-`zoNwxFWH&8l z#IQ4*a9-*8=$*aqOn+|6i&R`=w3%K|K6DS1h_@u+8>x(0)^-u zJr1gfM^QAF=^-%l&}Kxudc!Fiw0vX1@bZE-K`G0>^nCh)NM**jPI1h}7SGmLO80mF&$){tX8!jQV}3`;Lbz7PBurbDpjzTJb=B{A~ZvDmbpi9&!4*Z9HJ9 zHgv=?`YS7ytw}7S>wz_XmnDKM%QBFI%trA>J1*jEJ%dC%K++^;;- zr@6E7yLxSyw6Kdrm7?ctsNyI}EcTV!SX-n+rP1Oi4vcdgP%RNEHC2CwWv!a?;-}QI&*k&GXT(I3r9SSBiV7h}OZ6{Ry4f{+4NN{7pyG&-scUGm%AX z@XplXlkl}MntUAHv!~H(f|@g4qVq?0b~N# zKY27M&`q{rURm~8sc9xzok1;xsJuD!N6o?Zx$RXy7)-5EbQ?I^$`#J0q6P|mx@=jG za4$m!*C|k9TvZAgh&VJ^z}%P2Q^#ebwt9LeuEodLbO;gVF z)yv$jm%;g6gvvtvo`%B0lP?P;me@hjp;@ZY!76)lidOZ;@v~hlln=by@-0oHPdno~ zD$>2bUTF+?fSTig>UvvX;D>*D&Z?_;SS{2eS#gaSUO%e1%=8u&g*%|Y+*q4?$JVHm zh`~?0QZfxlw}4YSwB&B%kRD+~#+NzEs5t{O2hDN^ok3UNm=wcUkNG-&rFSV`)+{}h zB*Qj0g&u2{WCcD=!K*QY?GN*8$qUZD*QxF}yCacULs+l#yzZ3-V1$nj?*+a6GK-Dc zJSOTXtb&tL#>M;_izRhuthuP(JkRg5VO>T8j=~w`_qTuq&COt}P1c^%u}fzdGx~m4 zuDnl<=xTbvBg+gpXo&IkQpg+uTwn3G4293V`@hE8CyOPhh|wr)^6Ebz2GE87h#0`v z|A-jJ;{O-Kn1Wx@`5Q4Z>}YONmArJDtxJyGsVIU9Ie-8-PXVsDYsqZkqR&4{hsxRh z3o!t#{tYnz#m^4S0Whe6APfAX18^j%sdlaX7#D#ywAqG=Ih@`asvT>1OE8fPuNCcq z@=y_nTEB~PZnL}A9dni?OEy20AG^Q*>DGZA;Q<7IaSaeOGkK~Zq;syv=!vc!nwfKj zTwE7yasDIfPQ@5VgLBEMWfqBa}i0B=_fUDUgv? z=XqT><~se?cV01_#E5q&`{=Or(a=FUlz~mQKAI;}`XfN#ASQT|V@wJUoIJiw|0+V& zNeGC8U%JplsDAPQZccKZ^U3u90ukVbiTKVF2vk1NerrUO#sT)P7%qT$ga(4X|5|?I zlbwOgTJD z-_H1cn2TMeg`bw+{_oS51shv5!W0ke!e+{0;%ST!U8{xQ@#Y4-p3bnNeT}0>g6D@y z2=5oiejU(|QIF7~V_y~Ol(P3t(~J)@^wY{qixeI^l^RyT_H=~a#;g4F)?XMIpHz1e z&V(O|sQosNDTV!7L~R}IT$?FHFhX(;s&Y7&s+QBiMBfo~aCWI(Y$@j$_oV?QLmC2( z{Jh-g)ZMn&!;HUYVX4E|Wm$l9KxMJiT|znV_XY0K938T?Vr$N5nZ1jE{e$?n;9vNzep758J; zr9G9xM;p3(up{M%+n)n}HwI+xAxT5Pze}LZ(2O2)nH}hk2~3Ijz`p7AuV7=Y!Uucc zLXX|jqK)F77fjo_srlJ1OLw|=cq&y`g*;noxILQz(%Viz* z?oO&vlnhaC4Oe;q zc{+nN6XI20n|dLUJ>JBrg8DoTw*V2yGgUUoP#UYZ(D9?0i{z?rz;SAUf6+(G9-MPl=AQt=U3trWGxSr>(jnlzrNmUM(>E=oJ>rZ>77e! z97mT#bz6zczo=3Qx0tq?ny8pU>1W?8(s#BdIwoJ8tLU%^A+&l)Su9*d{*+Yscr+AY z2vWcA1paoj=m1GSrFzPg8R!#IYh9TMCRArNvYp5dj9#R2c)@SCTH_@~-^kOv_shB6 zp%;lfmTfAQY(oTpF=SSSWK&Aq5$RnykKYQ*SdqQ-?6W%fL<|GbxxoV|zR-!@*g@J^ zj*b~!9+dJl7{MwC(c%~D6IRtBVl>i4@J1WboJX=J&{CRmxN@+eR$d>nIZ8di>;VQT z#+y)9$ke8}yMFuQcJuf=Q#ljlplh@tZZHDslkQ7g~3{Q$#wy*)03o4!c z5y8m&eMsPBYX9p@t`dh{wcs45BueRgT{$BphRd;Oe4fU!jzwqFHRIWqd!ulcKPRzZ z<$V=Fg2z=M@o9`zi`dkj_Pw|i?dd?dVl^MLmfV0r->(S`hivdT4XCZyTA{_FrCbKs%l=tT_SSw` z^Pzfv^Ma+$6mFrgoF}C+K=I)tOZD`8t~YC+hkx89xTua>zH;WW60|5o0%-wh{H3e) z9?lVZorNCA+TB_?V|0a#tSVnrr9v8sqv|G-y8>&pGdW-L&Bo7L} literal 0 HcmV?d00001 diff --git a/media/schedule-limit-old.png b/media/schedule-limit-old.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ac00e559a933629c3acbb71c83a1916435a002 GIT binary patch literal 15817 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 W!m$cISIOxih4x)N%`$b{7yk!u^M!H% literal 0 HcmV?d00001 diff --git a/text/2018-09-17-schedule-limit.md b/text/2018-09-17-schedule-limit.md new file mode 100644 index 00000000..1644a1d7 --- /dev/null +++ b/text/2018-09-17-schedule-limit.md @@ -0,0 +1,176 @@ +# Schedule Limit + +## Summary + +This RFC proposes a new scheme to limit concurrent executing schedule +operators. Compared to old implementation, it is more intuitive, easy to +configure, and has better adaptability. + +## Motivation + +In order to prevent PD from generating a large number of schedule operators in +a short period of time and affecting the performance of the TiKV cluster, and +to avoid the problem of over-balance, currently PD uses a limitation based +scheme. + +Specifically, PD uses 4 different limit configurations to control the number of +concurrently running operators: + +* _leader-schedule-limit_ + + Controls the number of operators scheduling Region leaders at the same + time. + +* _region-schedule-limit_ + + Controls the number of operators scheduling Region peers at the same time. + +* _replica-schedule-limit_ + + Controls the number of operators scheduling Region replicas at the same + time. + +* _merge-schedule-limit_ + + Controls the number of Region merge operators at the same time. + +![Old schedule limit scheme](../media/schedule-limit-old.png) + +The mechanism of scheduling limiter is shown in the figure. All running +operators are maintained as a map in the coordinator, and the Limiter is +updated synchronously when running operators change. Different schedulers will +check whether running operators have exceeded the limit before generating +operators. The operators generated by schedulers will be added to the +_runningOperators_ map via addOperator method. + +This scheme is not good in these aspects: + +* Various limit configurations are confusing, tricky to use correctly +* Fixed limit configuration does not apply to clusters of different sizes +* The preemption strategy between different schedulers is not fair enough +* Scheduling is often concentrated on a small number of `tikv-server`s, which + leads to slow scheduling sometimes and may affect the latency of TiKV (we do + consider snapshot count, but this statistic is reported after TiKV receives + the scheduling request) +* Rely on schedulers correctly checking limit to ensure the limit configuration + has not been violated -- hotbed of bugs. See + [#1193](https://github.com/pingcap/pd/pull/1193) + [#1155](https://github.com/pingcap/pd/pull/1155). + +## Detailed design + +### Introducing waitingOperators queue + +![New schedule limit scheme](../media/schedule-limit-new.png) + +As shown in the figure, the biggest change is the introduction of the +_waitingOperators_ in the coordinator. The schedule operators generated by +schedulers are not directly put into the _runningOperators_, but are queued +first in _waitingOperators_. The coordinator is responsible for continuously +promoting operators in _waitingOperators_ queue to _runningOperators_ according +to a certain rule and configuration. + +### Operator effect evaluation + +Schedule operators consist of operator steps. The effect of an operator on the +cluster can be calculated as the sum of the effects of all steps. There are +several types of operator steps: + +* _TransferLeader_ +* _AddPeer_ +* _AddLearner_ +* _PromoteLearner_ +* _RemovePeer_ +* _MergeRegion_ +* _SplitRegion_ + +These steps only affect a subset of all `tikv-server`s -- not the +entire cluster. For example, _TransferLeader_ can only affect the original and +the new leader of the Region. _AddLearner_ can affect the store to add the +learner and the leader of the Region. + +The overhead of different types of operator are different. We can +assign different cost values to them based on experience. For example, we can +arbitrarily set the cost of _TransferLeader_ to 1, the cost of _RemovePeer_ to +2, and the cost of _AddLearner_ to 5 (leader) and 8 (new peer). + +### How to promote waiting operators + +Given a _MaxScheduleCost_ configuration (say 20), when the coordinator promotes +an operator, it needs to guarantee that if we add all the schedule cost in the +_runningOperators_, the total cost of any store will not exceed the configured +value. + +If the first operator in the waiting queue makes a store overload, it will wait +until the conflicting operator finishes or times out before it can be moved to +_runningOperators_. + +In order to improve efficiency of executing, when a non-first operator does not +conflict with any operator in front of it, it can be moved to +_runningOperators_ too. + +### How to ensure fair competition between different schedulers + +This is actually quite straightforward. We only need to limit the number of +generated operators by each scheduler in the _waitingOperators_ queue. For +example, up to 3 operators from a same scheduler. + +Only when an old operator is moved into the _runningOperators_, the +corresponding scheduler will have the opportunity to insert more operators into +_waitingOperators_. This strategy not only ensures fairness between different +schedulers, but also encourages schedulers to generate non-conflicting +operators, thus reducing the impact on cluster performance. + +As before, if different schedulers generate operators of a same Region, the +later one is rejected directly. Dropping the later operator when it is about +to promote is also an option. + +### Configuration and customization + +There is only 1 important configuration for basic usage: + +* _MaxScheduleCost_ + + The maximum summary of cost of running operators for each store. + +We can also allow to customize following configurations to better fit special +needs. (Tests are needed to decide if each of them is worth being made +configurable): + +* _StoreMaxScheduleCost_ + + Similar to _MaxScheduleCost_, but for a specific store. If could be useful + when performance of `tikv-server`s are different. + +* _OperatorStepCost_ + + We will assign cost values based on experience. They may need adjustment + in production. + +* _MaxWaitingOperator_ + + The maximum count of waiting operators of each scheduler. + +* _SchedulerMaxWaitingOperator_ + + Similar to _MaxWaitingOperator_, but for a specific scheduler. We can use + it to control priority of different schedulers. + +## Drawbacks + +The migration from the old way to the new scheme has a certain cost, involving +the update of the deployment script and compatibility with the old cluster +configuration in the future. + +## Alternatives + +It has been considered that the schedulers use a cooperative rather than a +competitive scheme to generate operators. However, this approach is more +dependent on the better quality of implementation of all the schedulers, and +the schedulers need to perceive each other, which will increase the coupling +degree of the system. + +## Unresolved questions + +The arbitrarily determined step cost may not correctly reflect the scheduling +overhead, so we need to test to verify that it is appropriate. From aa950ff10cc470ff94f4d1da6137b7fb2615e9f8 Mon Sep 17 00:00:00 2001 From: Wenxuan Shi Date: Mon, 7 Jan 2019 17:57:33 +0800 Subject: [PATCH 26/29] Unified Log Format RFC (#18) * unified log format Signed-off-by: Wenxuan Shi Signed-off-by: Ana Hobden --- text/2018-12-19-unified-log-format.md | 240 ++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 text/2018-12-19-unified-log-format.md diff --git a/text/2018-12-19-unified-log-format.md b/text/2018-12-19-unified-log-format.md new file mode 100644 index 00000000..133518b2 --- /dev/null +++ b/text/2018-12-19-unified-log-format.md @@ -0,0 +1,240 @@ +# Unified Log Format + +## Summary + +This RFC stipulates a unified log format called "TiDB Log Format" to be used +across all TiDB software, i.e. TiDB, TiKV, and PD. + +## Motivation + +A unified log format makes it easy to collect (i.e. Fluentd) logs into a central +storage (i.e. ElasticSearch) and allows users to query logs in a structural way +later. + +## Detailed Design + +A TiDB Log Format file contains a sequence of *lines* containing UTF-8 +characters terminated by either the sequence LF or CRLF. Each line contains a +*Log Header Section*, a *Log Message Section* and a *Log Fields Section*, +concatenated by one whitespace character U+0020 (SPACE). + +### Log Header Section + +The Log Header Section is in the following format: + +```text +[date_time] [LEVEL] [source_file:line_number] +``` + +- `date_time`: The human readable time that the log line is generated in the + local time zone in the following format using the [UTS #35] symbol table: + + ```text + yyyy/MM/dd HH:mm:ss.SSS ZZZZZ + ``` + + Sample: `2018/12/15 14:20:11.015 +08:00` + +- `LEVEL`: The log level in upper case. Available levels are `FATAL`, `ERROR`, + `WARN`, `INFO` and `DEBUG`. + + Sample: `WARN` + +- `source_file`: The source file name that generates the log line. Only + UTF-8 characters matching the regular expression `[a-zA-Z0-9\.-_]` are + permitted and other characters should be removed. + + Sample: `endpoint.rs` + +- `line_number`: The source file line number that generates the log line. + Only digits are permitted. + + Sample: `151` + +For cases that source file and source line number are unknown, +`source_file:line_numer` should be `` instead. + +Log Header Section sample: + +```text +[2018/12/15 14:20:11.015 +08:00] [INFO] [kv.rs:145] +``` + +```text +[2013/01/05 00:01:15.000 -07:00] [ERROR] [] +``` + +### Log Message Section + +The Log Message Section contains a customized message describing the log line in +the following format: + +```text +[message] +``` + +Message must be a valid UTF-8 string and follows the same encoding rule for +field key and field value (see Log Fields Section). + +Log Message Section sample: + +```text +[my_custom_message] +``` + +```text +["Slow Query"] +``` + +### Log Fields Section + +The Log Fields Section contains zero or more *Log Field(s)*, with each one +concatenated by one whitespace character U+0020 (SPACE). The number and the +content of Log Fields are left to the application. Additionally, Log Field +content and orders are not required to be identical for different log lines. + +Each Log Field is in the following format: + +```text +[field_key=field_value] +``` + +Log Field key and value must be valid UTF-8 strings. + +- If types other than string is provided, it must be converted to string. +- If string in other encoding is provided, it must be converted to UTF-8. + +When one of the following UTF-8 characters exists in the field key or field +value, field key or field value should be JSON string encoded: + +- U+0000 (NULL) ~ U+0020 (SPACE) +- U+0022 (QUOTATION MARK) +- U+003D (EQUALS SIGN) +- U+005B (LEFT SQUARE BRACKET) +- U+005D (RIGHT SQUARE BRACKET) + +Log Field sample: + +```text +[region_id=1] +``` + +```text +["user name"=foo] +``` + +```text +[sql="SELECT * FROM TABLE\nWHERE ID=\"abc\""] +``` + +```text +[duration=1.345s] +``` + +```text +[client=192.168.0.123:12345] +``` + +Log Fields Section sample: + +```text +[region_id=1] [peer_id=14] [duration=1.345s] [sql="insert into t values (\"]This should not break log parsing!\")"] +``` + +### Samples + +#### Sample 1 + +- There is a space in the message thus message is encoded when printing. +- No log fields. + +```text +[2018/12/15 14:20:11.015 +08:00] [INFO] [tikv-server.rs:13] ["TiKV Started"] +``` + +#### Sample 2 + +- Unknown source. +- There are some fields but all of them don't need to be encoded. + +```text +[2013/01/05 00:01:15.000 -07:00] [WARN] [] [DDL_Finished] [ddl_job_id=1] [duration=1.3s] +``` + +#### Sample 3 + +- Some fields are encoded but some are not. + +```text +[2018/12/15 14:20:11.015 +08:00] [WARN] [session.go:1234] ["Slow query"] [sql="SELECT * FROM TABLE\nWHERE ID=\"abc\""] [duration=1.345s] [client=192.168.0.123:12345] [txn_id=123000102231] +``` + +```text +[2018/12/15 14:20:11.015 +08:00] [FATAL] [panic_hook.rs:45] ["TiKV panic"] [stack=" 0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace\n at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49\n 1: std::sys_common::backtrace::_print\n at /checkout/src/libstd/sys_common/backtrace.rs:71\n 2: std::panicking::default_hook::{{closure}}\n at /checkout/src/libstd/sys_common/backtrace.rs:60\n at /checkout/src/libstd/panicking.rs:381"] [error="thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99"] +``` + +### Note for Non-UTF-8 Characters + +Similar to JSON specification, this RFC stipulates that all output characters +must be valid UTF-8 character sequences. Non-UTF-8 characters are not supported. +It's up to the logging framework or framework users to determine what to do when +there are non-UTF-8 characters. This RFC only provides some advice for different +scenarios here. + +#### Logging Framework + +The logging framework is recommended to accept only UTF-8 characters at the +interface level, e.g. use `str` in Rust language. In this way, the +responsibility of avoiding non-UTF-8 characters is left to the framework user. + +For languages that do not provide such facilities, it is recommended that the +logging framework should replace invalid UTF-8 character sequences with +U+FFFD (REPLACEMENT CHARACTER), which looks like this: �. Notice that this is a +lossy operation. + +#### Framework Users: Binary Fields + +Some fields are just likely to contain invalid UTF-8 characters, for example, +the region start key and region end key. In such scenario, the application is +recommended to do customized encoding before passing the field to the logging +framework. For example, there is already [Unified Key Format RFC] that requires +keys to be encoded in hex format when outputting to logs. + +#### Framework Users: Unknown User-Input Fields + +Some field content comes from user input, e.g. the SQL expression. The logging +framework user may not be able to ensure that the field content is a valid UTF-8 +string. In such scenario, this RFC provides two candidate solutions: + +- Lossy: Replacing invalid UTF-8 character sequences with U+FFFD + (REPLACEMENT CHARACTER) + +- Lossless: Performing customized escaping (e.g. Golang quoting) that converts + invalid UTF-8 character sequences to something else but also allows + converting back. + +### Note for Large Fields + +This RFC does not limit the key or value length of fields. However usually long +content (e.g. > 1KB) is not friendly for log storing or log parsing and is +likely to cause issues. It's up to logging framework users to decide whether or +not these long content should be avoided. + +## Drawbacks + +- We need to modify existing log outputs as well as support specifying + structural fields. +- The decoding process can be complex because we need to find out the end of + JSON string, which requires correctly parsing JSON string itself. + +## Alternatives + +We can use fixed fields as an alternative. It can be parsed easily compared to +this RFC, but is not flexible enough. + +## Unresolved questions + +The decoding process is not provided in this RFC. + +[UTS #35]: http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Field_Symbol_Table +[Unified Key Format RFC]: https://github.com/tikv/rfcs/blob/master/text/2018-11-12-unified-key-format.md From 5eb89c36a0b41e7154b86313cd153f156734db7b Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Mon, 28 Jan 2019 17:04:59 +0800 Subject: [PATCH 27/29] RFC: PD Simulator (#20) * introduce PD simulator Signed-off-by: Ryan Leung Signed-off-by: Ana Hobden --- media/pd-simulator.png | Bin 0 -> 142027 bytes text/2019-01-08-pd-simulator.md | 149 ++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 media/pd-simulator.png create mode 100644 text/2019-01-08-pd-simulator.md diff --git a/media/pd-simulator.png b/media/pd-simulator.png new file mode 100644 index 0000000000000000000000000000000000000000..d79f78f7410cac677711010b376e00bf34eb6457 GIT binary patch literal 142027 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 Date: Wed, 20 Feb 2019 15:38:29 -0800 Subject: [PATCH 28/29] Update RFC to match code Signed-off-by: Ana Hobden --- text/2018-10-25-tikv-client-rust.md | 74 ++++++++++++++++------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index f6e59ad3..0ad2fefa 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -108,29 +108,26 @@ To use the Raw Key-Value API, take the following steps: ```rust impl Client { pub fn new(_config: &Config) -> Connect; - pub fn get(&self, key: impl AsRef) -> Get; - pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; - pub fn put(&self, pair: impl Into) -> Put; + pub fn get(&self, key: impl Into) -> Get; + pub fn batch_get(&self, keys: impl IntoIterator>) -> BatchGet; + pub fn put(&self, key: impl Into, value: impl Into) -> Put; pub fn batch_put(&self, pairs: impl IntoIterator> ) -> BatchPut; - pub fn delete(&self, key: impl AsRef) -> Delete; - pub fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete; - pub fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan; + pub fn delete(&self, key: impl Into) -> Delete; + pub fn batch_delete(&self, keys: impl IntoIterator>) -> BatchDelete; + pub fn scan(&self, range: impl KeyRange, limit: u32) -> Scan; pub fn batch_scan(&self, - ranges: Ranges, + ranges: impl IntoIterator, each_limit: u32 ) -> BatchScan where Ranges: AsRef<[Bounds]>, Bounds: RangeBounds; - pub fn delete_range(&self, range: impl RangeBounds) -> DeleteRange; + pub fn delete_range(&self, range: impl KeyRange) -> DeleteRange; } ``` #### Usage example of the Raw Key-Value API ```rust -extern crate futures; -extern crate tikv_client; - use std::path::PathBuf; use futures::future::Future; @@ -156,27 +153,27 @@ fn main() { println!("Successfully put {:?}:{:?} to tikv", key, value); let value = raw - .get(&key) + .get(key.clone()) .cf("test_cf") .wait() .expect("Could not get value"); println!("Found val: {:?} for key: {:?}", value, key); - raw.delete(&key) + raw.delete(key.clone()) .cf("test_cf") .wait() .expect("Could not delete value"); println!("Key: {:?} deleted", key); - raw.get(&key) + raw.get(key) .cf("test_cf") .wait() .expect_err("Get returned value for not existing key"); - let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; + let keys: Vec = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; let values = raw - .batch_get(&keys) + .batch_get(keys.clone()) .cf("test_cf") .wait() .expect("Could not get values"); @@ -184,14 +181,14 @@ fn main() { let start: Key = b"k1".to_vec().into(); let end: Key = b"k2".to_vec().into(); - raw.scan(&start..&end, 10) + raw.scan(start.clone()..end.clone(), 10) .cf("test_cf") .key_only() .wait() .expect("Could not scan"); - let ranges = [&start..&end, &start..&end]; - raw.batch_scan(&ranges, 10) + let ranges = vec![start.clone()..end.clone(), start..end]; + raw.batch_scan(ranges, 10) .cf("test_cf") .key_only() .wait() @@ -255,7 +252,13 @@ To use the Transactional Key-Value API, take the following steps: let client = transaction::Client::new(&config); ``` -3. (Optional) Modify isolation level of a Transaction. +3. Create a Transaction. + + ```rust + let txn = client.begin(); + ``` + +4. (Optional) Modify isolation level of a Transaction. ``` rust #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -267,12 +270,12 @@ To use the Transactional Key-Value API, take the following steps: txn.set_isolation_level(IsolationLevel::ReadCommitted); ``` -4. (Optional) Modify data using a Transaction. +5. (Optional) Modify data using a Transaction. The lifecycle of a Transaction is: _begin → {get, set, delete, scan} → {commit, rollback}_. -5. Call the Transactional Key-Value API's methods to access the data on TiKV. +6. Call the Transactional Key-Value API's methods to access the data on TiKV. The Transactional Key-Value API contains the following most commonly used methods: @@ -288,37 +291,40 @@ To use the Transactional Key-Value API, take the following steps: impl Transaction { pub fn commit(self) -> Commit; pub fn rollback(self) -> Rollback; - pub fn lock_keys(&mut self, keys: impl AsRef<[Key]>) -> LockKeys; + pub fn lock_keys(&mut self, keys: impl IntoIterator>) -> LockKeys; pub fn is_readonly(&self) -> bool; pub fn start_ts(&self) -> Timestamp; pub fn snapshot(&self) -> Snapshot; pub fn set_isolation_level(&mut self, level: IsolationLevel); - pub fn get(&self, key: impl AsRef) -> Get; - pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; + pub fn get(&self, key: impl Into) -> Get; + pub fn batch_get(&self, keys: impl IntoIterator>) -> BatchGet; pub fn scan(&self, range: impl RangeBounds) -> Scanner; pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; pub fn set(&mut self, key: impl Into, value: impl Into ) -> Set; - pub fn delete(&mut self, key: impl AsRef) -> Delete; + pub fn delete(&mut self, key: impl Into) -> Delete; } impl Snapshot { - pub fn get(&self, key: impl AsRef) -> Get; - pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet; + pub fn get(&self, key: impl Into) -> Get; + pub fn batch_get(&self, keys: impl IntoIterator>) -> BatchGet; pub fn scan(&self, range: impl RangeBounds) -> Scanner; pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; } ``` +7. Complete the transaction. + + ```rust + txn.commit(); + ``` + #### Usage example of the Transactional Key-Value API ```rust -extern crate futures; -extern crate tikv_client; - use std::ops::RangeBounds; use std::path::PathBuf; @@ -338,7 +344,7 @@ fn puts(client: &Client, pairs: impl IntoIterator>) { txn.commit().wait().expect("Could not commit transaction"); } -fn get(client: &Client, key: &Key) -> Value { +fn get(client: &Client, key: Key) -> Value { let txn = client.begin(); txn.get(key).wait().expect("Could not get value") } @@ -391,7 +397,7 @@ fn main() { // get let key1: Key = b"key1".to_vec().into(); - let value1 = get(&txn, &key1); + let value1 = get(&txn, key1.clone()); println!("{:?}", (key1, value1)); // scan @@ -418,7 +424,7 @@ The result is like: The `tikv_client` crate will be tested with Travis CI using Rust's standard testing framework. We will also include benchmark with criterion in the future. For public functions which process user input, we will seek to use fuzz testing -such as `quickcheck` to find subtle bugs. +such as `proptest` to find subtle bugs. The CI will validate all code is warning free, passes `rustfmt`, and passes a `clippy` check without lint warnings. From 41d029db1970506c56b0654491d202f824f5c53d Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Mon, 25 Feb 2019 13:20:26 -0800 Subject: [PATCH 29/29] Fix lints Signed-off-by: Ana Hobden --- text/2018-10-25-tikv-client-rust.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/text/2018-10-25-tikv-client-rust.md b/text/2018-10-25-tikv-client-rust.md index 0ad2fefa..bae764e6 100644 --- a/text/2018-10-25-tikv-client-rust.md +++ b/text/2018-10-25-tikv-client-rust.md @@ -109,13 +109,17 @@ To use the Raw Key-Value API, take the following steps: impl Client { pub fn new(_config: &Config) -> Connect; pub fn get(&self, key: impl Into) -> Get; - pub fn batch_get(&self, keys: impl IntoIterator>) -> BatchGet; + pub fn batch_get(&self, + keys: impl IntoIterator> + ) -> BatchGet; pub fn put(&self, key: impl Into, value: impl Into) -> Put; pub fn batch_put(&self, pairs: impl IntoIterator> ) -> BatchPut; pub fn delete(&self, key: impl Into) -> Delete; - pub fn batch_delete(&self, keys: impl IntoIterator>) -> BatchDelete; + pub fn batch_delete(&self, + keys: impl IntoIterator> + ) -> BatchDelete; pub fn scan(&self, range: impl KeyRange, limit: u32) -> Scan; pub fn batch_scan(&self, ranges: impl IntoIterator, @@ -291,13 +295,17 @@ To use the Transactional Key-Value API, take the following steps: impl Transaction { pub fn commit(self) -> Commit; pub fn rollback(self) -> Rollback; - pub fn lock_keys(&mut self, keys: impl IntoIterator>) -> LockKeys; + pub fn lock_keys(&mut self,\ + keys: impl IntoIterator> + ) -> LockKeys; pub fn is_readonly(&self) -> bool; pub fn start_ts(&self) -> Timestamp; pub fn snapshot(&self) -> Snapshot; pub fn set_isolation_level(&mut self, level: IsolationLevel); pub fn get(&self, key: impl Into) -> Get; - pub fn batch_get(&self, keys: impl IntoIterator>) -> BatchGet; + pub fn batch_get(&self, + keys: impl IntoIterator> + ) -> BatchGet; pub fn scan(&self, range: impl RangeBounds) -> Scanner; pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; pub fn set(&mut self, @@ -309,7 +317,9 @@ To use the Transactional Key-Value API, take the following steps: impl Snapshot { pub fn get(&self, key: impl Into) -> Get; - pub fn batch_get(&self, keys: impl IntoIterator>) -> BatchGet; + pub fn batch_get(&self, + keys: impl IntoIterator> + ) -> BatchGet; pub fn scan(&self, range: impl RangeBounds) -> Scanner; pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner; }