Caching in Phoenix/Elixir with Cachex

A quick and dumb-down explanation of how to set up cachex in phoenix/elixir and an optional cache table/service management for awesomeness 😊 . No more chitchat on why caching is important blah blah blah, let’s go straight to coding.

Cachex Installation:

Add cachex to your list of dependencies:mix.exs

def deps do
[{:cachex, "~> 3.3"}]
end

Ensure cachex is started before your application:mix.exs

def application do
[applications: [:cachex]]
end

Starting your cache:

In your application.ex

def start(_type, _args) do
import Supervisor.Spec
children = [
MyApp.Cache
]
end

Setup child_spec for your cache:

Requirements:
- must be of type :supervisor , not a :worker
- must have a unique id

defmodule MyApp.Cache do
@moduledoc """
Cache
"""
@cache_table :my_app_cache def child_spec(_init_arg) do
%{
id: @cache_table,
type: :supervisor,
start:
{Cachex, :start_link,
[
@cache_table,
[
warmers: [
warmer(module: MyApp.CacheWarmer, state: "")
]
]
]}
}
end
end

Setting up cache warmer:

defmodule MyApp.CacheWarmer do
@moduledoc """
Cache Warmer
"""

use Cachex.Warmer
alias MyApp.CacheModule

def interval, do: :timer.minutes(60)
def execute(_args) do
with {:ok, data} <- CacheModule.get_data_somewhere() do
{:ok, data, [ttl: :timer.minutes(60)]} end
end
end

interval : Returns the interval this warmer will execute on. (Basically when you want the warmer to rerun)

This must be an integer representing a count of milliseconds to wait before the next execution of the warmer. Anything else will cause either invalidation errors on cache startup, or crashes at runtime.

execute : Executes actions to warm a cache instance on interval.

This can either return values to set in the cache or an atom :ignore to signal that there's nothing to be set at this point in time.

ttl : Time to live, expiration for an entry in a cache.

This is a millisecond value (if set) representing the time a cache entry has left to live in a cache. It can return nil if the entry does not have a set expiration.

Handling cache on one table (optional):

If you want all your services to share just one cache table, you should parse your data before returning it to the warmer.

defmodule MyApp.CacheModule do
@moduledoc """
Getting Cache Data
"""
alias MyApp.Repo
alias MyApp.Schemas.{
User,
Movie
}
def get_data_somewhere do
with users <- get_data(User, :user),
movies <- get_data(Movie, :movie),
data <- users ++ movies do
{:ok, data}
end
end
defp get_data(struct, type) do
struct
|> Repo.all()
|> parse_cache(type)
end
defp parse_cache(data, type) do
data
|> Enum.map(fn f ->
{{type, f.id}, f}
end)
end
end

In the parse_cachemethod, you want to structure the data like this {type, f.id}, f , you’ll see its use later on in the example.

type: is the cache type, in our example, the :user or :movie

f.id : is the cache key

f : is the actual cache data

Examples (if you set up one cache table):

Since we set our application to only have one cache table for all our services/data, using cachex functions will be a little different, but I’m sure you can get it easily

Retrieves an entry from a cache

In cachex documentation:

Cachex.get(:my_cache, "key")

In our app:

Cachex.get(:my_cache, {type, "key"})

Example:

Cachex.get(:my_cache, {:user, user.id}){:ok, %User{...}}

Placing an entry in a cache

In cachex documentation:

Cachex.put(:my_cache, "key", "value")

In our app:

Cachex.put(:my_cache, {type, "key"}, "value")

Example:

Cachex.put(:my_cache, {:user, user.id}, user){ :ok, true }

And that is it, a quite short blog, but I hope you get the idea, you can have a look at cachex documentation for more details here: https://hexdocs.pm/cachex/getting-started.html, Thanks!

Happy Coding!!

--

--

--

www.alvinrapada.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How To Deploy RabbitMQ With The Kubernetes Operators

“How To Deploy RabbitMQ With The Kubernetes Operators In 1 Hour” feature post image

Terminal setup for the millennial developer!

Introducing the Open Source Africa COVID-19 Response Toolkit

Africa COVID-19 Response Toolkit

3 Learning Points from MySQL

Apollo interns on GraphQL Radio

[Algo]: Interwoven Strings

My journey through grokking a contribution that is coded in a language that I have never coded in

Weekly Report #13

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alvin Rapada

Alvin Rapada

www.alvinrapada.com

More from Medium

Disaster Recovery with a CDK managed database

Running Redis with resilience in Linux containers on Windows — Part 3

docker network inspect

Understanding TLS Cipher Suites