Cruz

Cruz

Best way to keep read-only table in memory

Hello,

I have a design related question. I’d like to keep a very large list of products in-memory to serve multiple clients and queries. My first implementation uses structs, and it’s a good start but I now want to explore some of the more advanced options of the ecosystem, e.g. ETS, Mnesia, etc.

I was in the middle of refactoring to an ETS-based solution, but I stopped when I found out I couldn’t have two different keys on the same ETS table. I think I still can use “ETS match patterns” to get data based on the 2nd key, but I wonder if I should just use Mnesia instead.

This is just a read-only cache level for my app. There’s a DB that I’ll query to populate the ETS or Mnesia in-memory table on startup. The data will change only every two or three months; and at that point, reloading it is totally fine. So, I don’t think I need to go with something more complex like CacheX.

Obviously, I can use a bit of brute force, and have a 2nd ETS table with the 2nd key as key, get the 1st key, and then query the 1st table. Kind of an reverse index table. However, this seems somehow ugly. So, again, should I use Mnesia or something else instead?

Thank you,

Marked As Solved

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

If you need in memory multi column (non key) lookups you’re going to start making things pretty complex. No matter what you do, if you want to avoid linear scans you’ll need to maintain secondary indices. Adding in mnesia won’t really change that, or even something fancy like an in memory sqlite table.

15k items isn’t really all that many. You could easily just have 1 table for canonical key → value pairs, and then N tables, one per column you want to query on, containing value → canonical key pairs.

Also Liked

dimitarvp

dimitarvp

I might be severely off the mark here but I believe they were referring to something like FastGlobal. It produces a compiled module for you that would contain code lines like these:

def get("column1-key1"), do: "value1"
def get("column2-key1"), do: "value1"
def get("column1-key2"), do: "value2"

(This is the code generated by the library. You don’t write that. You do FastGlobal.put(:key, "value") and that’s it.)

…Which is the fastest ever access you can get with Erlang / Elixir. However, every changing of value has a heavy runtime cost (make sure to go through the README file). So only use FastGlobal for very rare writes and a ton of reads.

As Ben said, 15K records is nothing. Caching every value several times (on as many keys as you need) is a perfectly fine strategy at that small scale.

Cruz

Cruz

OK. I’m not even close to finish the little system I’m building, but I thought I could share some thoughts already.

I did some very rudimentary bench marking with a vanilla-type table ( 1 key - 1 value) per entry, configure each solution to perform 10M reads, and measure the throughput. I didn’t measure the load time (and the writes) because as I said before even several minutes is acceptable in my case. All these were done against only one process.

The ETS based solution clocked at: 1,079,098 reads/sec

I thought that was quite impressive until I saw the FastGlobal based results: 128,205,128 reads/sec

That’s crazy fast. And, the simple macro-based solution suggested by Kabie was even faster: 212,765,957 reads/sec

So, you’re absolutely right. If speed is the main factor, macros are the best choice.

However, having to recompile and redeploy every time a value changes in the data is something that will carry a cost. I’ll keep these solutions in my back pocket, and suggest them as an option, but for the moment I’ll continue with ETS. I also played with a larger table and saw the performance decreasing, but I’m sure is still going to be more than enough for my app.

One last thought, at the beginning of this task (and thread), I dismissed CacheX because I have to maintain two indexes in the main table. However, all the solutions considered (except Mnesia) require me to handle the additional index directly. So, I reconsidered CacheX, and I now think it is the right solution for most scenarios. I’m in a unique situation because my data changes between 3 and 6 times a year.

Thanks again to all of you for your help. You guys rock,

Cruz

Cruz

Cruz

Yes, that’s precisely my use case. Thank you so much. You’ve been very patient and helpful

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
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
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
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
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
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
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
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
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement