Coder Social home page Coder Social logo

retainer's Introduction

Retainer

Build Status Crates.io

This crate offers a very small cache with asynchronous bindings, allowing it to be used in async Rust contexts (Tokio, async-std, smol, etc.) without blocking the worker thread completely.

It also includes the ability to expire entries in the cache based on their time inside; this is done by spawning a monitor on your async runtime in order to perform cleanup tasks periodically. The eviction algorithm is similar to the one found inside Redis, although keys are not removed on access in order to reduce borrow complexity.

This crate is still a work in progress, so feel free to file any suggestions or improvements and I'll get to them as soon as possible :).

Getting Started

This crate is available on crates.io. The easiest way to use it is to add an entry to your Cargo.toml defining the dependency using cargo add:

$ cargo add retainer

Basic Usage

The construction of a cache is very simple, and (currently) requires no options. If you need to make use of key expiration, you must ensure to either await a monitor or spawn a monitor on your runtime.

There are many ways to provide an expiration time when inserting into a cache, by making use of several types implementing the Into<CacheExpiration> trait. Below are some examples of types which are available and some of the typical APIs you will find yourself using. This code uses the Tokio runtime, but this crate should be compatible with most of the popular asynchronous runtimes. Currently a small set of tests are run against async-std, smol and Tokio.

use retainer::*;
use tokio::time::sleep;

use std::sync::Arc;
use std::time::{Duration, Instant};

#[tokio::main]
async fn main() {
    // construct our cache
    let cache = Arc::new(Cache::new());
    let clone = cache.clone();

    // don't forget to monitor your cache to evict entries
    let monitor = tokio::spawn(async move {
        clone.monitor(4, 0.25, Duration::from_secs(3)).await
    });

    // insert using an `Instant` type to specify expiration
    cache.insert("one", 1usize, Instant::now()).await;

    // insert using a `Duration` type to wait before expiration
    cache.insert("two", 2, Duration::from_secs(2)).await;

    // insert using a number of milliseconds
    cache.insert("three", 3, 3500).await;

    // insert using a random number of milliseconds
    cache.insert("four", 4, 3500..5000).await;

    // insert without expiration (i.e. manual removal)
    cache.insert("five", 5, CacheExpiration::none()).await;

    // wait until the monitor has run once
    sleep(Duration::from_millis(3250)).await;

    // the first two keys should have been removed
    assert!(cache.get(&"one").await.is_none());
    assert!(cache.get(&"two").await.is_none());

    // the rest should be there still for now
    assert!(cache.get(&"three").await.is_some());
    assert!(cache.get(&"four").await.is_some());
    assert!(cache.get(&"five").await.is_some());

    // wait until the monitor has run again
    sleep(Duration::from_millis(3250)).await;

    // the other two keys should have been removed
    assert!(cache.get(&"three").await.is_none());
    assert!(cache.get(&"four").await.is_none());

    // the key with no expiration should still exist
    assert!(cache.get(&"five").await.is_some());

    // but we should be able to manually remove it
    assert!(cache.remove(&"five").await.is_some());
    assert!(cache.get(&"five").await.is_none());

    // and now our cache should be empty
    assert!(cache.is_empty().await);

    // shutdown monitor
    monitor.abort();
}

In the case this example is not kept up to date, you can look for any types which implement the Into<CacheExpiratio> trait in the documentation for a complete list.

Cache Monitoring

All key expiration is done on an interval, carried out when you await the future returned by Cache::monitor. The basis for how this is done has been lifted roughly from the implementation found inside Redis, as it's simple but still works well.

When you call Cache::monitor, you need to provide 3 arguments:

  • sample
  • frequency
  • threshold

Below is a summarization of the flow of eviction, hopefully in a clear way:

  1. Wait until the next tick of frequency.
  2. Take a batch of sample entries from the cache at random.
  3. Check for and remove any expired entries found in the batch.
  4. If more than threshold percent of the entries in the batch were removed, immediately goto #2, else goto #1.

This allows the user to control the aggressiveness of eviction quite effectively, by tweaking the threshold and frequency values. Naturally a cache uses more memory on average the higher your threshold is, so please do keep this in mind.

Cache Logging

As of v0.2, minimal logging is included using the log crate. You can attach any of the compatible logging backends to see what is happening in the cache (particularly the eviction loop) to better gauge your usage and parameters.

retainer's People

Contributors

whitfin avatar wiktor-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

retainer's Issues

Consider using runtime specific types rather than generic types

Right now this create uses some generic implementations of several things (timers, locks, etc). The intent is to support as many runtimes as possible, without picking a favourite. However, it might be better to have Cargo features for each runtime and defer to their types (falling back to the generic types). The assumption is that these runtimes will be able to optimize for themselves more than a generic implementation would.

I think it'll probably be better in the long run, but it's a little more complicated to maintain so I'm not sure how much time can be devoted to it at the moment (or in the near future). It does also mean that we would have to add new runtime support as more become popular.

Feature: Expose iterator to underlying store

I have a use case where I have small collection who's data needs to expire. Retainer would work well for me except that there's not a way for me to access the underlying map without knowing the individual keys in the map.

Add trace logging to cache monitor loops alongside timing

This should be (ideally) compiled out somehow, or maybe toggled off through the environment (or toggled on).

We just need a way to determine the effectiveness of the monitoring loop on a live system, to help people gauge their options and intervals.

Optimize monitor loops to lower lock overhead

The first implementation locks for the entire time - we can use an upgradable read lock to figure out the keys to remove, before upgrading to a write lock to actually do the removal.

This should lower the overhead a little.

Feature: get() function that returns expiration info?

I'm attempting to get() a value while also seeing how long it has left until it expires. It looks like right now the CacheEntryReadGuard returned by get() only returns the value.

The equivalent of this in Redis is a script like the following that can be run as a single query:

return {redis.call('get',KEYS[1]), redis.call('ttl',KEYS[1])}

As of 0.2.2 a workaround is to remove() the value (getting the full CacheEntry) and then putting the value back with an insert() against the original expiration deadline.

A couple possible ways of doing this (haven't looked at the internals so don't know which makes more sense):

  • An alternative get() function that returns the CacheEntry, including the expiration time, rather than just the CacheEntryReadGuard that only includes the value?
  • Add the expiration field to CacheEntryReadGuard?

A couple of suggestions

Hi,

I've just found your crate and it looks ideal for my case.

I had a couple of ideas when browsing docs.rs which you may or may not like (that's why I'm not submitting a PR, unless you do like them ;) )

  1. Using #![doc = include_str!("../README.md")] in lib.rs (an example here). That'd embed the README.md file as crate root level documentation. It has two nice properties: first the docs.rs page has content with code examples instead of short info: https://docs.rs/retainer/0.3.0/retainer/ second it makes cargo test run the examples in your README thus making sure they are OK.

  2. Instead of having this snippet of code:

[dependencies]
retainer = "0.3"

Ask users to execute cargo add retainer which will always pick-up the latest version and you wouldn't have to update the README on each release.

Well... that's it for now. Thanks for such a great crate! ๐Ÿ‘‹

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.