fireproofsocks
Best way to warm up cache
I am using Cachex to store several types of data for fast retrieval, but all of it is also in the database. I have experimented with adding a task to the application.ex that will read the database and populate the ETS table (i.e. Cachex) with current sessions. It works… but during deployments it can start barging so much if the database isn’t available when the app starts that it made me take it out.
Is that the wrong approach? My auth layer looks ONLY to the ETS cache, NOT to the database (for performance reasons), so it would be a problem if the app had to be restarted for any reason – everybody would get logged out! Is there a better way to warm up cache that isn’t so brittle?
I have something like this in my application.ex:
defmodule Auth.Application do
alias Auth.Contexts.SessionContext
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
worker(Cachex, [:sessions_cache, []], id: :sessions_cache),
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Auth.Supervisor]
ret = Supervisor.start_link(children, opts)
# Trying this as a task outside of the supervision tree...
warmup_task = Task.async(fn -> SessionContext.warm_up_cache() end)
Task.await(warmup_task)
ret
end
end
What are alternative approaches? Better ways?
Most Liked
axelson
I’m going to address only part of the question:
In my opinion, the majority of the time looking ONLY at the cache is the wrong approach. Instead I would use (and do use) Cachex.fetch/4 It allows you to get the data from the cache in the typical case, but if that specific entry is not in the cache it will hit the database (and the database will be queried only once, even if multiple processes request the same key at the same time!).
It would look something like this:
def user_logged_in?(user_id) do
Cachex.fetch(:my_app_cache, "user_logged_in_#{user_id}, fn user_id ->
# Make database call here
end)
end
Then if you call user_logged_in?/1 a second time you will get the cached value without anything hitting the database
OvermindDL1
Cachex has a distributed mode for note.
Cachex also has this.
Cachex has a distributed mode that you can setup with multiple synchronization methods you can setup.
I love this fallback approach that Cachex uses, cannot recommend it enough!
Storing misses is also a good thing, I do that (and I’m very eager about clearing keys in my cache on certain actions that even ‘might’ cause an update).
However, if you are worried about someone enumerating things then you really should use both a UUID and throttle any ‘busy’ connection like that (like a plug that stores an IP in a cache or ets table, even per single node is fine, and just store the access time associated with the IP each time in a list in each entry that is pruned of ‘too old’ times and then sleep for a time related to the length of that list, perhaps exponentially as that would catch siege bots really fast but humans wouldn’t notice).
Yep this!
Cachex is pretty amazing. Every cache related feature I’ve needed it has already had. ^.^
Harrisonl
Just to further touch on the above point - if you ONLY look at the cache for your session data then if you actually need to scale your application to two nodes, then users will be logged in/out depending on which node their connection goes through. To get around this you would need to implement a distributed cache across your nodes, adding another level of complexity. So i agree with @axelson and have a “cache miss” strategy in place which will fallback to the DB is the write way to go there. This means that your cache can have a short TTL (say 1- 5 minutes) meaning, that if the connect to multiple different servers and are logged out on server A, then at most they will be logged into server B for 5 minutes (but you would still probably want some sort of cache invalidation in place).
In regards to warming your cache, what you can do is have a simple gen server which starts with your application, then populates your cache after a certain amount of time.
defmodule CacheWarmer do
use GenServer
def start_link, do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
def init(_) do
start_timer()
{:ok, nil}
end
def handle_info(:warm, _state) do
start_timer() # Restart the timer again if you want this to continuously warm the cache.
SessionContext.warm_up_cache()
{:noreply, nil}
end
def start_timer, do: Process.send_after(self(), :warm, 1000 * 60 * 5) # 5 minutes
end
What you can then also do here, is using this Genserver is to have it act as a janitor process and every say 20 seconds, check the database for expried sessions and evict them from the cache.
Let me know if you have any other questions! I’d be happy to help out
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








