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
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
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
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
Yes, that’s precisely my use case. Thank you so much. You’ve been very patient and helpful
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








