Is there an elegant way of using Cachex and Ash Framework?
I have a read query that I need to cache results for, because of the many relationships attached to it. I wonder if there’s a better way of caching results while reading in Ash Framework.
Here’s how I am doing it from the domain module.
@doc """
Get cached person if exists, otherwise fetch from the DB and cache for future fast-reading
"""
def get_cached_person_first(person_id, context) do
case Cachex.get(:person, person_id) do
{:ok, %{} = person} ->
person
{:ok, nil} ->
Hr.People.get_loaded_person!(person_id, context)
|> cache_person()
end
end
There is no Cachex extension or anything like that at the moment.
A more Ash-centric approach would be to check the cache in the preparation of the read action, and if so, you can set the data on the query, and Ash won’t do the roundtrip to the database.
https://hexdocs.pm/ash/preparations.html
https://hexdocs.pm/ash/Ash.Query.html#set_result/2
You can add an after_action hook into the read action to populate the cache
I would create a specific read action for this.
You can also use a global change that runs on updates and destroys to invalidate the cache.
Caching involves a lot of complexities, make sure you really need it 
1 Like
Every extra layer adds complexity and caching is one of those layers that tend to become complex. Most databases can be tweaked to cache more (they already do, but default settings are generally quite low) and if tweaking is enough, the complexity is completely handled by the database. Let the database do the heavy lifting for you (also research ‘materialized tables’)
That being said: let’s continue with the original question below.
1 Like
I am attaching a code, for the sake of reference. Combing through different resources. To come up with possible answer.
action
read :read do
primary? true
pagination offset?: true, default_limit: 10
argument :check_cache, :boolean do
default true
end
prepare Preparations.CheckCache
end
defmodule Preparations.CheckCache do
@moduledoc """
Checks a simple agent cache for libraries
"""
use Ash.Resource.Preparation
def prepare(query, _, _) when query.arguments.check_cache == true do
CacheHelper.attach_cache(query)
end
def prepare(query, _, _) do
query
end
end
def attach_cache(%Ash.Query{} = query) do
query
|> before_action()
|> Ash.Query.after_action(fn query, records -> after_action(query, records) end)
end
def before_action(%Ash.Query{} = query) do
cache_name = ReserveCache.name()
key = query_filter_to_key(query)
# take arguments to key as well
updated_query =
case Cachex.exists?(cache_name, key) do
{:ok, true} ->
{:ok, results} = Cachex.get(cache_name, key)
Ash.Query.set_result(query, results)
_ ->
query
end
updated_query
end
2 Likes