3
votes

Short and sweet; I'm building a new project which I could back with ETS, but I'd prefer to back with Mnesia - due to things like built-in transactions which may come in handy. I don't care about replication and scaling onto other nodes, which is why I assume Mnesia's performance has overhead on ETS.

zackehh:~/GitHub/my_project$ MIX_ENV=test mix bench
Settings:
  duration:      1.0 s

## BasicBench
[19:24:15] 1/4: retrieve key hit mnesia
[19:24:23] 2/4: retrieve key hit
[19:24:30] 3/4: insert new key mnesia
[19:24:33] 4/4: insert new key

Finished in 25.24 seconds

## BasicBench
insert new key                 10000000   0.63 µs/op
retrieve key hit               10000000   0.64 µs/op
retrieve key hit mnesia        10000000   0.69 µs/op
insert new key mnesia            500000   4.70 µs/op

I ran a few (local) benchmarks, and it's clear that Mnesia is comparable for read perf, but the write perf is far slower. I'd like to know if there are any ways to speed it up (e.g. turning off replication checking, etc).

Additional info:

Mnesia Table:

[
    { :ram_copies, [node()] },
    { :local_content, true },
    { :attributes, [:key,:value] }
]

Tests

  • ETS operations use :ets.lookup/2 and :ets.insert/2
  • Mnesia operations use :mnesia.dirty_write/1 and :mnesia.dirty_read/2

I've been trawling the docs for hours and nothing has jumped out as a potential way to speed this up - so I might be staring into a perf wall, but if someone could clarify/confirm/suggest, it'd be appreciated.

2

2 Answers

3
votes

Short answer:

Although Mnesia uses ETS as engine for non-persistent data storage (ram_copies tables) but the overhead of Mnesia for checking if the selected table is replicated or has indices makes it slower than ETS in writing new records.

From Documentation:

For non-persistent database storage, prefer Ets tables over Mnesia local_content tables. Even the Mnesia dirty_write operations carry a fixed overhead compared to Ets writes. Mnesia must check if the table is replicated or has indices, this involves at least one Ets lookup for each dirty_write. Thus, Ets writes is always faster than Mnesia writes.

However you can consider mnesia:ets/2 to benefit from performance of ETS on your Mnesia tables:

mnesia:ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)

From Documentation:

mnesia:ets/2 calls the Fun in a raw context that is not protected by a transaction. The Mnesia function call is performed in the Fun and performed directly on the local ets tables on the assumption that the local storage type is ram_copies and the tables are not replicated to other nodes. Subscriptions are not triggered and checkpoints are not updated, but it is extremely fast. This function can also be applied to disc_copies tables if all operations are read only. For details, see mnesia:activity/4 and the User's Guide.


Edit: I wrote a benchmark and run it on my machine to clarify the rough differences among mnesia:ets/1, mnesia:dirty_write/1 and ets:insert/2.

mnesia_ets(Limit) ->
    application:ensure_started(mnesia),
    mnesia:create_table(foo, [{ram_copies, [node()]}]),
    WriteFun = fun() -> 
        [mnesia:write({foo, I, I}) || I <- lists:seq(1, Limit)]
    end,
    timer:tc(fun() -> mnesia:ets(fun() -> WriteFun() end) end).

ets(Limit) ->
    ets:new(bar, [named_table, public]),
    WriteFun = fun() -> 
        [ets:insert(bar, {bar, I, I}) || I <- lists:seq(1, Limit)]
    end,
    timer:tc(fun() -> WriteFun() end).

mnesia(Limit) ->
    application:ensure_started(mnesia),
    mnesia:create_table(baz, [{ram_copies, [node()]}]),
    WriteFun = fun() -> 
        [mnesia:dirty_write({baz, I, I}) || I <- lists:seq(1, Limit)]
    end,
    timer:tc(fun() -> WriteFun() end).

And the results for writing 10000000 records are as follows:

  • 4303992 microseconds for ets:insert/2
  • 15911681 microseconds for mnesia:ets/1
  • 29798736 microseconds for mnesia:dirty_write/1

So I can conclude that mnesia:ets/1 is faster than mnesia:dirty_write/1 for the aforementioned reasons, but it is still slower than ets:insert/2.

2
votes

As pointed out by Hamidreza Soleimani, Mnesia will always be slower than ETS, which in most situations isn't a problem anyway. Just wanted to point out another possibility. I don't know if it's going to help in your case but it's always worth having a look.

This slide by Erlang Factory presents a way of implementing a custom backed for mnesia. It may (or may not) allow you to skip the checks that slow down Mnesia when comparing to ETS.

http://www.erlang-factory.com/static/upload/media/143415340626199euc2015mnesialeveldb.pdf