csadewa

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

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

csadewa

Oh, yeah, it’s true, ConCache explicitly mention this functionality in readme. thanks

axelson

axelson

Scenic Core Team

It’s a little buried in the documentation but cachex can handle this with Cachex.fetch:
https://hexdocs.pm/cachex/reactive-warming.html#content

Where Next?

Popular in Questions Top

bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement