Fun = fun() -> {programming, erlang, elixir} end.


Mnesia Basics

05 May 2014

I wanted to document this basic Mnesia module.

It’s pretty simple:

-export([install/0, start/0, insert/1]).

-record(friends, {name, surname}).

nodos() ->
    ['a@', 'b@', 'c@'].

install() ->
    Nodes = nodos(),
    ok = mnesia:create_schema(Nodes),
    rpc:multicall(Nodes, application, start, [mnesia]),
                        [{attributes, record_info(fields, friends)},
                         {disc_copies, Nodes}]),
    rpc:multicall(Nodes, application, stop, [mnesia]).

start() ->
    Nodes = nodos(),
    rpc:multicall(Nodes, application, start, [mnesia]),
    mnesia:wait_for_tables([friends], 5000).

insert(0) ->
insert(N) ->
    mnesia:dirty_write({friends, N, N}),
    insert(N - 1).

Let’s set up our database cluster. We need to run all three nodes. For example, for node ‘a’.

$ erl -name a@ -setcookie abc

Note the node name and the cookie. The cookie must match in all nodes.

Once the three nodes are running let’s create the schema and start Mnesia at all of them.

$ erl -name a@ -setcookie abc
Erlang R16B02 (erts-5.10.3) [source-b44b726] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
(a@> c(mnesia_test).
(a@> mnesia_test:install().

=INFO REPORT==== 5-May-2014::22:27:25 ===
    application: mnesia
    exited: stopped
    type: temporary
(a@> mnesia_test:start().

Now we have our distributed database up. Let’s insert some data.

(a@> spawn(fun() -> mnesia_test:insert(10) end).

We can go to node ‘c’ and check our new data.

(c@> mnesia:dirty_all_keys(friends).

We can return to node ‘a’ again to insert a process PID in our database.

(a@> mnesia:dirty_write({friends, 2000, spawn(fun() -> receive A -> io:format("~p~n", [A]) end end)}).
(a@> mnesia:dirty_read({friends, 2000}).

Notice the difference if we read {friends, 2000} from node ‘c’.

(c@> mnesia:dirty_read({friends, 2000}).

Do you see the difference with the PID’s first number? Yes, it’s the node, the second one is the process.

Let’s use it from node ‘c’.

(c@> [{_, _, Process}] = mnesia:dirty_read({friends, 2000}).
(c@> Process ! hola.

Check the console in node ‘a’. It works.

Let’s clear the table.

(a@> mnesia:clear_table(friends).

Now, let’s shut node ‘c’ down.

(a@> mnesia:info().
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
friends        : with 0        records occupying 305      words of mem
schema         : with 2        records occupying 537      words of mem
===> System info in version "4.9", debug level = none <===
opt_disc. Directory "/Users/juan/Mnesia.a@" is used.
use fallback at restart = false
running db nodes   = ['b@','a@']
stopped db nodes   = ['c@']
master node tables = []
remote             = []
ram_copies         = []
disc_copies        = [friends,schema]
disc_only_copies   = []
[{'a@',disc_copies},{'b@',disc_copies}] = [schema,
12 transactions committed, 0 aborted, 1 restarted, 101 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []

Note the stopped nodes.

Let’s insert 20 rows from node ‘a’.

(a@> mnesia_test:insert(20).

Let’s start node ‘c’ up.

$ erl -name c@ -setcookie abc
Erlang/OTP 17 [RELEASE CANDIDATE 2] [erts-6.0] [source] [async-threads:10] [kernel-poll:false]

Eshell V6.0  (abort with ^G)
(c@> application:start(mnesia).
(c@> length(mnesia:dirty_all_keys(friends)).

The data is there.

Have fun.