fireproofsocks

fireproofsocks

Caching Strategies in Elixir for Microservices

This is a general water-cooler type of question for all the smart folks in this forum – thanks for any feedback and apologize if this is too wordy.

The problem: requests to /api/expensive responds too slowly.
The solution: caching. (Unless there’s some miracle alternative?).

One common approach implements a callback… i.e. “if cached result exists, return it, if not, then perform the callback: do the expensive operation, cache it, and then return it.” That’s fairly clean to implement, but I’ve seen that solution fail when lots of requests enter into the callback before the cached result is calculated. I’ve heard this condition referred to as a “cache slam”, and in PHP (and presumably with other languages too?), using a semaphore was required to lock and serialize requests in order to avoid it. Frequently, the database was the unavoidable “expensive” thing in this scenario.

One of the drawbacks of the above is that the code isn’t as clean… you can’t tell from looking at the route whether or not the results accurately reflect what is in the data model. You don’t know if you’re looking at cached data or the results directly from the database.

So, the next tweak is often to add an optional “refresh” parameter that triggers a fresh lookup. Although that works, you sacrifice even more clarity in your code.

In a super-clean/transparent API world of resources, I thought it might be a cleaner implementation if you implemented a cache service. In other words, /api/expensive ALWAYS performed that expensive operation (just as its name suggests). And a cache service could expose a route like /api/cache/expensive that would store the result of that operation. You could make POST/PUT operations to the cache service to add/update its contents, and there would never be any guessing – the cache endpoints would contain cached data, just as its name suggests.

Behind the scenes, we’d have to implement some message queue or callbacks to ensure that any changes to the data behind one of the expensive endpoints would cause the result to be stored in the cache service. Thoughts?

I’ve seen some discussion like this:

That’s really over my head, but is that a recommended solution to this problem?

I’m new to Elixir/Erlang/Phoenix, so I admit that my notions of caching are probably out of whack with what is idiomatic here, so I’d love some guidance and thoughts on how others have dealt with this problem. Many thanks!

Most Liked

OvermindDL1

OvermindDL1

I use the Cachex library for this.
I just set a fallback function that queries the database, set how often it should be considered expired (configurable key-by-key too if you need), set the janitor to sweep it every once in a while to clean up old stale entries, then I hit the cache as often as I want. It locks based on the key so concurrent access on a single key will pause on first access until it is resolved and cached then it returns them to all waiting, and others can be run as well at the same time. Then you can just use the Cachex api (I wrap it in case I ever change it of course) to always access that value and it returns it immediately via ETS if it exists else it locks and waits until the fallback function returns. And it even tells you in the result if it got it from the cache or if it had to acquire it as well if you need to know that too. You can purge any keys from the cache or all at any time you want, can set expirations for whatever makes sense for a given key, etc… etc…

It is single-node only though, so if you need to keep multiple nodes in ‘eventual sync’ then just send a (rpc?) message between them to clear that key on all nodes when you perform an update in the database (that’s what I do, eventual consistency in the range of a second is more than enough for my use for what I’m caching, otherwise I hit the database every time and use a materialized view to let the database do the caching of complex queries).

I’m not really a fan of stack overflow the more I see of it… >.>

axelson

axelson

Scenic Core Team

In general I’d prefer Cachex instead of a GenServer since it is backed by :ets. That means that if the data is already cached (and fresh) then the reads don’t need to be serialized through a GenServer resulting in higher performance. Also I think there are a lot of correctness guarantees that are solved by Cachex already that you’d have to re-implement in a custom GenServer.

hpopp

hpopp

Cachex is great, don’t get me wrong, and I totally use it when I need a quick KV cache, but perhaps I didn’t fully clarify the point I was making. Treat a GenServer as your data store, complete with the necessary business logic, and you’ll probably find yourself needing a cache less often.

Where Next?

Popular in Discussions Top

Rustixir
Hi everyone, im working on find best language/framework/system for high concurrency, high performance and stable performance after wor...
New
arpan
Hello everyone :wave: Today I am very excited to announce a project that I have been working on for almost 3 months now. The project is...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
WolfDan
After doing a port from a c++ library to my project in phoenix I’ve seen that I need a faster way to run this algorithm and I found this ...
New
tmbb
This is a post to discuss the new Phoenix LiveView functionality. From Chris’s talk, it appears that they generate all HTML on the serve...
342 18146 126
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
und0ck3d
Hello everyone! A few days ago I’ve created a topic here about how people were creating CMSs with Elixir and Phoenix. I’ve been studying...
New
opsb
We’re considering our architecture from a viewpoint of scaling our traffic heavily over the next 6 months. Our current deployment is runn...
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

Other popular topics Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
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
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement