#key-value #data-store #update #mutex #limited

concurrent-kv

key-value data store with limited concurrent updates

2 releases

Uses old Rust 2015

0.2.1 Nov 30, 2016
0.2.0 Nov 30, 2016

#44 in #limited

MIT license

10KB
118 lines

concurrent-kv

Build Status

License

concurrent-kv is licensed under the MIT license. See LICENSE for details.


lib.rs:

generic, thread safe, in-memory, key-value data store

As indicated by the name, the Library struct is the main export of this module. Some supporting types and traits are also exported.

The Library structure supports two operations: get and insert, which fill similar roles to their counter parts in the HashMap collection. get accepts a key and returns a value. insert updates provided key in the data store with the provided value.

The thread safety property of the Library is the result of wrapping multiple concurrency primitives; Arc, ArcCell, and Mutex.

Mutex and Arc are part of the standard library. We use mutex to prevent multiple writers from adding a new key simultaneously. Note: this is subtly, but significantly, different from preventing multiple insertions of the same key.

Insertion

There are two different scenarios to consider: inserting a new value under a new key and inserting a new value for an existing key and

New value under new key

Inserting a value with a new key requires allocating additional space in the HashMap and potentially rearranging the underlying data. To prevent consistency errors the Library has an internal Mutex (Library.insert_mutex) which must be obtained before inserting a key.

use concurrent_kv::Library;

let lib: Library<String, i64> = Library::new();
lib.insert("qwerty".into(), 12345);
let val0 = lib.get("qwerty").unwrap();
lib.insert("asdfgh".into(), 67890);
let val1 = lib.get("asdfgh").unwrap();
assert_eq!(val0, 12345.into());
assert_eq!(val1, 67890.into());

New value under existing key

Since the key already exists the HashMap does not need to allocate any additional storage (we are just swapping the contents of an ArcCell). So we can short-circuit the insertion process, and thus skipping lock acquisition, by providing a reference to the ArcCell and swapping directly.

This tradeoff for performance is what introduces the "Last Writer Wins" behavior for multiple insertions to the same key.

use concurrent_kv::Library;
use std::sync::Arc;

let lib0: Arc<Library<String, i64>> = Library::new().into();
let val0 = lib0.get("abc");
assert_eq!(val0, None.into());
let lib1 = lib0.clone();
lib0.insert("abc".into(), 123);
let val123 = lib1.get("abc");
assert_eq!(val123, lib0.get("abc"));
assert_eq!(val0, None.into());
lib1.insert("abc".into(), 456);
let val456 = lib1.get("abc");
assert_eq!(val456, Some(456.into()));
assert_eq!(val123, Some(123.into()));

ArcCell

ArcCell is provided by the crossbeam crate. The naming and documentation are atrocious, so we attempt to provide an explaination here.

As the figure below attempts to depict, The defining feature of this type is the ability to swap out the contents of the heap allocated value (i.e. Cell) atomically. So a more accurate name would be AtomicCell.

         A ----> N
  A -\
  A ---> A ----> O
  A --/      /
           A-

         A -\ /- N
  A -\       X
  A ---> A -/ \- > O
  A --/       /
            A-

see: https://internals.rust-lang.org/t/atomic-arc-swap/3588

Caveats

It is up to the user of Library to ensure that only a single update to an individual key happens concurrently. Otherwise the Library will default to the "Last Writer Wins" conflict resolution strategy (hardly ever the desired behavior from an end user perspective).

Dependencies

~15KB