Building a Cache in Elixir
openmymind.net
openmymind.net
This is how Redis versions prior to 6.0 implemented key expiry. It's a very simple algorithm and can prove quite efficient at evicting expired keys without having to constantly scan the entire range.
Large, mutable, shared state like a cache works much better if it lives in ETS. Any process can read/write to a properly configured table in a few microseconds. Scales very well to large data sizes and high QPS.
The random probing approach has the disadvantage that it stores expired entries longer than necessary. This either wastes memory or reduces cache hit rate. Either of these seem preferable to limiting throughput and maximum entry count, as the min heap would.
This why I like hobby projects. In my day-to-day work, my job is to deliver value and "get stuff done". However, in hobby projects, I can feel free to dive into an optimization rabbit hole and learn a whole bunch of stuff. Even if I don't ship my hobby projects, I still gain a lot from the background knowledge.
A joy to look at in a sea of hard-to-read blog posts
For me, the font was also too large, but 0.5 seconds later, it wasn't. But no need to comment about something like this (same with the parent) as it's not actually about the article, just about something website specific.
About the post, I'm not sure what dcache offers over Nebulex though.
Nebulex looks more complicated than dcache, just comparing the getting started info: "In order to give more flexibility and loading only needed dependencies, Nebulex makes all its dependencies as optional." Then it goes on to describing shards, decorators, telemetry and external adapters like Cachex and Redis. So before I can run `mix deps.get` to include this package I now have to make decisions or at least read thru what those things are for this package.
I prefer picking simple dependencies and add complexity later on if needed.
That's exactly what having optional dependencies does, though. Except that when you inevitably realize that e.g. you need to monitor your cache-hit ratio in production, enablement for that means adding a suggested dep and then flipping a config flag to use it, rather than finding your own separate dep that does that thing, and then gluing the two "simple" components together using code that is both only maintained by you (rather than the upstream), and which must necessarily only touch the outside interface of both of the components, and so may be many times less efficient than code that hooks into one component or the other.
It's also helped by an eye toward low coupling of these support libraries. Elixir's Phoenix web framework, for example, is "batteries included" in some senses; but all those batteries are only included because your own generated skeleton code pulls them in; not because the framework itself depends on them in any way. So if you don't need e.g. i18n, or views, or really anything beyond an API that responds with JSON; then you can delete ~five lines from your skeleton project, drop the relevant deps, and the resulting project will be a lightweight web framework that just serves API calls. (This is why there isn't really a "lightweight" Elixir web framework ala Flask/Sinatra/etc.; Phoenix is both Elixir's batteries-included web framework, and its lightweight/minimal web framework, depending on how you use it.)
But yeah I guess the docs can be a bit overwhelming at first glance.
Personally I like the decorators, especially as Ecto does not provide a cache solution.
To keep it simple, you could rewrite this post using ex_shards as it handles scaling ETS (which IIRC is the backend by default for Nebulex).
That said, I was able to write a distributed session store with Nebulex pretty quickly. Then extended it to support live sessions, though it's not up to feature parity with its ETS counterpart if anyone wants to help out with my PR https://github.com/pentacent/phoenix_live_session/pull/14
Matchspecs are gnarly! Very annoying to wrap your head around, and it's closer to being an AST than anything.
Shameless plug: I had written a library at one point to make matchspecs with Mnesia easier, but it might also work with ETS (haven't tested unfortunately): https://github.com/queer/lethe
:ets.select_delete(table, [{{:_, :_, :"$1"}, [], [{:>, :"$1", {:const, expires}}]}])I say almost because floats and their equivalent integer are different items but they are equal in the total order. In practice this is fine, and I have never heard of this causing a problem.
Also, for some languages, not failing with an exception when comparing different strong types, could be a source of bugs, generally.
Elixir/BEAM is strongly typed. There is no coercion going on when you are doing comparisons.
[0] note that in elixir, "if" is in the stdlib, it's sugar over case, and the falsiness of nil is software-level (only false and nil are falsy in elixir, which is sane).