pub trait Cache:
Send
+ Sync
+ 'static {
// Required methods
fn name(&self) -> &str;
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Vec<u8>>>>;
fn put<'a>(
&'a self,
key: &'a str,
value: &'a [u8],
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<()>>;
fn forget<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>>;
fn flush(&self) -> BoxFuture<'_, Result<()>>;
fn increment<'a>(
&'a self,
key: &'a str,
by: i64,
) -> BoxFuture<'a, Result<i64>>;
fn add<'a>(
&'a self,
key: &'a str,
value: &'a [u8],
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<bool>>;
fn forget_if<'a>(
&'a self,
key: &'a str,
expected: &'a [u8],
) -> BoxFuture<'a, Result<bool>>;
// Provided method
fn has<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>> { ... }
}Expand description
A key/value store with expiry.
Bytes rather than strings, because the interesting things to cache — a serialised session, a rendered page, a protobuf — are not all text, and forcing them through UTF-8 would mean base64 on top of an already-encoded value.
Every method is fallible and a miss is not a failure: get returns
Ok(None) for an absent key and Err only when the cache itself could not
be reached. Conflating the two is how a cache outage becomes an outage.
Required Methods§
Sourcefn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Vec<u8>>>>
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Vec<u8>>>>
The value at key, or None if it is absent or expired.
Sourcefn put<'a>(
&'a self,
key: &'a str,
value: &'a [u8],
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<()>>
fn put<'a>( &'a self, key: &'a str, value: &'a [u8], ttl: Option<Duration>, ) -> BoxFuture<'a, Result<()>>
Store value, expiring after ttl. None means no expiry.
Sourcefn forget<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>>
fn forget<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>>
Remove a key. true if it was there.
Sourcefn flush(&self) -> BoxFuture<'_, Result<()>>
fn flush(&self) -> BoxFuture<'_, Result<()>>
Remove everything.
On a shared server this removes everything in the database, not just
this application’s keys — which is why prefix
exists and why this is not something to call on a schedule.
Sourcefn increment<'a>(&'a self, key: &'a str, by: i64) -> BoxFuture<'a, Result<i64>>
fn increment<'a>(&'a self, key: &'a str, by: i64) -> BoxFuture<'a, Result<i64>>
Add by to a counter, creating it at zero first. Returns the new value.
Atomic where the driver can be — which is the point of having it on the port rather than as a get-then-put in application code, because that version loses increments under concurrency.
Sourcefn add<'a>(
&'a self,
key: &'a str,
value: &'a [u8],
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<bool>>
fn add<'a>( &'a self, key: &'a str, value: &'a [u8], ttl: Option<Duration>, ) -> BoxFuture<'a, Result<bool>>
Store value only if key is absent. true if it was stored.
§This must be atomic
Not “atomic where convenient” — atomic. It is the primitive
Lock is built from, and therefore the primitive behind
without_overlapping and on_one_server. A has followed by a put
satisfies the signature and satisfies no caller: two processes both see
the key absent, both write, both believe they hold the lock, and the
scheduled task runs twice.
It is a required method with no default for exactly that reason. A default would be inherited silently by the next driver somebody adds, and the failure it causes is rare, non-local and expensive.
| Driver | How |
|---|---|
| memory | inside the same mutex as the map |
| Redis | SET key value NX PX ttl |
| Memcached | the add command, which is atomic by definition |
| DynamoDB | PutItem with a condition on the key not existing |
Sourcefn forget_if<'a>(
&'a self,
key: &'a str,
expected: &'a [u8],
) -> BoxFuture<'a, Result<bool>>
fn forget_if<'a>( &'a self, key: &'a str, expected: &'a [u8], ) -> BoxFuture<'a, Result<bool>>
Remove key only if it currently holds expected. true if it was
removed.
The other half of a lock, and the half people leave out.
Releasing with a plain forget is wrong in a way that
only shows up under load. A holder that stalls past its TTL — a long GC
pause, a slow query, a suspended VM — has already lost the lock; another
process has taken it. When the first one finally finishes and deletes
the key, it deletes someone else’s lock, and now a third process can
take it while the second still thinks it holds it.
Comparing the value first closes that. The value is the holder’s random token, so “is this still mine” has an answer.
Also required, and for the same reason as add.
Provided Methods§
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".