• Stars
    star
    412
  • Rank 105,024 (Top 3 %)
  • Language
    Rust
  • License
    Apache License 2.0
  • Created over 3 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Stretto is a Rust implementation for Dgraph's ristretto (https://github.com/dgraph-io/ristretto). A high performance memory-bound Rust cache.

Stretto

Stretto is a pure Rust implementation for https://github.com/dgraph-io/ristretto.

A high performance thread-safe memory-bound Rust cache.

English | 简体中文

github Build codecov

docs.rs crates.io crates.io

license-apache license-mit

Features

  • Internal Mutability - Do not need to use Arc<RwLock<Cache<...>> for concurrent code, you just need Cache<...> or AsyncCache<...>
  • Sync and Async - Stretto support sync and runtime agnostic async.
    • In sync, Cache starts two extra OS level threads. One is policy thread, the other is writing thread.
    • In async, AsyncCache starts two extra green threads. One is policy thread, the other is writing thread.
  • Store policy Stretto only store the value, which means the cache does not store the key.
  • High Hit Ratios - with Dgraph's developers unique admission/eviction policy pairing, Ristretto's performance is best in class.
    • Eviction: SampledLFU - on par with exact LRU and better performance on Search and Database traces.
    • Admission: TinyLFU - extra performance with little memory overhead (12 bits per counter).
  • Fast Throughput - use a variety of techniques for managing contention and the result is excellent throughput.
  • Cost-Based Eviction - any large new item deemed valuable can evict multiple smaller items (cost could be anything).
  • Fully Concurrent - you can use as many threads as you want with little throughput degradation.
  • Metrics - optional performance metrics for throughput, hit ratios, and other stats.
  • Simple API - just figure out your ideal CacheBuilder/AsyncCacheBuilder values and you're off and running.

Table of Contents

Installation

  • Use Cache.
[dependencies]
stretto = "0.8"

or

[dependencies]
stretto = { version = "0.8", features = ["sync"] }
  • Use AsyncCache
[dependencies]
stretto = { version = "0.8", features = ["async"] }
  • Use both Cache and AsyncCache
[dependencies]
stretto = { version = "0.8", features = ["full"] }

Related

If you want some basic caches implementation(no_std), please see https://crates.io/crates/caches.

Usage

Example

Sync

use std::time::Duration;
use stretto::Cache;

fn main() {
    let c = Cache::new(12960, 1e6 as i64).unwrap();

    // set a value with a cost of 1
    c.insert("a", "a", 1);
    // set a value with a cost of 1 and ttl
    c.insert_with_ttl("b", "b", 1, Duration::from_secs(3));

    // wait for value to pass through buffers
    c.wait().unwrap();

    // when we get the value, we will get a ValueRef, which contains a RwLockReadGuard
    // so when we finish use this value, we must release the ValueRef
    let v = c.get(&"a").unwrap();
    assert_eq!(v.value(), &"a");
    v.release();

    // lock will be auto released when out of scope
    {
        // when we get the value, we will get a ValueRef, which contains a RwLockWriteGuard
        // so when we finish use this value, we must release the ValueRefMut
        let mut v = c.get_mut(&"a").unwrap();
        v.write("aa");
        assert_eq!(v.value(), &"aa");
        // release the value
    }

    // if you just want to do one operation
    let v = c.get_mut(&"a").unwrap();
    v.write_once("aaa");

    let v = c.get(&"a").unwrap();
    assert_eq!(v.value(), &"aaa");
    v.release();

    // clear the cache
    c.clear().unwrap();
    // wait all the operations are finished
    c.wait().unwrap();
    assert!(c.get(&"a").is_none());
}

Async

Stretto support runtime agnostic AsyncCache, the only thing you need to do is passing a spawner when building the AsyncCache.

use std::time::Duration;
use stretto::AsyncCache;

#[tokio::main]
async fn main() {
    // In this example, we use tokio runtime, so we pass tokio::spawn when constructing AsyncCache
    let c: AsyncCache<&str, &str> = AsyncCache::new(12960, 1e6 as i64, tokio::spawn).unwrap();

    // set a value with a cost of 1
    c.insert("a", "a", 1).await;

    // set a value with a cost of 1 and ttl
    c.insert_with_ttl("b", "b", 1, Duration::from_secs(3)).await;

    // wait for value to pass through buffers
    c.wait().await.unwrap();

    // when we get the value, we will get a ValueRef, which contains a RwLockReadGuard
    // so when we finish use this value, we must release the ValueRef
    let v = c.get(&"a").unwrap();
    assert_eq!(v.value(), &"a");
    // release the value
    v.release(); // or drop(v)

    // lock will be auto released when out of scope
    {
        // when we get the value, we will get a ValueRef, which contains a RwLockWriteGuard
        // so when we finish use this value, we must release the ValueRefMut
        let mut v = c.get_mut(&"a").unwrap();
        v.write("aa");
        assert_eq!(v.value(), &"aa");
        // release the value
    }

    // if you just want to do one operation
    let v = c.get_mut(&"a").unwrap();
    v.write_once("aaa");

    let v = c.get(&"a").unwrap();
    println!("{}", v);
    assert_eq!(v.value(), &"aaa");
    v.release();

    // clear the cache
    c.clear().await.unwrap();
    // wait all the operations are finished
    c.wait().await.unwrap();

    assert!(c.get(&"a").is_none());
}

