ETS, DETS & Mnesia

Access: public / protected (default) / private.

32.1 ETS — in-memory term storage

T = ets:new(users, [set, named_table, public, {read_concurrency, true}]),
ets:insert(users, {1, "Ann", 30}),
ets:insert(users, [{2, "Bob", 25}, {3, "Eve", 35}]),
ets:insert_new(users, {1, "Dup", 0}),          % false: key exists
ets:lookup(users, 1),                          % [{1,"Ann",30}]
ets:lookup_element(users, 1, 2),               % "Ann"
ets:member(users, 2),
ets:delete(users, 2),
ets:update_element(users, 1, {3, 31}),
ets:update_counter(stats, hits, {2, 1}, {hits, 0}),
ets:tab2list(users),
ets:foldl(fun(Obj, Acc) -> [Obj | Acc] end, [], users),
ets:first(users), ets:next(users, 1),
ets:info(users, size),
ets:delete(users).

32.2 Table types & options

duplicate keys, unique objects Access: public / protected (default) / private. Options: named_table, {keypos, N}, {read_concurrency, true}, {write_concurrency, true | auto}, {decentralized_counters, true}, compressed, {heir, Pid, Data}.

A table is owned by its creating process and deleted when the owner dies (unless an heir is set).

32.3 Match specifications

ets:match(users, {'$1', '_', 30}),                     % [[1]]
ets:match_object(users, {'_', "Ann", '_'}),
ets:select(users, [{{'$1', '$2', '$3'}, [{'>', '$3', 26}], ['$2']}]),
ets:select_delete(users, [{{'_', '_', '$1'}, [{'<', '$1', 18}], [true]}]),

%% Readable match specs with fun2ms (needs the parse transform header)
-include_lib("stdlib/include/ms_transform.hrl").
MS = ets:fun2ms(fun({Id, Name, Age}) when Age > 26 -> {Id, Name} end),
ets:select(users, MS).

32.4 DETS — disk-based tables

Max 2 GB per file; no ordered_set.

{ok, T} = dets:open_file(store, [{file, "store.dets"}, {type, set}]),
dets:insert(T, {k, v}), dets:lookup(T, k), dets:close(T).

32.5 Mnesia — distributed DBMS

Storage types: ram_copies, disc_copies, disc_only_copies. Features: replication across nodes, transactions, indexes, fragmentation. Best for config/session data in Erlang clusters; for large data consider external DBs.

-record(account, {id, owner, balance = 0}).

setup() ->
    mnesia:create_schema([node()]),
    mnesia:start(),
    mnesia:create_table(account, [{attributes, record_info(fields, account)},
                                  {disc_copies, [node()]},
                                  {index, [owner]}]).

deposit(Id, Amount) ->
    F = fun() ->
            [Acc] = mnesia:read(account, Id, write),
            mnesia:write(Acc#account{balance = Acc#account.balance + Amount})
        end,
    mnesia:transaction(F).          % {atomic, Result} | {aborted, Reason}

mnesia:dirty_read(account, 1),       % fast, non-transactional
mnesia:select(account, MatchSpec),
qlc:e(qlc:q([A || A <- mnesia:table(account), A#account.balance > 100])).

32.6 persistent_term, atomics & counters

persistent_term:put({myapp, config}, #{port => 80}),
persistent_term:get({myapp, config}),        % very fast reads, expensive updates

C = counters:new(1, [write_concurrency]),
counters:add(C, 1, 5), counters:get(C, 1),
A = atomics:new(1, []), atomics:compare_exchange(A, 1, 0, 1).