Fl4m3Ph03n1x
Best way to get multiple keys from static ETS table
Background
I have an ets table that has, for each key, a list of items. ETS tables have O(1) complexity when using :ets.lookup, as we all know and they are super fast, but for my specific use case, I need to ask to the ets table for more than 1 key at a time.
This means I have 2 options:
- Perform multiple
lookups - Perform a match or select on the keys
Multiple lookups
I don’t really like this approach because it would mean my operation is not atomic. This is a big no no.
Match or Select on keys
This is my preferred approach since I want the operation to be as efficient as possible. I am also not going to look for patterns in keys, I am simply going to look directly for keys that match my spec, so it should be fast.
I do have several questions about match and select though (see Questions section)
Issue
The problem here is that I don’t know how to use :ets.match or it’s select variant correctly. This is an example:
iex(27)> table = :ets.new(:table, [:public])
#Reference<0.830523521.2311716867.20185>
iex(28)> :ets.insert(table, {:a, [{:banana, :orange, 10}, {:tomato, :potato, 6}]})
true
iex(29)> :ets.insert(table, {:b, [{:car, :moto, 1}, {:plane, :heli, 60}]})
true
iex(30)> :ets.tab2list(table)
[
b: [{:car, :moto, 1}, {:plane, :heli, 60}],
a: [{:banana, :orange, 10}, {:tomato, :potato, 6}]
]
ex(32)> :ets.match(table, [:a, :b]) # I want to get the values from both keys :a :b
[]
Questions
- What am I doing wrong?
- How do I optimize my match queries so they are atomic? From the docs:
Traversals using match and select functions may not need to scan the entire table depending on how the key is specified. A match pattern with a fully bound key (without any match variables) will optimize the operation to a single key lookup without any table traversal at all.
- The data on my table will never change. Does it matter if I make the table an
ordered_setor are the gains for match and select not worth it?
Marked As Solved
sorentwo
This sounds like a good use case for :persistent_term. The data will never change so there isn’t a write penalty and you get extremely fast reads.
I’m curious though, if the data doesn’t change why are you concerned about atomicity?
Also Liked
jola
iex(13)> :ets.select(table, [{{:"$1", :_}, [{:or, {:==, :"$1", :a}, {:==, :"$1", :b}}], [:"$_"]}])
[
b: [{:car, :moto, 1}, {:plane, :heli, 60}],
a: [{:banana, :orange, 10}, {:tomato, :potato, 6}]
]
Boom! You can adjust the match body to what you’re interested in. Also note that the match head has to match the table format.
I have a weird fascination with match specs.
jola
The erlang code from that mailinglist could be translated into Elixir like this (using the sample code from the OP):
iex(5)> my_keys = [:b, :a]
[:b, :a]
iex(6)> :ets.select(table, (for key <- my_keys, do: {{key, :_}, [], [:"$_"]}))
[
a: [{:banana, :orange, 10}, {:tomato, :potato, 6}],
b: [{:car, :moto, 1}, {:plane, :heli, 60}]
]
Basically, it uses the comprehension to build a list of match specs, where each one has a fully bound key.
NobbZ
You can’t have a fully bound key in a match spec that checks for many keys. Either do one lookup per key and throw away atomicity (which might not be a great deal as @sorentwo already mentioned) or build a matchspec as shown by @jola (perhaps even use ex2ms and just write functions rather than weirdly nested tuples).
But you’ll need to give up either atomicity or O(1) reads…
dimitarvp
The persistent_term module is a very recent addition. And a very useful one, too!
NobbZ
Well, ETS does not need to be 2 element tuples with the key beeing the first element… When you create the table with :ets.new/2, you can specify the position of the key using the :keypos option.
You can then use :ets.lookup/2 to lookup all entries that have match your key.