Config

The CacheBuilder struct is used when creating Cache instances if you want to customize the Cache settings.

num_counters

num_counters is the number of 4-bit access counters to keep for admission and eviction. Dgraph's developers have seen good performance in setting this to 10x the number of items you expect to keep in the cache when full.

For example, if you expect each item to have a cost of 1 and max_cost is 100, set num_counters to 1,000. Or, if you use variable cost values but expect the cache to hold around 10,000 items when full, set num_counters to 100,000. The important thing is the number of unique items in the full cache, not necessarily the max_cost value.

max_cost

max_cost is how eviction decisions are made. For example, if max_cost is 100 and a new item with a cost of 1 increases total cache cost to 101, 1 item will be evicted.

max_cost can also be used to denote the max size in bytes. For example, if max_cost is 1,000,000 (1MB) and the cache is full with 1,000 1KB items, a new item (that's accepted) would cause 5 1KB items to be evicted.

max_cost could be anything as long as it matches how you're using the cost values when calling insert.

key_builder

pub trait KeyBuilder {
    type Key: Hash + Eq + ?Sized;

    /// hash_index is used to hash the key to u64
    fn hash_index<Q>(&self, key: &Q) -> u64
        where 
            Self::Key: core::borrow::Borrow<Q>,
            Q: Hash + Eq + ?Sized;

    /// if you want a 128bit hashes, you should implement this method,
    /// or leave this method return 0
    fn hash_conflict<Q>(&self, key: &Q) -> u64
        where 
            Self::Key: core::borrow::Borrow<Q>,
            Q: Hash + Eq + ?Sized;
    { 0 }

    /// build the key to 128bit hashes.
    fn build_key<Q>(&self, k: &Q) -> (u64, u64) 
        where 
            Self::Key: core::borrow::Borrow<Q>,
            Q: Hash + Eq + ?Sized;
    {
        (self.hash_index(k), self.hash_conflict(k))
    }
}

KeyBuilder is the hashing algorithm used for every key. In Stretto, the Cache will never store the real key. The key will be processed by KeyBuilder. Stretto has two default built-in key builder, one is TransparentKeyBuilder, the other is DefaultKeyBuilder. If your key implements TransparentKey trait, you can use TransparentKeyBuilder which is faster than DefaultKeyBuilder. Otherwise, you should use DefaultKeyBuilder You can also write your own key builder for the Cache, by implementing KeyBuilder trait.

Note that if you want 128bit hashes you should use the full (u64, u64), otherwise just fill the u64 at the 0 position, and it will behave like any 64bit hash.

buffer_size

buffer_size is the size of the insert buffers. The Dgraph's developers find that 32 * 1024 gives a good performance.

If for some reason you see insert performance decreasing with lots of contention (you shouldn't), try increasing this value in increments of 32 * 1024. This is a fine-tuning mechanism and you probably won't have to touch this.

metrics

Metrics is true when you want real-time logging of a variety of stats. The reason this is a CacheBuilder flag is because there's a 10% throughput performance overhead.

ignore_internal_cost

Set to true indicates to the cache that the cost of internally storing the value should be ignored. This is useful when the cost passed to set is not using bytes as units. Keep in mind that setting this to true will increase the memory usage.

cleanup_duration

The Cache will cleanup the expired values every 500ms by default.

update_validator

pub trait UpdateValidator: Send + Sync + 'static {
    type Value: Send + Sync + 'static;

    /// should_update is called when a value already exists in cache and is being updated.
    fn should_update(&self, prev: &Self::Value, curr: &Self::Value) -> bool;
}

By default, the Cache will always update the value if the value already exists in the cache, this trait is for you to check if the value should be updated.

callback

pub trait CacheCallback: Send + Sync + 'static {
    type Value: Send + Sync + 'static;

    /// on_exit is called whenever a value is removed from cache. This can be
    /// used to do manual memory deallocation. Would also be called on eviction
    /// and rejection of the value.
    fn on_exit(&self, val: Option<Self::Value>);

    /// on_evict is called for every eviction and passes the hashed key, value,
    /// and cost to the function.
    fn on_evict(&self, item: Item<Self::Value>) {
        self.on_exit(item.val)
    }

    /// on_reject is called for every rejection done via the policy.
    fn on_reject(&self, item: Item<Self::Value>) {
        self.on_exit(item.val)
    }
}

CacheCallback is for customize some extra operations on values when related event happens.

coster

pub trait Coster: Send + Sync + 'static {
    type Value: Send + Sync + 'static;

    /// cost evaluates a value and outputs a corresponding cost. This function
    /// is ran after insert is called for a new item or an item update with a cost
    /// param of 0.
    fn cost(&self, val: &Self::Value) -> i64;
}

