/SineCache

SineCache is a high-performance, in-memory caching library for Rust, designed to efficiently store and manage key-value pairs with support for various eviction policies and persistence with AOF.

Primary LanguageRustMIT LicenseMIT

SineCache

SineCache is a high-performance, in-memory caching library for Rust, designed to efficiently store and manage key-value pairs with support for various eviction policies and persistence options using Append-Only Files (AOF).

Features

Powerful Caching Mechanism

SineCache provides a robust caching solution with flexible configurations and support for multiple eviction policies, ensuring optimal performance for varying application needs.

Eviction Policies

Choose from FIFO (First-In, First-Out), LRU (Least Recently Used), and LFU (Least Frequently Used) eviction policies. Additionally, define custom eviction policies through a simple trait implementation.

Asynchronous Support

  • AsyncCache: Wraps the Cache struct with a tokio::sync::Mutex, enabling safe concurrent access with asynchronous operations (async versions of get, put, remove, etc.).

Persistence with Append-Only Files (AOF)

Optionally persist cache data using AOF, ensuring durability and recovery of cache state across application restarts. If flush_time is provided (milliseconds), data is flushed to disk after every flush_time milliseconds to disk without blocking the main thread. In case of None, every operation is flushed to disk in the same thread.

Thread Safety

Ensures thread safety with appropriate locking mechanisms (tokio::sync::Mutex for AsyncCache), making it suitable for multi-threaded environments.

Configuration Flexibility

Configure cache size limits, eviction policies, AOF settings, and more through intuitive configuration structs (CacheSyncConfig and AsyncCacheConfig).

Comprehensive Documentation

Extensive API documentation and examples facilitate easy integration and customization within applications.

Safety and Reliability

Built with Rust's ownership model and type system, ensuring memory safety and preventing common bugs like null pointer dereferencing and data races.

Getting Started

To use SineCache in your Rust project, add it to your Cargo.toml:

[dependencies]
sine_cache = "0.2.0"

Examples

Some examples are listed below but for the more detailed documentation, visit: https://docs.rs/sine_cache/latest/sine_cache/

Cache - Synchronous Cache:

Simple methods related to Cache . For using it in concurrent environment, customize on top of it like wrapping in Mutex and using async methods etc.

use sine_cache::{cache::Cache, config::CacheConfig};

fn main() {
    let capacity = 10; // Maximum number of entries in the cache.
    let mut cache = Cache::new(sine_cache::config::CacheSyncConfig::LFU(CacheConfig{max_size: capacity}));

    // Inserting key-value pairs into the cache
    cache.put(1, "One");
    cache.put(1, "one"); // Overwrites previous value
    cache.put(2, "Two");

    // Retrieving a value from the cache
    let value = cache.get(&1);
    assert!(value.is_some_and(|x| x == &"one"));
}

AsyncCache - Asynchronous Cache:

Some examples related to AsyncCache are listed below:

  • Without AOF:

    When AOF is not required:

use sine_cache::{cache::AsyncCache, config::{AsyncCacheConfig, EvictionAsyncConfig}};

#[tokio::main]
async fn main() {
    let capacity = 10; // Maximum number of entries in the cache.
    let mut cache = AsyncCache::new(AsyncCacheConfig::LFU(EvictionAsyncConfig {max_size: capacity, aof_config: None})).await;

    // Inserting key-value pairs into the cache
    cache.put(1, String::from("One")).await;
    cache.put(1, String::from("one")).await; // Overwrites previous value
    cache.put(2, String::from("Two")).await;

    // Retrieving a value from the cache
    let value = cache.get(&1).await;
    assert!(value.is_some_and(|x| x == "one"));
}
  • With AOF:

    When AOF is required, we can pass details related to AOF in the configurations and set the periodic flushes to disk or each operation record to disk based on setting flush_time in milliseconds or None.

use sine_cache::{cache::AsyncCache, config::{AsyncCacheConfig, EvictionAsyncConfig, EvictionAOFConfig}};

#[tokio::main]
async fn main() {
  
    let capacity = 10; // Maximum number of entries in the cache.
    let mut cache = AsyncCache::new(AsyncCacheConfig::LFU(EvictionAsyncConfig {
        max_size: capacity,
        aof_config: Some(EvictionAOFConfig {
            folder: String::from("./data"), //folder in which persistent file should be written.
            cache_name: String::from("async_lof_cache"), //Unique cache name as with same name file will be created.
            flush_time: Some(5000) //After every 5000 milliseconds data will be flushed to disk.
        })
    })).await;

    // Inserting key-value pairs into the cache
    cache.put(1, String::from("One")).await;
    cache.put(1, String::from("one")).await; // Overwrites previous value
    cache.put(2, String::from("Two")).await;

    // Retrieving a value from the cache
    let value = cache.get(&1).await;
    assert!(value.is_some_and(|x| x == "one"));
}

Custom eviction policy

Custom evicton policies can also be defined and used with all the features of AsyncCache and Cache.

use sine_cache::eviction_policies::common::EvictionPolicy;
use sine_cache::{cache::AsyncCache, config::{AsyncCacheConfig, CustomEvictionAsyncConfig, CustomEvictionAOFConfig}};

pub struct CustomEviction<K> {
    _phantom: std::marker::PhantomData<K>,
}

impl<K: Eq + std::hash::Hash + Clone> CustomEviction<K> {
    pub fn new() -> Self{
        Self{
            _phantom: std::marker::PhantomData
        }
    }
}

impl<K: Eq + std::hash::Hash + Clone> EvictionPolicy<K> for CustomEviction<K> {
    fn on_get(&mut self, key: &K) {
        // nothing to do.
    }

    fn on_set(&mut self, key: K) {
        // nothing to do.
    }

    fn evict(&mut self) -> Option<K> {
        // nothing to do
        None
    }

    fn remove(&mut self, key: K) {
        //nothing to do
    }
}

#[tokio::main]
async fn main() {
  
    let capacity = 10; // Maximum number of entries in the cache.
    let mut cache = AsyncCache::new(AsyncCacheConfig::Custom(CustomEvictionAsyncConfig {
        max_size: capacity,
        aof_config: Some(CustomEvictionAOFConfig {
            folder: String::from("./data"), //folder in which persistent file should be written.
            cache_name: String::from("async_lof_custom_cache"), //Unique cache name as with same name file will be created.
            flush_time: Some(5000), //After every 5000 milliseconds data will be flushed to disk.
            persist_read_ops: true //whether to store reads also, true generally.
        }),
        policy: Box::new(CustomEviction::new())
    })).await;

    // Inserting key-value pairs into the cache
    cache.put(1, String::from("One")).await;
    cache.put(1, String::from("one")).await; // Overwrites previous value
    cache.put(2, String::from("Two")).await;

    // Retrieving a value from the cache
    let value = cache.get(&1).await;
    assert!(value.is_some_and(|x| x == "one"));
}

Planned Features

AOF Compaction Periodically

Compact AOF files periodically to stop the append only file becoming too large.

License

This project is licensed under the MIT License - see the LICENSE file for details.