Skip to content

Memory based caching with optional expiration after creation/modification/access, automatic value loading and time travel support.

License

Notifications You must be signed in to change notification settings

nico-amsterdam/simple_mem_cache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SimpleMemCache

Trade memory for performance.

In-memory key-value cache with expiration-time after creation or last access (a.k.a. entry time-to-live and entry idle-timeout), automatic value loading and time travel support.

Installation

  1. Add simple_mem_cache to your list of dependencies in mix.exs:
```elixir
def deps do
  [{:simple_mem_cache, "~> 1.0"}]
end
```
  1. mix deps.get
```sh
$ mix deps.get
```
  1. create ETS table:

Only ETS types: set and ordered_set are supported.

To create the ETS table I recommend Eternal.

Code example:

```elixir

defmodule MyProject do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    Eternal.start_link(SimpleMemCache, [ :set,
                                        {:read_concurrency,  true},
                                        {:write_concurrency, true}
                                       ])

```

Usage

Keep in cache for a limited time, automatically load new value after that

  • Example: scrape news and keep it 10 minutes in cache

    us_news = SimpleMemCache.cache(SimpleMemCache, "news_us", 10,
                                  &Scraper.scrape_news_us/0)
  • or with anonymous function:

    def news(country) do
      SimpleMemCache.cache(SimpleMemCache, "news_" <> country, 10,
                           fn -> Scraper.scrape_news(country) end)
    end

Note about automatically new value loading:

  • How long does this function take to get the new value, and is this acceptable when the old value is expired? If it takes too long, consider to use an scheduler to regularly recalculate the new value and update the cache with that.

Keep in cache for a limited time but extend life-time everytime it is accessed

  • Example: cache http response of countries rest service for at least 20 minutes

    countries_response = SimpleMemCache.cache(SimpleMemCache,
             "countries_response",
             20,
             true,
             fn -> HTTPoison.get! "http://restcountries.eu/rest/v1/" end)

Keep as long as the ETS table exists

  • Example: Cache products retrieved from csv file. Not a good example, because nowadays files are stored on SSD and there will be no performance gain.

    products = SimpleMemCache.cache(SimpleMemCache, "products", fn -> "products.csv"
                                                                      |> File.stream!
                                                                      |> CSV.parse_stream
                                                                      |> Enum.to_list
                                                                end)
  • updates are still possible:

    SimpleMemCache.put(SimpleMemCache, "products", new_value)

or you can force an automatically load at first access by invalidating the cached item.

Invalidate cached item

  • Example: remove products from cache

    old_value = SimpleMemCache.remove(SimpleMemCache, "products")

IEx demo

$ iex -S mix
Interactive Elixir (1.3.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> f_now = fn -> DateTime.to_string(DateTime.utc_now()) end
#Function<20.52032458/0 in :erl_eval.expr/5>
iex(2)> tid = :ets.new(__MODULE__, [:set, :public, {:read_concurrency, true}, {:write_concurrency, true}])
127009
iex(3)> SimpleMemCache.put(tid, "key1", "value1")
"value1"
iex(4)> SimpleMemCache.get(tid, "key1")
{:ok, "value1"}
iex(5)> SimpleMemCache.get!(tid, "key1")
"value1"
iex(6)> SimpleMemCache.remove(tid, "key1")
"value1"
iex(7)> SimpleMemCache.get(tid, "key1")
{:not_cached, nil}
iex(8)> SimpleMemCache.get!(tid, "key1")
nil
iex(9)> IO.puts f_now.(); SimpleMemCache.put(tid, "key1", 1, "value1"); # one minute
2016-08-03 22:42:04.410133Z
"value1"
iex(10)> IO.puts f_now.(); SimpleMemCache.get(tid, "key1")
2016-08-03 22:42:36.641060Z
{:ok, "value1"}
iex(11)> IO.puts f_now.(); SimpleMemCache.get(tid, "key1")
2016-08-03 22:43:10.278992Z
{:expired, "value1"}
iex(12)> f_new_value = fn -> IO.puts "new"; "value2" end
#Function<20.52032458/0 in :erl_eval.expr/5>
iex(13)> IO.puts f_now.(); SimpleMemCache.cache(tid, "key2", 1, f_new_value); # one minute
2016-08-03 22:45:10.551159Z
new
"value2"
iex(14)> IO.puts f_now.(); SimpleMemCache.cache(tid, "key2", 1, f_new_value); # one minute
2016-08-03 22:45:16.410884Z
"value2"
iex(15)> SimpleMemCache.get(tid, "key1")
{:not_cached, nil}
iex(16)> SimpleMemCache.put(tid, "key2", "value2_changed")
"value2_changed"
iex(17)> SimpleMemCache.get(tid, "key2")
{:ok, "value2_changed"}
iex(18)> SimpleMemCache.put(tid, "key3", %{"a" => 1, "b" => {1, 2, "whatever"}})  # put whatever you want
%{"a" => 1, "b" => {1, 2, "whatever"}}
iex(19)> SimpleMemCache.get!(tid, "key3") |> Map.get("b")
{1, 2, "whatever"}
iex(20)> SimpleMemCache.stop(tid)
:ok
iex(21)> SimpleMemCache.get!(tid, "key2")
** (ArgumentError) argument error
              (stdlib) :ets.lookup(127009, "Elixir.SimpleMemCache_state")
    (simple_mem_cache) lib/simple_mem_cache.ex:139: SimpleMemCache.get_cache_state!/1
    (simple_mem_cache) lib/simple_mem_cache.ex:105: SimpleMemCache.get/3
    (simple_mem_cache) lib/simple_mem_cache.ex:111: SimpleMemCache.get!/3

License

MIT

About

Memory based caching with optional expiration after creation/modification/access, automatic value loading and time travel support.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages