Live data from Hacker News

Building a Cache in Elixir

openmymind.net

11–20 of 27 posts

Re: Building a Cache in Elixir

#11
post #6

One thing you can do is use :ets.slot/2 to do random probing of the cache and evict entries. The way to do so is simple, just have a process every few milliseconds check a random set of keys (say, 50 keys), and check if they expire. If >25% of keys were about to expire, repeat this process again instantly, until the % of keys expired in the batch is This is how Redis versions prior to 6.0 implemented key expiry. It's…

How does Redis do it now in 6.0 and later?

Re: Building a Cache in Elixir

#12
post #4

You can even get a simple cache running quite quickly with a Genserver and ETS. About the post, I'm not sure what dcache offers over Nebulex though.

If you want a simple, easily understood cache and didn't want to write all the logic using GenServers and ETS you could use dcache.

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.

Re: Building a Cache in Elixir

#13
post #8

"nil is greater than any integer", that was astounding. Which languages have a no-data is bigger than max_int rule?

There is a total order on (almost) all data types in the beam. This is useful for arbitrary sorting and comparison of different types. 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.

For me, no data is either NaN or zero, but +infinity is was the astounding part.

Also, for some languages, not failing with an exception when comparing different strong types, could be a source of bugs, generally.

Re: Building a Cache in Elixir

#14

The purging is a scan with `next()` and a `lookup()` for each item. Since the logic is a simple `<` comparison, could this be done with a single `match_delete` instead? (Unfortunately, ETS match specifications are quite wonky, so without a lot of fiddling myself, I can't suggest exactly what the comparison logic would look like, but I know that you can express certain things like comparisons with it.) That would cut…

> Unfortunately, ETS match specifications are quite wonky

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

Re: Building a Cache in Elixir

#15

The purging is a scan with `next()` and a `lookup()` for each item. Since the logic is a simple `<` comparison, could this be done with a single `match_delete` instead? (Unfortunately, ETS match specifications are quite wonky, so without a lot of fiddling myself, I can't suggest exactly what the comparison logic would look like, but I know that you can express certain things like comparisons with it.) That would cut…

That's what `ets:fun2ms` at least in Erlang. Not sure how does that map in Elixir?

Re: Building a Cache in Elixir

#16
post #6

One thing you can do is use :ets.slot/2 to do random probing of the cache and evict entries. The way to do so is simple, just have a process every few milliseconds check a random set of keys (say, 50 keys), and check if they expire. If >25% of keys were about to expire, repeat this process again instantly, until the % of keys expired in the batch is This is how Redis versions prior to 6.0 implemented key expiry. It's…

Why not use a min heap to track expiry times? (Or a doubly-linked list, if lifetimes are uniform?)

Re: Building a Cache in Elixir

#17
post #13

Earlier quoted context omitted.

There is a total order on (almost) all data types in the beam. This is useful for arbitrary sorting and comparison of different types. 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.

For me, no data is either NaN or zero, but +infinity is was the astounding part. Also, for some languages, not failing with an exception when comparing different strong types, could be a source of bugs, generally.

In Elixir, nil is an atom (a global enum namespace), it's not a special data type that has meaning outside of a convention (heavily supported by the stdlib) that "you should use it to mean nothing"[0]. To hammer this home, nil is meaningless and not generally understood by that convention in Erlang (which often returns undefined).

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).

Re: Building a Cache in Elixir

#18
post #4

You can even get a simple cache running quite quickly with a Genserver and ETS. About the post, I'm not sure what dcache offers over Nebulex though.

Nebulex shines when you need a distributed cache. ETS is per-node so you'd need to handle synchronizing the data if you ever scale horizontally.

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

Re: Building a Cache in Elixir

#19
> I've always been a firm believer that micro-optimization is a critical task for developers to undertake. In the worst case, it tends to be a great teacher. It's also only through such efforts that informed decisions, based on previously gained knowledge and facts, can be made about what truly is and isn't worth pursuing with respect to performance.

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.

Re: Building a Cache in Elixir

#20
post #16
post #6

One thing you can do is use :ets.slot/2 to do random probing of the cache and evict entries. The way to do so is simple, just have a process every few milliseconds check a random set of keys (say, 50 keys), and check if they expire. If >25% of keys were about to expire, repeat this process again instantly, until the % of keys expired in the batch is This is how Redis versions prior to 6.0 implemented key expiry. It's…

Why not use a min heap to track expiry times? (Or a doubly-linked list, if lifetimes are uniform?)

that was my immediate first question
Post reply on HN