Cost is a trait you can pass to the CacheBuilder in order to evaluate item cost at runtime, and only for the insert calls that aren't dropped (this is useful if calculating item cost is particularly expensive, and you don't want to waste time on items that will be dropped anyways).

To signal to Stretto that you'd like to use this Coster trait:

  1. Set the Coster field to your own Coster implementation.
  2. When calling insert for new items or item updates, use a cost of 0.

hasher

The hasher for the Cache, default is SeaHasher.

Acknowledgements

  • Thanks Dgraph's developers for providing amazing Go version Ristretto implementation.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

More Repositories

1

skipdb

An embedded, in-memory, zero-copy, atomicity, consistency, isolation, MVCC, almost lock-free and serializable snapshot isolation database engine.
Rust
202
star
2

fmmap

A flexible and convenient high-level mmap for zero-copy file I/O.
Rust
105
star
3

caches-rs

This is a Rust implementation for popular caches (support no_std).
Rust
104
star
4

skl

A lock-free thread-safe arena based Skiplist impelementation for building memtable.
Rust
37
star
5

ruserf

A highly customable, adaptable, runtime agnostic and WASM/WASI friendly decentralized solution for service discovery and orchestration that is lightweight, highly available, and fault tolerant.
Rust
35
star
6

wg

Golang like WaitGroup implementation for sync/async Rust, support no_std environment.
Rust
32
star
7

lsmtree

Sparse Merkle tree for a key-value map.
Rust
19
star
8

memberlist

A highly customable, adaptable, runtime agnostic and WASM/WASI friendly Gossip protocol (SWIM) which helps manage cluster membership and member failure detection.
Rust
17
star
9

gin-for-miniprogram

微信、支付宝、百度、字节跳动、QQ小程序登录请求,Gin框架完全解决方案
Go
13
star
10

agnostic

Agnostic is a small crate which helps users who want to develop async runtime-agnostic crate.
Rust
13
star
11

veladb

Rust
7
star
12

zallocator

Amortizes the cost of small allocations by allocating memory in bigger chunks.
Rust
6
star
13

rarena

Lock-free ARENA allocator and a set of lock-free data structures based on the ARENA allocator.
Rust
6
star
14

crabmole

Porting Go standard libraries to Rust.
Rust
5
star
15

atomic-time

AtomicDuration, AtomicOptionDuration, AtomicSystemTime, AtomicOptionSystemTime, AtomicInstant and AtomicOptionInstant for Rust.
Rust
5
star
16

peekable

Peekable reader and async reader, which enhance your network programming experience.
Rust
4
star
17

kit-auth

Go Kit based authentication micro services, combined with Jaeger, Prometheus, Consul.
Go
4
star
18

indexsort

Yet another sort crate, porting Golang sort package to Rust.
Rust
4
star
19

nodecraft

Crafting seamless node operations for distributed systems, which provides foundational traits for node identification and address resolution.
Rust
4
star
20

godbpool

Databases connection pool for Golang. Go语言数据库连接池
Go
3
star
21

arcmut

Introduce ArcMut, utility for FFI.
Rust
3
star
22

template-rs

Shell
3
star
23

aommap

Append-only concurrent-safe memory map implementation.
Rust
3
star
24

rcbytes

Rc version `tokio-rs/bytes`
Rust
3
star
25

fromit

Convert struct to struct.
Rust
3
star
26

among

The enum Among with variants Left, Middle and Right is a general purpose sum type with three cases.
Rust
3
star
27

orderwal

A generic-purpose, ordered, zero-copy, Write-Ahead Log implementation for Rust.
Rust
3
star
28

objectpool

Yet another lock-free object pool, support `no_std`.
Rust
3
star
29

derivit

Rust
2
star
30

countries

Rust
2
star
31

flake

Distributed unique ID generator.
Rust
2
star
32

lazyext

Tons of extension utility functions for Rust.
Rust
2
star
33

smallvec-wrapper

Macro and common structs to play with `smallvec`
Rust
2
star
34

shareable-notes

A temporary solution for note sharing across multiple platforms.
Go
1
star
35

enumit

Generate enum type from struct.
Rust
1
star
36

goplus-image

Dockerfile for liuguanyan/goplus image
Roff
1
star
37

dart_thread_example

Dart
1
star
38

micro-boot

Microservices booter for Golang
Go
1
star
39

dbutils

A collection of crates which helps develop database.
Rust
1
star
40

provider_bloc

A simple example, how to custom app theme by flutter with provider. 一个例子,使用flutter的provider实现用户自定义app主题。
Dart
1
star
41

ruwal

Rust Write-Ahead Log implementation.
Rust
1
star
42

awesome_textfield

Dart
1
star
43

distributed-tasks-scheduling

Go
1
star
44

dartsult

A library implements Rust style [Result] for Dart error handle.
Dart
1
star
45

valog

A lightweight value log for Rust.
Shell
1
star
46

rafty

Raft algorithm implementation based on Tokio async runtime.
Rust
1
star
47

aol

Generic append-only log or write-ahead log with in-memory snapshot support.
Rust
1
star
48

al8n

1
star
49

crates

A templete for Rust project with multiple crates.
Shell
1
star