mnesia is the database written in Erlang that is part of the open source Erlang release. It allows storage of any Erlang type, including complex tuples and records, and various methods of querying and selecting these items.
Continuing the saga of my
server push project, I need to store the data that is generated by the server events so that web clients can browse the historical information. This gave me the opportunity to work out how to use mnesia - it turns out to be quite easy.
First steps are the following functions that need to be called to initially setup the mnesia system:
mnesia:create_schema([node()]).
mnesia:start().
The easiest way of using mnesia seems to be to store and retrieve Erlang records. Defining a record for my data is done with '-record':
-record(odds, {key, date, meeting, race, update_time, close_time, pools, dividends})This data is dividend information for a particular race produced by a Totalizator agency. When a race runs, at various intervals an update of the amount of money bet on each runner in the race is provided. This 'odds' record holds that information. The data in the record also has complex information. For example, 'dividends' is an Erlang list of tuples, one for each entrant in the race, each tuple containing the runner number, current dividend if that runner wins and current dividend if it comes 1st,2nd or 3rd, etc).
To create a table to hold instances of this record type, the mnesia function is 'create_table':
mnesia:create_table(odds,
[{attributes, record_info(fields, odds)},
{disc_copies, [node()]},
{type, bag}]).
First we pass the name of the table, odds, and then a list of key/value pairs holding configuration information about the table. The first, attributes, lists the field names in the table. The function 'record_info' is used to return all field names from the 'odds' record. This saves us from manually entering [key, date, meeting, race, update_time, close_time, pools, dividends] and protects us from changes to the record structure.
The 'disc_copies' attribute lists the nodes that will hold disc based copies of this table. In this case, only our node. The type of 'bag' means that multiple entries can exist in the table for the same key. One of the other types, 'set', is used for unique keys. Mnesia automatically assumes that the first element in the record ('key' in our 'odds' record) is the key field. For the 'odds' structure this will actually be a tuple of {date,meeting,race} so I can pull all odds event information for a particular race quite efficiently.
My server event producer sends a notification to a 'notifier' process as outlined in my
previous weblog posting. The event it provides is actually an instance of the 'odds' record. By creating a process to handle these 'odds' events and I can easily store them in the database:
odds_monitor() ->
receive
{odds_change, {Date,Meeting,Race,UpdateTime,CloseTime,Pools,Dividends}} ->
mnesia:transaction(
fun() ->
mnesia:write(#odds{key={Date,Meeting,Race},
date=Date,
meeting=Meeting,
race=Race,
update_time=UpdateTime,
close_time=CloseTime,
pools=Pools,
dividends=Dividends})
end),
odds_monitor()
end.
All updates to mnesia need to be done as part of a transaction. The 'mnesia:transaction' function takes a function as an argument. It is inside that function that all the database transactions should occur. In this case a simple write of an 'odds' record. The syntax '#odds{..}' creates a new odds instance.
With this process running and registered with the event notifier I now get the data stored in the database as well as sent to the web clients. Getting all the odds data for a given race is as simple as:
mnesia:transaction(
fun() ->
mnesia:read({odds, {{2005,11,25},"MEETING1",1}})
end).
'read' returns the object that has the specific key. Or in the case of a 'bag' table type, all the objects with that key. It is also possible to do quite complex queries across all fields in the table but for my purposes this call is enough. An example of the data returned is:
{atomic,{odds,{{2005,11,25},"MEETING1",1},
{2005,11,25},
"MEETING1",
1,
{21,3,0},
finished,
[{win,14831},{place,9579},{trifecta,12400}],
[{1,3.20000,1.35000},
{2,2.20000,1.25000},
{3,8.30000,2.15000},
{4,34.2000,4.70000},
{5,scratched,scratched},
{6,27.3500,5.45000},
{7,scratched,scratched},
{8,20.0500,3.65000},
{9,34.1000,5.75000},
{10,9.25000,1.80000},
{11,20.5000,4.70000}]}}The documentation for mnesia has a
good tutorial, as well as the
reference of all the functions.
Categories: erlang