Cachex is an extremely fast in-memory key/value store with support for many useful features. You can read the documentation to get into the details of all the features.
This blog post will walk through setting up a simple cache for a Phoenix project. This example works on Elixir 1.8 and greater and Phoenix 1.4 and greater.
Installing the Dependencies
Add this at the mix.exs
file
# mix.exs
defp deps do
[
...
{:cachex, "~> 3.2"}
]
end
Now we are going to add a worker.
Edit your application file at lib/yourapp/application.ex
and add the following:
Don’t forget to add the comma before you paste your code in or you will get a syntax error before: worker
. This means your missing a comma.
Just make sure it looks like what I have outlined below.
defmodule YourApp.Application do
use Application
def start(_type, _args) do
import Supervisor.Spec
children = [
# Start the Ecto repository
YourApp.Repo,
# Start the endpoint when the application starts
YourAppWeb.Endpoint,
# Worker
worker(Cachex, [ :my_cache, [] ])
]
opts = [strategy: :one_for_one, name: YourApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Now run in the console mix deps.get
.
~/Desktop/your_app_name > mix deps.get
...
New:
cachex 3.2.0
eternal 1.2.1
jumper 1.0.1
sleeplocks 1.1.1
unsafe 1.0.1
* Getting cachex (Hex package)
* Getting eternal (Hex package)
* Getting jumper (Hex package)
* Getting sleeplocks (Hex package)
* Getting unsafe (Hex package)
Restart the server if it was running before.
To verify that Cachex is working you can always run iex -S mix
and then type in Cachex.
and then tab. You should see the following in your tab.
iex(1)> Cachex.
Actions Application Disk
Errors ExecutionError Hook
Options Policy Query
Router Services Spec
Stats Warmer child_spec/1
clear/1 clear/2 count/1
count/2 decr/2 decr/3
decr/4 del/2 del/3
dump/2 dump/3 empty?/1
empty?/2 execute/2 execute/3
exists?/2 exists?/3 expire/3
expire/4 expire_at/3 expire_at/4
export/1 export/2 fetch/2
fetch/3 fetch/4 get/2
get/3 get_and_update/3 get_and_update/4
import/2 import/3 incr/2
incr/3 incr/4 inspect/2
inspect/3 invoke/3 invoke/4
keys/1 keys/2 load/2
load/3 persist/2 persist/3
purge/1 purge/2 put/3
put/4 put_many/2 put_many/3
refresh/2 refresh/3 reset/1
reset/2 set/3 set/4
set_many/2 set_many/3 size/1
size/2 start/1 start/2
start_link/1 start_link/2 stats/1
stats/2 stream/1 stream/2
stream/3 take/2 take/3
touch/2 touch/3 transaction/3
transaction/4 ttl/2 ttl/3
update/3 update/4
After the setup
The name :my_cache
is just a name to access the cache. You can name it whatever you would like. For demo purposes this is what I called it.
Creating Cache Warmer
Now what we want is the ability to create a cache when we first launch the application. In this example we are going to use create a module that is a GenServer
and fire off a function that will create our cache.
Step 1
Create a module called CacheWarmer
and follow the details in the example below. This module will have one job and that will be to fire a function that will prepopulate our cache after 1 second on inital boot up.
defmodule YourApp.CacheWarmer do
use GenServer
def start_link, do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
def init(_) do
start_timer()
{:ok, nil}
end
def handle_info(:warm, _state) do
warm_up_cache()
{:noreply, nil}
end
def start_timer, do: Process.send_after(self(), :warm, 1000) # send after 1 sec
defp warm_up_cache() do
Enum.each(["tony", "sam", "angela"], fn(slug) ->
result = YourApp.Repo.find_by_slug(slug)
Cachex.put(:my_cache, slug, result)
end)
end
end
This Module will:
-
Start a timer
-
The timer will end after 1 second
-
When timer finishes it calls
:warm
which will call thewarm_up_cache()
-
That
warm_up_cache()
will then prepopulate the cache
Example
This example is querying the DB by a slug name for some users. We take the result of that querying and then add that to the cache by using the function Cachex.put(:my_cache, slug, result).
The cache follows a key, value look up system.
So in ths example the slug tony
is our key. And the value is what we got out the Repo
With that done we can now add this module as a worker within our application.ex
.
Worker
The CacheWarmer
module needs to be added it as a worker/2
within the start function. To do this we need to go back to our application.ex
file and added it as a worker
defmodule YourApp.Application do
use Application
def start(_type, _args) do
import Supervisor.Spec
children = [
YourApp.Repo,
YourAppWeb.Endpoint,
worker(Cachex, [ :my_cache, [] ]),
# ADD: Your worker
worker(YourApp.CacheWarmer, [])
]
opts = [strategy: :one_for_one, name: YourApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Stop the server in the console and then start it up again. If it works you will definitely see some SQL log files getting generated during boot up.
Accessing Cachex
Within your controller you can write the following code for retrieval of your cache
defmodule YourApp.HomeController do
# omitting some functions for clarity
def show(conn, %{"slug" => slug}) do
{:ok, results} = Cachex.get(:my_cache, slug)
render(conn, "show.html", page_title: "Hello", results: results)
end
end
Cachex.get(:my_cache, slug)
will return a tuple. Pattern match that to get the results
back. Pass that into your template and your done.
Performance Improvement
The performance improvement should be noticable depending on the content. In one of my applications I had a page that would take 150ms to fetch and render. Once I used Cachex
I reduced it down to 1ms.
Troubleshooting
-
If you are getting {:ok, nil} this means the data has not been setup in your Cachex.
-
If you get an
:error
it could mean the GenServer is not working correctly and need to double check your setup inapplication.ex
-
Double check nothing is misspelled. In my examples my app name is
YourApp
that does not mean you should name it that :)
Other sources
- Discussion on Cachex and Phoenix
- Discussion on elixir forum
- Docs for cachex source
- Video from Elixir Cast has a tutorial
- Getting started source