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

Kabie

Kabie

Yes, it’s super simple if you don’t need to add things at runtime.

defmodule Data do
  # load external data
  @data [
    %{id: 1, other_key: :a},
    %{id: 2, other_key: :b},
    %{id: 3, other_key: :c},
  ]


  for key <- [:id, :other_key] do
    for row <- @data do
      def unquote(:"by_#{key}")(unquote(row[key])) do
        unquote(Macro.escape(row))
      end
    end

    def unquote(:"by_#{key}")(_) do
      nil
    end
  end

end
benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Can you make this a bit more concrete? What does match mean? equality? pattern matching? are there multiple keys for the same value?

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
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 45766 226
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement