Erlang Data Storage Modules
Tutorial
by Flavio Ishii
Erlang Data Storage Modules
•Modules:
•Erlang Terms Storage (ETS)
•Disk Erlang Term Storage (DETS)
•Mnesia Distributed DBMS
• Data Storage Design
Flavio Ishii, Sept. 17, 2009
Tuples
•A Tuple groups items into one entity/
term.
•Used in Erlang data storage modules.
Flavio Ishii, Sept. 17, 2009
Tuples
> N = { “Flavio Ishii” }.
> S = [“cycling”,"ultimate frisbee" ].
> P = { person, { 1, N, S } }.
> { person, Properties } = P.
> Properties.
> { Id, Fullname, Sports } = Properties.
> X = lists:append( Sports, [“basketball”] ).
> X.
Flavio Ishii, Sept. 17, 2009
ETS
• Erlang Term Storage
• Constant access time (excl. ordered_set)
• Table Options:
• access rights: private | public | *protected
• types: *set | ordered_set | bag | duplicate_bag
• transfer ownership: {heir, Pid, HeirData}, or
give_away/3
• Various methods to query the table: mnesia
function (pattern matching) or qlc
* default
Flavio Ishii, Sept. 17, 2009
ETS Table Access Rights
• private - Only owner process can read/write
• public - All processes with table id can read/write
• protected - Only owner process can write and
any process with table id can read.
Flavio Ishii, Sept. 17, 2009
ETS Table Types
• set - unique {key, value}, inserts may overwrite.
• [ {b,2}, {a,5} ]
• ordered_set - key ordered and unique tuple
• [ {a,5}, {b,2} ]
• bag - duplicate key allowed
• [ {a,5}, {b,2}, {b,4} ]
• duplicate_bag - duplicate tuple allowed
• [ {a,5}, {b,2}, {b,2} ]
• hash tables: set, bag, and duplicate_bag
• balanced binary tree: ordered_set
Flavio Ishii, Sept. 17, 2009
ETS
> Tab = ets:new(user, []).
> ets:insert(Tab, [{1,flavio},{2,ralph},{3,melissa},{4,bob}]).
> ets:info(Tab).
> ets:match(Tab,'$1').
> ets:match(Tab,{'$1',bob}).
> ets:match_object(Tab,{'$1',bob}).
> ets:select(Tab,[{{'$1',bob},[],['$$']}]).
> ets:select(Tab,[{{'$1',bob},[],['$_']}]).
> qlc:eval( qlc:q([{Y} || {X,Y} <- ets:table(Tab), (X > 2) and
(X < 4)])).
Flavio Ishii, Sept. 17, 2009
DETS
• Stores persistent data in disk/file (disk seeks)
• Process needs to open the file.
• Table may be shared by local processes.
• No ordered_set table type.
• Must be properly closed when process is
terminated.
• Checks for consistency on startup after crash.
Flavio Ishii, Sept. 17, 2009
DETS
> dets:open_file(user, []).
> dets:insert(user, [{1,flavio},{2,ralph},{3,melissa},
{4,bob}]).
> dets:info(user).
> dets:match(user,'$1').
> dets:match(user,{'$1',bob}).
> dets:match_object(user,{'$1',bob}).
> dets:select(user,[{{'$1',bob},[],['$$']}]).
> dets:select(user,[{{'$1',bob},[],['$_']}]).
> qlc:eval( qlc:q([{Y} || {X,Y} <- dets:table(user), (X > 2)
and (X < 4)])).
Flavio Ishii, Sept. 17, 2009
Mnesia
• Erlang’s distributed DBMS
• ETS & DETS core
• Location Transparency
• Fault Tolerance via Table Replication &/or
Fragmentation across nodes
• Transactions, Locking, & Dirty Operations
• Schema Manipulation at runtime
• ACID Properties
Flavio Ishii, Sept. 17, 2009
Mnesia ACID Properties
• Atomicity - succeed on all or no nodes
• Consistency - ensure consistent state
after crash
• Isolation - isolate manipulations on
same record
• Durability - changes are committed to
disk.
Flavio Ishii, Sept. 17, 2009
Mnesia Table Options
• {type, Type} % set, ordered_set, bag
• {record_name, Name}
• {ram_copies, NodeList} % fastest
• {disc_copies, NodeList} % RAM and disc copies
• {disc_only_copies, NodeList} % slowest
• {attributes, AtomList}
• {index, IndexAtomList}
Flavio Ishii, Sept. 17, 2009
Terminal Time!
• Create record structure
• Create schema and tables
• Insert records
• Query records
Flavio Ishii, Sept. 17, 2009
api.hrl
% this file defines the records
-record(counter,{id_name,value}).
-record(user_details,
{password,firstname,lastname,sports=[]}).
-record(user,{id,username,user_details}).
Flavio Ishii, Sept. 17, 2009
Part 1/4 of api_db.erl
% this file defines the records
-module(api_db).
-export([init_db/0, add_sport/2, mne_fun_query/1, qlc_query/1]).
-include("api.hrl").
-include_lib("stdlib/include/qlc.hrl").
init_db() ->
mnesia:create_schema([node()]),
io:format("Mnesia schema created~n"),
mnesia:start(),
mnesia:change_table_copy_type(schema, node(), disc_copies),
mnesia:create_table(counter, [{disc_copies,[node()]}
, {attributes, record_info(fields,
counter)}]),
mnesia:create_table(user, [{disc_copies,[node()]}, {index, [username]}
, {attributes, record_info(fields, user)}]),
io:format("Mnesia tables created~n"),
add_sample_data().
Flavio Ishii, Sept. 17, 2009
Part 2/4 of api_db.erl
insert_user(UserRecord) ->
case UserRecord#user.id =:= undefined of
true -> NewUser = UserRecord#user{ id =
mnesia:dirty_update_counter(counter, user_id, 1) };
false -> NewUser = UserRecord
end,
% mnesia:dirty_write(NewUser). or use a transaction...
Fun = fun() ->
mnesia:write(NewUser)
end,
mnesia:transaction(Fun).
add_sample_data() ->
Flavio = #user{username="flavio"
, user_details=#user_details{ password="mypassword", firstname="Flavio",
lastname="Ishii", sports=["biking","basketball"]}},
insert_user(Flavio),
Bob = #user{username="bob",
user_details=#user_details{ password="hispassword", firstname="Bob",
lastname="The Builder", sports=["soccer"]}},
insert_user(Bob),
io:format("Users added to table.~n").
Flavio Ishii, Sept. 17, 2009
Part 3/4 of api_db.erl
% > api_db:add_sport("flavio","football").
add_sport(Un,NewSport) ->
[User] = mne_fun_query({username,Un}),
UserDetails = User#user.user_details,
Sports = UserDetails#user_details.sports,
NewList = lists:append(Sports,[NewSport]),
NewUser = User#user{user_details=#user_details{sports=NewList}},
insert_user(NewUser).
% This is one method of querying a user.
% > api_db:qlc_query({username,"flavio"}).
qlc_query({username,Username}) ->
F = fun() ->
qlc:e(qlc:q([U#user.user_details || U <- mnesia:table(user)
, U#user.username =:= Username
]))
end,
mnesia:transaction(F).
Flavio Ishii, Sept. 17, 2009
Part 4/4 of api_db.erl
% This may not be ideal but it demonstrates the use of Pattern Matching
% > api_db:mne_fun_query({username,"flavio"}).
mne_fun_query({username,Un}) ->
MatchHead = #user{username='$1', _='_'},
Guard = [{'=:=','$1',Un}],
Result = ['$_'],
MatchSpec = [{MatchHead, Guard, Result}],
mnesia:dirty_select(user, MatchSpec);
% > api_db:mne_fun_query({sport,"soccer"}).
mne_fun_query({sport,Sport}) ->
UserDetails = #user_details{_='_',sports='$1'},
MatchHead = #user{user_details=UserDetails,_='_'},
Guard = [{'=:=','$1',[Sport]}],
Result = ['$_'],
MatchSpec = [{MatchHead, Guard, Result}],
F = fun() -> mnesia:select(user, MatchSpec) end,
mnesia:transaction(F);
mne_fun_query(_) -> io:format("No match~n").
Flavio Ishii, Sept. 17, 2009
Head to Head Feature List
ETS DETS Mnesia
Persistent storage X X
Complex search queries X X X
Distributed Replicated data storage X X X
Table Fragmentation X
Fault tolerance via replication X X X
Tight Erlang coupling X X X
Complex objects & relationships X
Dynamic reconfiguration X X X
Table indexing X
Distributed Transactions X
Flavio Ishii, Sept. 17, 2009
Possible Design Permutations
Flavio Ishii, Sept. 17, 2009
More Permutations
• Table access (i.e. public,
protected)
• Table type (i.e. set, bag...)
• Table locks
• Distribution via replication
and/or table fragmentation
Flavio Ishii, Sept. 17, 2009
Useful Mnesia Functions
• change_table_access_mode/2
• change_table_copy_type/3
• backup/2
• install_fallback/2
• restore/2
• dump_tables/1
• lock/2
• move_table_copy/3
• async_dirty/2
• sync_dirty/2
• add_table_copy/3
• transform_table/3
• change_table_frag/2
• activity/4
Flavio Ishii, Sept. 17, 2009
Resources
• Programming Erlang, Joe Armstrong
• ets, dets, qlc, mnesia manuals:
• http://www.erlang.org/doc/man/
• Mnesia User’s Guide:
• http://www.erlang.org/doc/apps/mnesia
• Mnesia - A Distributed Robust DBMS for
Telecommunications Applications, Håkan
Mattsson, Hans Nilsson and Claes Wikström
Flavio Ishii, Sept. 17, 2009
Contact Info
USASK - MADMUC Lab
http://flavioishii.com
@flavioishii
Flavio Ishii, Sept. 17, 2009