fireproofsocks

fireproofsocks

Periodically clearing out values store in a process? (i.e. enforcing TTL, e.g. in an Agent)

I’m trying to learn more about use processes to store state (e.g. following this page), and I’m wondering how one might implement a TTL in a value. For example, if I wanted to cache a web request for a short period of time specified at runtime, what strategies could be employed? Sorry, I know this is kind of open ended…

Marked As Solved

lucaong

lucaong

Here is a minimal example of the first strategy. It does not handle situations like setting the same key more than once, but it should give you some inspiration:

defmodule Cache do
  use GenServer

  def start_link() do
    GenServer.start_link(__MODULE__, [], [])
  end

  def put(pid, key, value, ttl \\ 10_000) do
    GenServer.call(pid, {:put, key, value, ttl})
  end

  def get(pid, key) do
    GenServer.call(pid, {:get, key})
  end

  # GenServer callbacks

  def init(_) do
    state = %{}
    {:ok, state}
  end

  def handle_call({:put, key, value, ttl}, _from, state) do
    Process.send_after(self(), {:expire, key}, ttl)
    {:reply, :ok, Map.put(state, key, value)}
  end

  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  def handle_info({:expire, key}, state) do
    {:noreply, Map.delete(state, key)}
  end
end

You can verify in the console that the TTL works:

{:ok, pid} = Cache.start_link()

# Store something in the cache
Cache.put(pid, :foo, 123)
#=> :ok

# Get the stored value:
Cache.get(pid, :foo)
#=> 123

# Wait more than 10 seconds, then try again:
Cache.get(pid, :foo)
#=> nil

Also Liked

lucaong

lucaong

One common pattern to implement TTL is the following: the process that maintains the state (usually a GenServer) upon setting some data in the state would also send a delayed message to itself with Process.send_after/4 with a timeout equal to the TTL. The delayed message would contain some reference to the data to expire (for example the key, if it is some sort of key/value cache). Upon receiving the message, the GenServer would clear the expired data.

Another common pattern is to invalidate the cache lazily: the cached data would contain a timestamp, and upon retrieval the process would check if the cached value is stale, and if so evict it and consider it a cache miss. This would mean that a value could stay in cache forever if it’s never requested, so often one would implement some sort of fixed-capacity cache (like an LRU cache), or periodically clean up the expired values by enumerating or sampling the entries.

shanesveller

shanesveller

Along with the the start_link alternative Luca presented, if you need more than one running copy of a process within a single BEAM node that you can still reference from elsewhere “by name”, take a look at stdlib’s Registry and via-tuples.

From there, another refinement of Luca’s later suggestions would be to brush up on child specifications and child_spec/1 which give you finer control over the relationship between “how I supervise this process” and “how it receives arguments”. You’re not limited to a single catch-all argument if you don’t want to be or if it doesn’t make sense for your purposes. This technique translates to many OTP process types and isn’t limited to just GenServers, either.

dimitarvp

dimitarvp

Everything you described should be doable with Cachex. It also has a TTL implementation. You don’t have to reinvent it unless it’s a learning project.

Last Post!

dimitarvp

dimitarvp

Everything you described should be doable with Cachex. It also has a TTL implementation. You don’t have to reinvent it unless it’s a learning project.

Where Next?

Popular in Questions Top

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
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
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
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New

Other popular topics Top

hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
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
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement