readme.md

A simple, fast and process-free cache. Or, a Dumb Cache.

# Overview
The cache is broken into a configurable number of ETS tables (called segments). The segment for a given key is `:erlang.phash2(key, NUMBER_OF_SEGMENT)` and all operations (get, put, del, ttl, ...) are directed to the correct segment.

Expired values are only removed from the cache when `get` is called on them.

While a maximum cache size must be configured, the size limit is enforced on the segment. That is, given 10 segments and a max size of 10_000, the maximum size of each segment will be 1000. When a segment is full, the segment is erased.

The cache spawns no additional process. 

`put` *may* result in an O(N) operation when a segment is (N = MAX_SIZE / NUMBER_OF_SEGMENT). Creating a cache with more segment reduces this cost. Specifically, the O(N) operation is a call to :ets.delete_all_objects/1.

The cache will never grow beyond the configured MAX_SIZE.

Your keys might hash to only 1 or a few segments. This would negatively impact performance. However, this would likely require a very unfortunate set of keys (e.g. integers with a fixed gaps between them).

## Usage 1
The preferred usage, which offers better performance, is to use the `define/3` macro:

```
defmodule MyApp.Cache do
  require DCache

  DCache.define(Users, 100_000)
  DCache.define(Products, 1_000_000, segment: 100)
end
```

And then call the following on application start:

```
MyApp.Cache.Users.setup()
MyApp.Cache.Products.setup()
```

You can then use the caching methods on the created module, e.g:

```
MyApp.Cache.Users.get(KEY)
```

## Usage 2
Alternatively, you can create the cache at runtime. Note that this will require 1 extra ETS lookup for every call, as well as requiring 1 extra ETS table to be allocated.

```
DCache.setup(:users, 100_000)
DCache.setup(:products, 1_000_000, segments: 100)
```

Which you can then use as:

```
DCache.get(:users, KEY)
```

## Functions
Regardless of which of the above two approaches you use, the same functionality is available:

### get/1 & get/2
```
Cache.Users.get(key)
# OR
DCache.get(:users, key)
```

Returns the value from the cache. Returns `nil` if the key isn't found or if the value is expired.

### del/1 & del/2
```
Cache.Users.del(key)
# OR
DCache.del(:users, key)
```

Deletes the value from the cache. Does nothing if the key isn't found. The return value is currently always true.

### ttl/1 & ttl/2
```
Cache.Users.ttl(key)
# OR
DCache.ttl(:users, key)
```

Returns the unix time, in seconds, when the value will be considered expired. Returns `nil` if the key isn't found. This can return a value which is less than now.

### put/3 & put/4
```
Cache.Users.put(key, value, ttl)
# OR
DCache.put(:users, key, value, ttl)
```

Stores the value in the cache. The ttl is given in seconds relative to now (e.g. 300 to have the value expire in 5 minutes).


### fetch/2, fetch/3 & fetch/4
```
Cache.Users.fetch(key, fun)
# OR
DCache.fetch(:users, key, fun)
# OR
Cache.Users.fetch(key, fun, ttl)
# OR
DCache.fetch(:users, key, fun, ttl)
```

Returns the value from the cache. If the key is not found, or expired, fun/1 will be called (receiving the key as an argument).

The provided `fun/1` should return one of:
* `{:ok, value}` - to put `value` into the cache with the `ttl` that was provided to `fetch`. `{:ok, value}` will be returned from `fetch`.
* `{:ok, value, ttl}` - to put `value` into the cache with the specified `ttl`.  `{:ok, value}` will be returned from `fetch`.
* `{:skip, value`} - does not place `value` in the cache, but still returns `value` from `fetch`
* `{:error, anything}` - does not place value in the cache and return `{:error, anything}` from fetch.


### fetch!/2, fetch!/3 & fetch!/4

Same as `fetch/2`, `fetch/3` & `fetch/4`, but unwraps `{:ok, value}` into `value` and `{:error, err}` into `raise err`.


### size/0 & size/1
```
Cache.Users.size()
# OR
DCache.size(:users)
```

Returns the total number of items in the cache, including expired items. This is an O(N) operation where N is the number of segments (which will typically be small).