csadewa
Handling/batching concurrent call for cacheable (same) result
Hi all, i hit an issue of performance problem in the term of caching. I wonder if there’s any easy way to batch concurrent call (same key) for cacheable result
To illustrate the problem, here i was using Cachex for caching, and to help cache some action/external nerwork call, i was using this helper code to wrap an action/function with cacheable result
@doc """
Wrap doing something in cache, with cache_key
Cache will only cache for succesful result, for error result will not be cached
Input:
cache_key: cache_key, can be anything (tuple, map, etc.)
result_fn: anoymous zero-argument function which return {:ok. any()} or {:error, any()}
opt:
ttl: time to live, default to :timer.hours(1)
Output:
{:ok, any()} | {:error, any()} based on result_fn
"""
def wrap_caching(cache_key, result_fn, opt \\ []) do
case Cachex.get(:my_cache, key) do
{:ok, nil} ->
result = result_fn.()
case result do
{:ok, data} ->
Cachex.put(:my_cache, cache_key, data, ttl: opt[:ttl] || :timer.hours(1))
{:error, _} ->
:do_nothing
end
result
{:ok, data} ->
{:ok, data}
end
end
so if i were to do an same external network call in a function, it would cache nicely and call external network only once, like:
def call_osrm_routing(origin, destination)
wrap_caching({:call_osrm_routing, origin, destination}, fn ->
... do network call to OSRM routing server
end)
end
def do_some_logic() do
# Here, in this function, actually calling OSRM routing server only done once
call_osrm_routing(origin, destination)
... do some work
call_osrm_routing(origin, destination)
... do some work
call_osrm_routing(origin, destination)
end
However, i hit a problem, is when call is concurrent (like for example in Absinthe async resolver), it would make each concurrent call do the external network call by themself, because when the function is called, the cache result isn’t yet ready yet, for example:
# these would make the external osrm call three time, due to cache result isn't ready when second/third invocation
[
Task.async(fn -> call_osrm_routing(origin, destination) end),
Task.async(fn -> call_osrm_routing(origin, destination) end),
Task.async(fn -> call_osrm_routing(origin, destination) end),
]
How should i solve this problem? is there library/tools to help?
I was thinking to build a batching genserver, which would intercept function call and do work if there’s no same work happen, or if there’s same work currently executing, it would wait until it’s finished, but would like to know if there’s other better way.
Marked As Solved
stefanchrobot
You can solve this by just serializing the Cachex.get calls via a process - the first process will make the actual call and the others will read the cached value. A GenServer per cache/key would do the job.
But maybe Cachex has that feature built in? Seems like ConCache does this by default.
Also Liked
csadewa
axelson
It’s a little buried in the documentation but cachex can handle this with Cachex.fetch:
https://hexdocs.pm/cachex/reactive-warming.html#content








