Cachex with Phoenix
A tutorial on how to setup Cachex within your Phoenix application. This tutorial walks through setting up a basic cache for some expensive queries.
We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
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.
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
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.
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.
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
Start a timer
The timer will end after 1 second
When timer finishes it calls :warm
which will call the warm_up_cache()
That warm_up_cache()
will then prepopulate the cache
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
.
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.
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.
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.
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 in application.ex
Double check nothing is misspelled. In my examples my app name is YourApp
that does not mean you should name it that :)