aboutsummaryrefslogtreecommitdiffstats
path: root/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mnesia/doc/src/Mnesia_chap5.xmlsrc')
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap5.xmlsrc1398
1 files changed, 1398 insertions, 0 deletions
diff --git a/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
new file mode 100644
index 0000000000..3ec0aa37f5
--- /dev/null
+++ b/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
@@ -0,0 +1,1398 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1997</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Miscellaneous Mnesia Features</title>
+ <prepared>Claes Wikstr&ouml;m, Hans Nilsson and H&aring;kan Mattsson</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev></rev>
+ <file>Mnesia_chap5.xml</file>
+ </header>
+ <p>The earlier chapters of this User Guide described how to get
+ started with Mnesia, and how to build a Mnesia database. In this
+ chapter, we will describe the more advanced features available
+ when building a distributed, fault tolerant Mnesia database. This
+ chapter contains the following sections:
+ </p>
+ <list type="bulleted">
+ <item>Indexing
+ </item>
+ <item>Distribution and Fault Tolerance
+ </item>
+ <item>Table fragmentation.
+ </item>
+ <item>Local content tables.
+ </item>
+ <item>Disc-less nodes.
+ </item>
+ <item>More about schema management
+ </item>
+ <item>Debugging a Mnesia application
+ </item>
+ <item>Concurrent Processes in Mnesia
+ </item>
+ <item>Prototyping
+ </item>
+ <item>Object Based Programming with Mnesia.
+ </item>
+ </list>
+
+ <section>
+ <marker id="indexing"></marker>
+ <title>Indexing</title>
+ <p>Data retrieval and matching can be performed very efficiently
+ if we know the key for the record. Conversely, if the key is not
+ known, all records in a table must be searched. The larger the
+ table the more time consuming it will become. To remedy this
+ problem Mnesia's indexing capabilities are used to improve data
+ retrieval and matching of records.
+ </p>
+ <p>The following two functions manipulate indexes on existing tables:
+ </p>
+ <list type="bulleted">
+ <item><c>mnesia:add_table_index(Tab, AttributeName) -> {aborted, R} |{atomic, ok}</c></item>
+ <item><c>mnesia:del_table_index(Tab, AttributeName) -> {aborted, R} |{atomic, ok}</c></item>
+ </list>
+ <p>These functions create or delete a table index on field
+ defined by <c>AttributeName</c>. To illustrate this, add an
+ index to the table definition <c>(employee, {emp_no, name, salary, sex, phone, room_no}</c>, which is the example table
+ from the Company database. The function
+ which adds an index on the element <c>salary</c> can be expressed in
+ the following way:
+ </p>
+ <list type="ordered">
+ <item><c>mnesia:add_table_index(employee, salary)</c></item>
+ </list>
+ <p>The indexing capabilities of Mnesia are utilized with the
+ following three functions, which retrieve and match records on the
+ basis of index entries in the database.
+ </p>
+ <list type="bulleted">
+ <item><c>mnesia:index_read(Tab, SecondaryKey, AttributeName) -> transaction abort | RecordList</c>.
+ Avoids an exhaustive search of the entire table, by looking up
+ the <c>SecondaryKey</c> in the index to find the primary keys.
+ </item>
+ <item><c>mnesia:index_match_object(Pattern, AttributeName) -> transaction abort | RecordList</c>
+ Avoids an exhaustive search of the entire table, by looking up
+ the secondary key in the index to find the primary keys.
+ The secondary key is found in the <c>AttributeName</c> field of
+ the <c>Pattern</c>. The secondary key must be bound.
+ </item>
+ <item><c>mnesia:match_object(Pattern) -> transaction abort | RecordList</c>
+ Uses indices to avoid exhaustive search of the entire table.
+ Unlike the other functions above, this function may utilize
+ any index as long as the secondary key is bound.</item>
+ </list>
+ <p>These functions are further described and exemplified in
+ Chapter 4: <seealso marker="Mnesia_chap4#matching">Pattern matching</seealso>.
+ </p>
+ </section>
+
+ <section>
+ <title>Distribution and Fault Tolerance</title>
+ <p>Mnesia is a distributed, fault tolerant DBMS. It is possible
+ to replicate tables on different Erlang nodes in a variety of
+ ways. The Mnesia programmer does not have to state
+ where the different tables reside, only the names of the
+ different tables are specified in the program code. This is
+ known as "location transparency" and it is an important
+ concept. In particular:
+ </p>
+ <list type="bulleted">
+ <item>A program will work regardless of the
+ location of the data. It makes no difference whether the data
+ resides on the local node, or on a remote node. <em>Note:</em> The program
+ will run slower if the data is located on a remote node.
+ </item>
+ <item>The database can be reconfigured, and tables can be
+ moved between nodes. These operations do not effect the user
+ programs.
+ </item>
+ </list>
+ <p>We have previously seen that each table has a number of
+ system attributes, such as <c>index</c> and
+ <c>type</c>.
+ </p>
+ <p>Table attributes are specified when the table is created. For
+ example, the following function will create a new table with two
+ RAM replicas:
+ </p>
+ <pre>
+ mnesia:create_table(foo,
+ [{ram_copies, [N1, N2]},
+ {attributes, record_info(fields, foo)}]).
+ </pre>
+ <p>Tables can also have the following properties,
+ where each attribute has a list of Erlang nodes as its value.
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>ram_copies</c>. The value of the node list is a list of
+ Erlang nodes, and a RAM replica of the table will reside on
+ each node in the list. This is a RAM replica, and it is
+ important to realize that no disc operations are performed when
+ a program executes write operations to these replicas. However,
+ should permanent RAM replicas be a requirement, then the
+ following alternatives are available:</p>
+ <list type="ordered">
+ <item>The <c>mnesia:dump_tables/1</c> function can be used
+ to dump RAM table replicas to disc.
+ </item>
+ <item>The table replicas can be backed up; either from
+ RAM, or from disc if dumped there with the above
+ function.
+ </item>
+ </list>
+ </item>
+ <item><c>disc_copies</c>. The value of the attribute is a list
+ of Erlang nodes, and a replica of the table will reside both
+ in RAM and on disc on each node in the list. Write operations
+ addressed to the table will address both the RAM and the disc
+ copy of the table.
+ </item>
+ <item><c>disc_only_copies</c>. The value of the attribute is a
+ list of Erlang nodes, and a replica of the table will reside
+ only as a disc copy on each node in the list. The major
+ disadvantage of this type of table replica is the access
+ speed. The major advantage is that the table does not occupy
+ space in memory.
+ </item>
+ </list>
+ <p>It is also possible to set and change table properties on
+ existing tables. Refer to Chapter 3: <seealso marker="Mnesia_chap3#def_schema">Defining the Schema</seealso> for full
+ details.
+ </p>
+ <p>There are basically two reasons for using more than one table
+ replica: fault tolerance, or speed. It is worthwhile to note
+ that table replication provides a solution to both of these
+ system requirements.
+ </p>
+ <p>If we have two active table replicas, all information is
+ still available if one of the replicas fail. This can be a very
+ important property in many applications. Furthermore, if a table
+ replica exists at two specific nodes, applications which execute
+ at either of these nodes can read data from the table without
+ accessing the network. Network operations are considerably
+ slower and consume more resources than local operations.
+ </p>
+ <p>It can be advantageous to create table replicas for a
+ distributed application which reads data often, but writes data
+ seldom, in order to achieve fast read operations on the local
+ node. The major disadvantage with replication is the increased
+ time to write data. If a table has two replicas, every write
+ operation must access both table replicas. Since one of these
+ write operations must be a network operation, it is considerably
+ more expensive to perform a write operation to a replicated
+ table than to a non-replicated table.
+ </p>
+ </section>
+
+ <section>
+ <title>Table Fragmentation</title>
+
+ <section>
+ <title>The Concept</title>
+ <p>A concept of table fragmentation has been introduced in
+ order to cope with very large tables. The idea is to split a
+ table into several more manageable fragments. Each fragment
+ is implemented as a first class Mnesia table and may be
+ replicated, have indices etc. as any other table. But the
+ tables may neither have <c>local_content</c> nor have the
+ <c>snmp</c> connection activated.
+ </p>
+ <p>In order to be able to access a record in a fragmented
+ table, Mnesia must determine to which fragment the
+ actual record belongs. This is done by the
+ <c>mnesia_frag</c> module, which implements the
+ <c>mnesia_access</c> callback behaviour. Please, read the
+ documentation about <c>mnesia:activity/4</c> to see how
+ <c>mnesia_frag</c> can be used as a <c>mnesia_access</c>
+ callback module.
+ </p>
+ <p>At each record access <c>mnesia_frag</c> first computes
+ a hash value from the record key. Secondly the name of the
+ table fragment is determined from the hash value. And
+ finally the actual table access is performed by the same
+ functions as for non-fragmented tables. When the key is
+ not known beforehand, all fragments are searched for
+ matching records. Note: In <c>ordered_set</c> tables
+ the records will be ordered per fragment, and the
+ the order is undefined in results returned by select and
+ match_object.
+ </p>
+ <p>The following piece of code illustrates
+ how an existing Mnesia table is converted to be a
+ fragmented table and how more fragments are added later on.
+ </p>
+ <code type="none"><![CDATA[
+Eshell V4.7.3.3 (abort with ^G)
+(a@sam)1> mnesia:start().
+ok
+(a@sam)2> mnesia:system_info(running_db_nodes).
+[b@sam,c@sam,a@sam]
+(a@sam)3> Tab = dictionary.
+dictionary
+(a@sam)4> mnesia:create_table(Tab, [{ram_copies, [a@sam, b@sam]}]).
+{atomic,ok}
+(a@sam)5> Write = fun(Keys) -> [mnesia:write({Tab,K,-K}) || K <- Keys], ok end.
+#Fun<erl_eval>
+(a@sam)6> mnesia:activity(sync_dirty, Write, [lists:seq(1, 256)], mnesia_frag).
+ok
+(a@sam)7> mnesia:change_table_frag(Tab, {activate, []}).
+{atomic,ok}
+(a@sam)8> mnesia:table_info(Tab, frag_properties).
+[{base_table,dictionary},
+ {foreign_key,undefined},
+ {n_doubles,0},
+ {n_fragments,1},
+ {next_n_to_split,1},
+ {node_pool,[a@sam,b@sam,c@sam]}]
+(a@sam)9> Info = fun(Item) -> mnesia:table_info(Tab, Item) end.
+#Fun<erl_eval>
+(a@sam)10> Dist = mnesia:activity(sync_dirty, Info, [frag_dist], mnesia_frag).
+[{c@sam,0},{a@sam,1},{b@sam,1}]
+(a@sam)11> mnesia:change_table_frag(Tab, {add_frag, Dist}).
+{atomic,ok}
+(a@sam)12> Dist2 = mnesia:activity(sync_dirty, Info, [frag_dist], mnesia_frag).
+[{b@sam,1},{c@sam,1},{a@sam,2}]
+(a@sam)13> mnesia:change_table_frag(Tab, {add_frag, Dist2}).
+{atomic,ok}
+(a@sam)14> Dist3 = mnesia:activity(sync_dirty, Info, [frag_dist], mnesia_frag).
+[{a@sam,2},{b@sam,2},{c@sam,2}]
+(a@sam)15> mnesia:change_table_frag(Tab, {add_frag, Dist3}).
+{atomic,ok}
+(a@sam)16> Read = fun(Key) -> mnesia:read({Tab, Key}) end.
+#Fun<erl_eval>
+(a@sam)17> mnesia:activity(transaction, Read, [12], mnesia_frag).
+[{dictionary,12,-12}]
+(a@sam)18> mnesia:activity(sync_dirty, Info, [frag_size], mnesia_frag).
+[{dictionary,64},
+ {dictionary_frag2,64},
+ {dictionary_frag3,64},
+ {dictionary_frag4,64}]
+(a@sam)19>
+ ]]></code>
+ </section>
+
+ <section>
+ <title>Fragmentation Properties</title>
+ <p>There is a table property called
+ <c>frag_properties</c> and may be read with
+ <c>mnesia:table_info(Tab, frag_properties)</c>. The
+ fragmentation properties is a list of tagged tuples with
+ the arity 2. By default the list is empty, but when it is
+ non-empty it triggers Mnesia to regard the table as
+ fragmented. The fragmentation properties are:
+ </p>
+ <taglist>
+ <tag><c>{n_fragments, Int}</c></tag>
+ <item>
+ <p><c>n_fragments</c> regulates how many fragments
+ that the table currently has. This property may explicitly
+ be set at table creation and later be changed with
+ <c>{add_frag, NodesOrDist}</c> or
+ <c>del_frag</c>. <c>n_fragment</c>s defaults to <c>1</c>.
+ </p>
+ </item>
+ <tag><c>{node_pool, List}</c></tag>
+ <item>
+ <p>The node pool contains a list of nodes and may
+ explicitly be set at table creation and later be changed
+ with <c>{add_node, Node}</c> or <c>{del_node, Node}</c>. At table creation Mnesia tries to distribute
+ the replicas of each fragment evenly over all the nodes in
+ the node pool. Hopefully all nodes will end up with the
+ same number of replicas. <c>node_pool</c> defaults to the
+ return value from <c>mnesia:system_info(db_nodes)</c>.
+ </p>
+ </item>
+ <tag><c>{n_ram_copies, Int}</c></tag>
+ <item>
+ <p>Regulates how many <c>ram_copies</c> replicas
+ that each fragment should have. This property may
+ explicitly be set at table creation. The default is
+ <c>0</c>, but if <c>n_disc_copies</c> and
+ <c>n_disc_only_copies</c> also are <c>0</c>,
+ <c>n_ram_copies</c>\011will default be set to <c>1</c>.
+ </p>
+ </item>
+ <tag><c>{n_disc_copies, Int}</c></tag>
+ <item>
+ <p>Regulates how many <c>disc_copies</c> replicas
+ that each fragment should have. This property may
+ explicitly be set at table creation. The default is <c>0</c>.
+ </p>
+ </item>
+ <tag><c>{n_disc_only_copies, Int}</c></tag>
+ <item>
+ <p>Regulates how many <c>disc_only_copies</c> replicas
+ that each fragment should have. This property may
+ explicitly be set at table creation. The default is <c>0</c>.
+ </p>
+ </item>
+ <tag><c>{foreign_key, ForeignKey}</c></tag>
+ <item>
+ <p><c>ForeignKey</c> may either be the atom
+ <c>undefined</c> or the tuple <c>{ForeignTab, Attr}</c>,
+ where <c>Attr</c> denotes an attribute which should be
+ interpreted as a key in another fragmented table named
+ <c>ForeignTab</c>. Mnesia will ensure that the number of
+ fragments in this table and in the foreign table are
+ always the same. When fragments are added or deleted
+ Mnesia will automatically propagate the operation to all
+ fragmented tables that has a foreign key referring to this
+ table. Instead of using the record key to determine which
+ fragment to access, the value of the <c>Attr</c> field is
+ used. This feature makes it possible to automatically
+ co-locate records in different tables to the same
+ node. <c>foreign_key</c> defaults to
+ <c>undefined</c>. However if the foreign key is set to
+ something else it will cause the default values of the
+ other fragmentation properties to be the same values as
+ the actual fragmentation properties of the foreign table.
+ </p>
+ </item>
+ <tag><c>{hash_module, Atom}</c></tag>
+ <item>
+ <p>Enables definition of an alternate hashing scheme.
+ The module must implement the <c>mnesia_frag_hash</c>
+ callback behaviour (see the reference manual). This
+ property may explicitly be set at table creation.
+ The default is <c>mnesia_frag_hash</c>.</p>
+ <p>Older tables that was created before the concept of
+ user defined hash modules was introduced, uses
+ the <c>mnesia_frag_old_hash</c> module in order to
+ be backwards compatible. The <c>mnesia_frag_old_hash</c>
+ is still using the poor deprecated <c>erlang:hash/1</c>
+ function.
+ </p>
+ </item>
+ <tag><c>{hash_state, Term}</c></tag>
+ <item>
+ <p>Enables a table specific parameterization
+ of a generic hash module. This property may explicitly
+ be set at table creation.
+ The default is <c>undefined</c>.</p>
+ <code type="none"><![CDATA[
+Eshell V4.7.3.3 (abort with ^G)
+(a@sam)1> mnesia:start().
+ok
+(a@sam)2> PrimProps = [{n_fragments, 7}, {node_pool, [node()]}].
+[{n_fragments,7},{node_pool,[a@sam]}]
+(a@sam)3> mnesia:create_table(prim_dict,
+ [{frag_properties, PrimProps},
+ {attributes,[prim_key,prim_val]}]).
+{atomic,ok}
+(a@sam)4> SecProps = [{foreign_key, {prim_dict, sec_val}}].
+[{foreign_key,{prim_dict,sec_val}}]
+(a@sam)5> mnesia:create_table(sec_dict,
+\011 [{frag_properties, SecProps},
+(a@sam)5> {attributes, [sec_key, sec_val]}]).
+{atomic,ok}
+(a@sam)6> Write = fun(Rec) -> mnesia:write(Rec) end.
+#Fun<erl_eval>
+(a@sam)7> PrimKey = 11.
+11
+(a@sam)8> SecKey = 42.
+42
+(a@sam)9> mnesia:activity(sync_dirty, Write,
+\011\011 [{prim_dict, PrimKey, -11}], mnesia_frag).
+ok
+(a@sam)10> mnesia:activity(sync_dirty, Write,
+\011\011 [{sec_dict, SecKey, PrimKey}], mnesia_frag).
+ok
+(a@sam)11> mnesia:change_table_frag(prim_dict, {add_frag, [node()]}).
+{atomic,ok}
+(a@sam)12> SecRead = fun(PrimKey, SecKey) ->
+\011\011 mnesia:read({sec_dict, PrimKey}, SecKey, read) end.
+#Fun<erl_eval>
+(a@sam)13> mnesia:activity(transaction, SecRead,
+\011\011 [PrimKey, SecKey], mnesia_frag).
+[{sec_dict,42,11}]
+(a@sam)14> Info = fun(Tab, Item) -> mnesia:table_info(Tab, Item) end.
+#Fun<erl_eval>
+(a@sam)15> mnesia:activity(sync_dirty, Info,
+\011\011 [prim_dict, frag_size], mnesia_frag).
+[{prim_dict,0},
+ {prim_dict_frag2,0},
+ {prim_dict_frag3,0},
+ {prim_dict_frag4,1},
+ {prim_dict_frag5,0},
+ {prim_dict_frag6,0},
+ {prim_dict_frag7,0},
+ {prim_dict_frag8,0}]
+(a@sam)16> mnesia:activity(sync_dirty, Info,
+\011\011 [sec_dict, frag_size], mnesia_frag).
+[{sec_dict,0},
+ {sec_dict_frag2,0},
+ {sec_dict_frag3,0},
+ {sec_dict_frag4,1},
+ {sec_dict_frag5,0},
+ {sec_dict_frag6,0},
+ {sec_dict_frag7,0},
+ {sec_dict_frag8,0}]
+(a@sam)17>
+ ]]></code>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Management of Fragmented Tables</title>
+ <p>The function <c>mnesia:change_table_frag(Tab, Change)</c>
+ is intended to be used for reconfiguration of fragmented
+ tables. The <c>Change</c> argument should have one of the
+ following values:
+ </p>
+ <taglist>
+ <tag><c>{activate, FragProps}</c></tag>
+ <item>
+ <p>Activates the fragmentation properties of an
+ existing table. <c>FragProps</c> should either contain
+ <c>{node_pool, Nodes}</c> or be empty.
+ </p>
+ </item>
+ <tag><c>deactivate</c></tag>
+ <item>
+ <p>Deactivates the fragmentation properties of a
+ table. The number of fragments must be <c>1</c>. No other
+ tables may refer to this table in its foreign key.
+ </p>
+ </item>
+ <tag><c>{add_frag, NodesOrDist}</c></tag>
+ <item>
+ <p>Adds one new fragment to a fragmented table. All
+ records in one of the old fragments will be rehashed and
+ about half of them will be moved to the new (last)
+ fragment. All other fragmented tables, which refers to this
+ table in their foreign key, will automatically get a new
+ fragment, and their records will also be dynamically
+ rehashed in the same manner as for the main table.
+ </p>
+ <p>The <c>NodesOrDist</c> argument may either be a list
+ of nodes or the result from <c>mnesia:table_info(Tab, frag_dist)</c>. The <c>NodesOrDist</c> argument is
+ assumed to be a sorted list with the best nodes to
+ host new replicas first in the list. The new fragment
+ will get the same number of replicas as the first
+ fragment (see <c>n_ram_copies</c>, <c>n_disc_copies</c>
+ and <c>n_disc_only_copies</c>). The <c>NodesOrDist</c>
+ list must at least contain one element for each
+ replica that needs to be allocated.
+ </p>
+ </item>
+ <tag><c>del_frag</c></tag>
+ <item>
+ <p>Deletes one fragment from a fragmented table. All
+ records in the last fragment will be moved to one of the other
+ fragments. All other fragmented tables which refers to
+ this table in their foreign key, will automatically lose
+ their last fragment and their records will also be
+ dynamically rehashed in the same manner as for the main
+ table.
+ </p>
+ </item>
+ <tag><c>{add_node, Node}</c></tag>
+ <item>
+ <p>Adds a new node to the <c>node_pool</c>. The new
+ node pool will affect the list returned from
+ <c>mnesia:table_info(Tab, frag_dist)</c>.
+ </p>
+ </item>
+ <tag><c>{del_node, Node}</c></tag>
+ <item>
+ <p>Deletes a new node from the <c>node_pool</c>. The
+ new node pool will affect the list returned from
+ <c>mnesia:table_info(Tab, frag_dist)</c>.</p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Extensions of Existing Functions</title>
+ <p>The function <c>mnesia:create_table/2</c> is used to
+ create a brand new fragmented table, by setting the table
+ property <c>frag_properties</c> to some proper values.
+ </p>
+ <p>The function <c>mnesia:delete_table/1</c> is used to
+ delete a fragmented table including all its
+ fragments. There must however not exist any other
+ fragmented tables which refers to this table in their foreign key.
+ </p>
+ <p>The function <c>mnesia:table_info/2</c> now understands
+ the <c>frag_properties</c> item.
+ </p>
+ <p>If the function <c>mnesia:table_info/2</c> is invoked in
+ the activity context of the <c>mnesia_frag</c> module,
+ information of several new items may be obtained:
+ </p>
+ <taglist>
+ <tag><c>base_table</c></tag>
+ <item>
+ <p>the name of the fragmented table
+ </p>
+ </item>
+ <tag><c>n_fragments</c></tag>
+ <item>
+ <p>the actual number of fragments
+ </p>
+ </item>
+ <tag><c>node_pool</c></tag>
+ <item>
+ <p>the pool of nodes
+ </p>
+ </item>
+ <tag><c>n_ram_copies</c></tag>
+ <item></item>
+ <tag><c>n_disc_copies</c></tag>
+ <item></item>
+ <tag><c>n_disc_only_copies</c></tag>
+ <item>
+ <p>the number of replicas with storage type
+ <c>ram_copies</c>, <c>disc_copies</c> and <c>disc_only_copies</c>
+ respectively. The actual values are dynamically derived
+ from the first fragment. The first fragment serves as a
+ pro-type and when the actual values needs to be computed
+ (e.g. when adding new fragments) they are simply
+ determined by counting the number of each replicas for
+ each storage type. This means, when the functions
+ <c>mnesia:add_table_copy/3</c>,
+ <c>mnesia:del_table_copy/2</c> and<c>mnesia:change_table_copy_type/2</c> are applied on the
+ first fragment, it will affect the settings on
+ <c>n_ram_copies</c>, <c>n_disc_copies</c>, and
+ <c>n_disc_only_copies</c>.
+ </p>
+ </item>
+ <tag><c>foreign_key</c></tag>
+ <item>
+ <p>the foreign key.
+ </p>
+ </item>
+ <tag><c>foreigners</c></tag>
+ <item>
+ <p>all other tables that refers to this table in
+ their foreign key.
+ </p>
+ </item>
+ <tag><c>frag_names</c></tag>
+ <item>
+ <p>the names of all fragments.
+ </p>
+ </item>
+ <tag><c>frag_dist</c></tag>
+ <item>
+ <p>a sorted list of <c>{Node, Count}</c> tuples
+ which is sorted in increasing <c>Count</c> order. The
+ <c>Count</c> is the total number of replicas that this
+ fragmented table hosts on each <c>Node</c>. The list
+ always contains at least all nodes in the
+ <c>node_pool</c>. The nodes which not belongs to the
+ <c>node_pool</c> will be put last in the list even if
+ their <c>Count</c> is lower.
+ </p>
+ </item>
+ <tag><c>frag_size</c></tag>
+ <item>
+ <p>a list of <c>{Name, Size}</c> tuples where
+ <c>Name</c> is a fragment <c>Name</c> and <c>Size</c> is
+ how many records it contains.
+ </p>
+ </item>
+ <tag><c>frag_memory</c></tag>
+ <item>
+ <p>a list of <c>{Name, Memory}</c> tuples where
+ <c>Name</c> is a fragment <c>Name</c> and <c>Memory</c> is
+ how much memory it occupies.
+ </p>
+ </item>
+ <tag><c>size</c></tag>
+ <item>
+ <p>total size of all fragments
+ </p>
+ </item>
+ <tag><c>memory</c></tag>
+ <item>
+ <p>the total memory of all fragments</p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Load Balancing</title>
+ <p>There are several algorithms for distributing records
+ in a fragmented table evenly over a
+ pool of nodes. No one is best, it simply depends of the
+ application needs. Here follows some examples of
+ situations which may need some attention:
+ </p>
+ <p><c>permanent change of nodes</c> when a new permanent
+ <c>db_node</c> is introduced or dropped, it may be time to
+ change the pool of nodes and re-distribute the replicas
+ evenly over the new pool of nodes. It may also be time to
+ add or delete a fragment before the replicas are re-distributed.
+ </p>
+ <p><c>size/memory threshold</c> when the total size or
+ total memory of a fragmented table (or a single
+ fragment) exceeds some application specific threshold, it
+ may be time to dynamically add a new fragment in order
+ obtain a better distribution of records.
+ </p>
+ <p><c>temporary node down</c> when a node temporarily goes
+ down it may be time to compensate some fragments with new
+ replicas in order to keep the desired level of
+ redundancy. When the node comes up again it may be time to
+ remove the superfluous replica.
+ </p>
+ <p><c>overload threshold</c> when the load on some node is
+ exceeds some application specific threshold, it may be time to
+ either add or move some fragment replicas to nodes with lesser
+ load. Extra care should be taken if the table has a foreign
+ key relation to some other table. In order to avoid severe
+ performance penalties, the same re-distribution must be
+ performed for all of the related tables.
+ </p>
+ <p>Use <c>mnesia:change_table_frag/2</c> to add new fragments
+ and apply the usual schema manipulation functions (such as
+ <c>mnesia:add_table_copy/3</c>, <c>mnesia:del_table_copy/2</c>
+ and <c>mnesia:change_table_copy_type/2</c>) on each fragment
+ to perform the actual re-distribution.
+ </p>
+ </section>
+ </section>
+
+ <section>
+ <title>Local Content Tables</title>
+ <p>Replicated tables have the same content on all nodes where
+ they are replicated. However, it is sometimes advantageous to
+ have tables but different content on different nodes.
+ </p>
+ <p>If we specify the attribute <c>{local_content, true}</c> when
+ we create the table, the table will reside on the nodes where
+ we specify that the table shall exist, but the write operations on the
+ table will only be performed on the local copy.
+ </p>
+ <p>Furthermore, when the table is initialized at start-up, the
+ table will only be initialized locally, and the table
+ content will not be copied from another node.
+ </p>
+ </section>
+
+ <section>
+ <title>Disc-less Nodes</title>
+ <p>It is possible to run Mnesia on nodes that do not have a
+ disc. It is of course not possible to have replicas
+ of neither <c>disc_copies</c>, nor <c>disc_only_copies</c>
+ on such nodes. This especially troublesome for the
+ <c>schema</c> table since Mnesia need the schema in order
+ to initialize itself.
+ </p>
+ <p>The schema table may, as other tables, reside on one or
+ more nodes. The storage type of the schema table may either
+ be <c>disc_copies</c> or <c>ram_copies</c>
+ (not <c>disc_only_copies</c>). At
+ start-up Mnesia uses its schema to determine with which
+ nodes it should try to establish contact. If any
+ of the other nodes are already started, the starting node
+ merges its table definitions with the table definitions
+ brought from the other nodes. This also applies to the
+ definition of the schema table itself. The application
+ parameter <c>extra_db_nodes</c> contains a list of nodes which
+ Mnesia also should establish contact with besides the ones
+ found in the schema. The default value is the empty list
+ <c>[]</c>.
+ </p>
+ <p>Hence, when a disc-less node needs to find the schema
+ definitions from a remote node on the network, we need to supply
+ this information through the application parameter <c>-mnesia extra_db_nodes NodeList</c>. Without this
+ configuration parameter set, Mnesia will start as a single node
+ system. It is also possible to use <c>mnesia:change_config/2</c>
+ to assign a value to 'extra_db_nodes' and force a connection
+ after mnesia have been started, i.e.
+ mnesia:change_config(extra_db_nodes, NodeList).
+ </p>
+ <p>The application parameter schema_location controls where
+ Mnesia will search for its schema. The parameter may be one of
+ the following atoms:
+ </p>
+ <taglist>
+ <tag><c>disc</c></tag>
+ <item>
+ <p>Mandatory disc. The schema is assumed to be located
+ on the Mnesia directory. And if the schema cannot be found,
+ Mnesia refuses to start.
+ </p>
+ </item>
+ <tag><c>ram</c></tag>
+ <item>
+ <p>Mandatory ram. The schema resides in ram
+ only. At start-up a tiny new schema is generated. This
+ default schema contains just the definition of the schema
+ table and only resides on the local node. Since no other
+ nodes are found in the default schema, the configuration
+ parameter <c>extra_db_nodes</c> must be used in order to let the
+ node share its table definitions with other nodes. (The
+ <c>extra_db_nodes</c> parameter may also be used on disc-full nodes.)
+ </p>
+ </item>
+ <tag><c>opt_disc</c></tag>
+ <item>
+ <p>Optional disc. The schema may reside on either disc
+ or ram. If the schema is found on disc, Mnesia starts as a
+ disc-full node (the storage type of the schema table is
+ disc_copies). If no schema is found on disc, Mnesia starts
+ as a disc-less node (the storage type of the schema table is
+ ram_copies). The default value for the application parameter
+ is
+ <c>opt_disc</c>. </p>
+ </item>
+ </taglist>
+ <p>When the <c>schema_location</c> is set to opt_disc the
+ function <c>mnesia:change_table_copy_type/3</c> may be used to
+ change the storage type of the schema.
+ This is illustrated below:
+ </p>
+ <pre>
+ 1> mnesia:start().
+ ok
+ 2> mnesia:change_table_copy_type(schema, node(), disc_copies).
+ {atomic, ok}
+ </pre>
+ <p>Assuming that the call to <c>mnesia:start</c> did not
+ find any schema to read on the disc, then Mnesia has started
+ as a disc-less node, and then changed it to a node that
+ utilizes the disc to locally store the schema.
+ </p>
+ </section>
+
+ <section>
+ <title>More Schema Management</title>
+ <p>It is possible to add and remove nodes from a Mnesia system.
+ This can be done by adding a copy of the schema to those nodes.
+ </p>
+ <p>The functions <c>mnesia:add_table_copy/3</c> and
+ <c>mnesia:del_table_copy/2</c> may be used to add and delete
+ replicas of the schema table. Adding a node to the list
+ of nodes where the schema is replicated will affect two
+ things. First it allows other tables to be replicated to
+ this node. Secondly it will cause Mnesia to try to contact
+ the node at start-up of disc-full nodes.
+ </p>
+ <p>The function call <c>mnesia:del_table_copy(schema, mynode@host)</c> deletes the node 'mynode@host' from the
+ Mnesia system. The call fails if mnesia is running on
+ 'mynode@host'. The other mnesia nodes will never try to connect
+ to that node again. Note, if there is a disc
+ resident schema on the node 'mynode@host', the entire mnesia
+ directory should be deleted. This can be done with
+ <c>mnesia:delete_schema/1</c>. If
+ mnesia is started again on the the node 'mynode@host' and the
+ directory has not been cleared, mnesia's behaviour is undefined.
+ </p>
+ <p>If the storage type of the schema is ram_copies, i.e, we
+ have disc-less node, Mnesia
+ will not use the disc on that particular node. The disc
+ usage is enabled by changing the storage type of the table
+ <c>schema</c> to disc_copies.
+ </p>
+ <p>New schemas are
+ created explicitly with <c>mnesia:create_schema/1</c> or implicitly
+ by starting Mnesia without a disc resident schema. Whenever
+ a table (including the schema table) is created it is
+ assigned its own unique cookie. The schema table is not created with
+ <c>mnesia:create_table/2</c> as normal tables.
+ </p>
+ <p>At start-up Mnesia connects different nodes to each other,
+ then they exchange table definitions with each other and the
+ table definitions are merged. During the merge procedure Mnesia
+ performs a sanity test to ensure that the table definitions are
+ compatible with each other. If a table exists on several nodes
+ the cookie must be the same, otherwise Mnesia will shutdown one
+ of the nodes. This unfortunate situation will occur if a table
+ has been created on two nodes independently of each other while
+ they were disconnected. To solve the problem, one of the tables
+ must be deleted (as the cookies differ we regard it to be two
+ different tables even if they happen to have the same name).
+ </p>
+ <p>Merging different versions of the schema table, does not
+ always require the cookies to be the same. If the storage
+ type of the schema table is disc_copies, the cookie is
+ immutable, and all other db_nodes must have the same
+ cookie. When the schema is stored as type ram_copies,
+ its cookie can be replaced with a cookie from another node
+ (ram_copies or disc_copies). The cookie replacement (during
+ merge of the schema table definition) is performed each time
+ a RAM node connects to another node.
+ </p>
+ <p><c>mnesia:system_info(schema_location)</c> and
+ <c>mnesia:system_info(extra_db_nodes)</c> may be used to determine
+ the actual values of schema_location and extra_db_nodes
+ respectively. <c>mnesia:system_info(use_dir)</c> may be used to
+ determine whether Mnesia is actually using the Mnesia
+ directory. <c>use_dir</c> may be determined even before
+ Mnesia is started. The function <c>mnesia:info/0</c> may now be
+ used to printout some system information even before Mnesia
+ is started. When Mnesia is started the function prints out
+ more information.
+ </p>
+ <p>Transactions which update the definition of a table,
+ requires that Mnesia is started on all nodes where the
+ storage type of the schema is disc_copies. All replicas of
+ the table on these nodes must also be loaded. There are a
+ few exceptions to these availability rules. Tables may be
+ created and new replicas may be added without starting all
+ of the disc-full nodes. New replicas may be added before all
+ other replicas of the table have been loaded, it will suffice
+ when one other replica is active.
+ </p>
+ </section>
+
+ <section>
+ <title>Mnesia Event Handling</title>
+ <p>System events and table events are the two categories of events
+ that Mnesia will generate in various situations.
+ </p>
+ <p>It is possible for user process to subscribe on the
+ events generated by Mnesia.
+ We have the following two functions:</p>
+ <taglist>
+ <tag><c>mnesia:subscribe(Event-Category)</c></tag>
+ <item>
+ <p>Ensures that a copy of all events of type
+ <c>Event-Category</c> are sent to the calling process.
+ </p>
+ </item>
+ <tag><c>mnesia:unsubscribe(Event-Category)</c></tag>
+ <item>Removes the subscription on events of type
+ <c>Event-Category</c></item>
+ </taglist>
+ <p><c>Event-Category</c> may either be the atom <c>system</c>, or
+ one of the tuples <c>{table, Tab, simple}</c>, <c>{table, Tab, detailed}</c>. The old event-category <c>{table, Tab}</c> is the same
+ event-category as <c>{table, Tab, simple}</c>.
+ The subscribe functions activate a subscription
+ of events. The events are delivered as messages to the process
+ evaluating the <c>mnesia:subscribe/1</c> function. The syntax of
+ system events is <c>{mnesia_system_event, Event}</c> and
+ <c>{mnesia_table_event, Event}</c> for table events. What system
+ events and table events means is described below.
+ </p>
+ <p>All system events are subscribed by Mnesia's
+ gen_event handler. The default gen_event handler is
+ <c>mnesia_event</c>. But it may be changed by using the application
+ parameter <c>event_module</c>. The value of this parameter must be
+ the name of a module implementing a complete handler
+ as specified by the <c>gen_event</c> module in
+ STDLIB. <c>mnesia:system_info(subscribers)</c> and
+ <c>mnesia:table_info(Tab, subscribers)</c> may be used to determine
+ which processes are subscribed to various
+ events.
+ </p>
+
+ <section>
+ <title>System Events</title>
+ <p>The system events are detailed below:</p>
+ <taglist>
+ <tag><c>{mnesia_up, Node}</c></tag>
+ <item>
+ <p>Mnesia has been started on a node.
+ Node is the name of the node. By default this event is ignored.
+ </p>
+ </item>
+ <tag><c>{mnesia_down, Node}</c></tag>
+ <item>
+ <p>Mnesia has been stopped on a node.
+ Node is the name of the node. By default this event is
+ ignored.
+ </p>
+ </item>
+ <tag><c>{mnesia_checkpoint_activated, Checkpoint}</c></tag>
+ <item>
+ <p>a checkpoint with the name
+ <c>Checkpoint</c> has been activated and that the current node is
+ involved in the checkpoint. Checkpoints may be activated
+ explicitly with <c>mnesia:activate_checkpoint/1</c> or implicitly
+ at backup, adding table replicas, internal transfer of data
+ between nodes etc. By default this event is ignored.
+ </p>
+ </item>
+ <tag><c>{mnesia_checkpoint_deactivated, Checkpoint}</c></tag>
+ <item>
+ <p>A checkpoint with the name
+ <c>Checkpoint</c> has been deactivated and that the current node was
+ involved in the checkpoint. Checkpoints may explicitly be
+ deactivated with <c>mnesia:deactivate/1</c> or implicitly when the
+ last replica of a table (involved in the checkpoint)
+ becomes unavailable, e.g. at node down. By default this
+ event is ignored.
+ </p>
+ </item>
+ <tag><c>{mnesia_overload, Details}</c></tag>
+ <item>
+ <p>Mnesia on the current node is
+ overloaded and the subscriber should take action.
+ </p>
+ <p>A typical overload situation occurs when the
+ applications are performing more updates on disc
+ resident tables than Mnesia is able to handle. Ignoring
+ this kind of overload may lead into a situation where
+ the disc space is exhausted (regardless of the size of
+ the tables stored on disc).
+ <br></br>
+ Each update is appended to
+ the transaction log and occasionally(depending of how it
+ is configured) dumped to the tables files. The
+ table file storage is more compact than the transaction
+ log storage, especially if the same record is updated
+ over and over again. If the thresholds for dumping the
+ transaction log have been reached before the previous
+ dump was finished an overload event is triggered.
+ </p>
+ <p>Another typical overload situation is when the
+ transaction manager cannot commit transactions at the
+ same pace as the applications are performing updates of
+ disc resident tables. When this happens the message
+ queue of the transaction manager will continue to grow
+ until the memory is exhausted or the load
+ decreases.
+ </p>
+ <p>The same problem may occur for dirty updates. The overload
+ is detected locally on the current node, but its cause may
+ be on another node. Application processes may cause heavy
+ loads if any table are residing on other nodes (replicated or not). By default this event
+ is reported to the error_logger.
+ </p>
+ </item>
+ <tag><c>{inconsistent_database, Context, Node}</c></tag>
+ <item>
+ <p>Mnesia regards the database as
+ potential inconsistent and gives its applications a chance
+ to recover from the inconsistency, e.g. by installing a
+ consistent backup as fallback and then restart the system
+ or pick a <c>MasterNode</c> from <c>mnesia:system_info(db_nodes)</c>)
+ and invoke <c>mnesia:set_master_node([MasterNode])</c>. By default an
+ error is reported to the error logger.
+ </p>
+ </item>
+ <tag><c>{mnesia_fatal, Format, Args, BinaryCore}</c></tag>
+ <item>
+ <p>Mnesia has encountered a fatal error
+ and will (in a short period of time) be terminated. The reason for
+ the fatal error is explained in Format and Args which may
+ be given as input to <c>io:format/2</c> or sent to the
+ error_logger. By default it will be sent to the
+ error_logger. <c>BinaryCore</c> is a binary containing a summary of
+ Mnesia's internal state at the time the when the fatal error was
+ encountered. By default the binary is written to a
+ unique file name on current directory. On RAM nodes the
+ core is ignored.
+ </p>
+ </item>
+ <tag><c>{mnesia_info, Format, Args}</c></tag>
+ <item>
+ <p>Mnesia has detected something that
+ may be of interest when debugging the system. This is explained
+ in <c>Format</c> and <c>Args</c> which may appear
+ as input to <c>io:format/2</c> or sent to the error_logger. By
+ default this event is printed with <c>io:format/2</c>.
+ </p>
+ </item>
+ <tag><c>{mnesia_error, Format, Args}</c></tag>
+ <item>
+ <p>Mnesia has encountered an error. The
+ reason for the error is explained i <c>Format</c> and <c>Args</c>
+ which may be given as input to <c>io:format/2</c> or sent to the
+ error_logger. By default this event is reported to the error_logger.
+ </p>
+ </item>
+ <tag><c>{mnesia_user, Event}</c></tag>
+ <item>
+ <p>An application has invoked the
+ function <c>mnesia:report_event(Event)</c>. <c>Event</c> may be any Erlang
+ data structure. When tracing a system of Mnesia applications
+ it is useful to be able to interleave Mnesia's own events with
+ application related events that give information about the
+ application context. Whenever the application starts with
+ a new and demanding Mnesia activity or enters a
+ new and interesting phase in its execution it may be a good idea
+ to use <c>mnesia:report_event/1</c>. </p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Table Events</title>
+ <p>Another category of events are table events, which are
+ events related to table updates. There are two types of table
+ events simple and detailed.
+ </p>
+ <p>The simple table events are tuples looking like this:
+ <c>{Oper, Record, ActivityId}</c>. Where <c>Oper</c> is the
+ operation performed. <c>Record</c> is the record involved in the
+ operation and <c>ActivityId</c> is the identity of the
+ transaction performing the operation. Note that the name of the
+ record is the table name even when the <c>record_name</c> has
+ another setting. The various table related events that may
+ occur are:
+ </p>
+ <taglist>
+ <tag><c>{write, NewRecord, ActivityId}</c></tag>
+ <item>
+ <p>a new record has been written.
+ NewRecord contains the new value of the record.
+ </p>
+ </item>
+ <tag><c>{delete_object, OldRecord, ActivityId}</c></tag>
+ <item>
+ <p>a record has possibly been deleted
+ with <c>mnesia:delete_object/1</c>. <c>OldRecord</c>
+ contains the value of the old record as stated as argument
+ by the application. Note that, other records with the same
+ key may be remaining in the table if it is a bag.
+ </p>
+ </item>
+ <tag><c>{delete, {Tab, Key}, ActivityId}</c></tag>
+ <item>
+ <p>one or more records possibly has
+ been deleted. All records with the key Key in the table
+ <c>Tab</c> have been deleted. </p>
+ </item>
+ </taglist>
+ <p>The detailed table events are tuples looking like
+ this: <c>{Oper, Table, Data, [OldRecs], ActivityId}</c>.
+ Where <c>Oper</c> is the operation
+ performed. <c>Table</c> is the table involved in the operation,
+ <c>Data</c> is the record/oid written/deleted.
+ <c>OldRecs</c> is the contents before the operation.
+ and <c>ActivityId</c> is the identity of the transaction
+ performing the operation.
+ The various table related events that may occur are:
+ </p>
+ <taglist>
+ <tag><c>{write, Table, NewRecord, [OldRecords], ActivityId}</c></tag>
+ <item>
+ <p>a new record has been written.
+ NewRecord contains the new value of the record and OldRecords
+ contains the records before the operation is performed.
+ Note that the new content is dependent on the type of the table.</p>
+ </item>
+ <tag><c>{delete, Table, What, [OldRecords], ActivityId}</c></tag>
+ <item>
+ <p>records has possibly been deleted
+ <c>What</c> is either {Table, Key} or a record {RecordName, Key, ...}
+ that was deleted.
+ Note that the new content is dependent on the type of the table.</p>
+ </item>
+ </taglist>
+ </section>
+ </section>
+
+ <section>
+ <title>Debugging Mnesia Applications</title>
+ <p>Debugging a Mnesia application can be difficult due to a number of reasons, primarily related
+ to difficulties in understanding how the transaction
+ and table load mechanisms work. An other source of
+ confusion may be the semantics of nested transactions.
+ </p>
+ <p>We may set the debug level of Mnesia by calling:
+ </p>
+ <list type="bulleted">
+ <item><c>mnesia:set_debug_level(Level)</c></item>
+ </list>
+ <p>Where the parameter <c>Level</c> is:
+ </p>
+ <taglist>
+ <tag><c>none</c></tag>
+ <item>
+ <p>no trace outputs at all. This is the default.
+ </p>
+ </item>
+ <tag><c>verbose</c></tag>
+ <item>
+ <p>activates tracing of important debug events. These
+ debug events will generate <c>{mnesia_info, Format, Args}</c>
+ system events. Processes may subscribe to these events with
+ <c>mnesia:subscribe/1</c>. The events are always sent to Mnesia's
+ event handler.
+ </p>
+ </item>
+ <tag><c>debug</c></tag>
+ <item>
+ <p>activates all events at the verbose level plus
+ traces of all debug events. These debug events will generate
+ <c>{mnesia_info, Format, Args}</c> system events. Processes may
+ subscribe to these events with <c>mnesia:subscribe/1</c>. The
+ events are always sent to Mnesia's event handler. On this
+ debug level Mnesia's event handler starts subscribing
+ updates in the schema table.
+ </p>
+ </item>
+ <tag><c>trace</c></tag>
+ <item>
+ <p>activates all events at the debug level. On this
+ debug level Mnesia's event handler starts subscribing
+ updates on all Mnesia tables. This level is only intended
+ for debugging small toy systems, since many large
+ events may be generated.</p>
+ </item>
+ <tag><c>false</c></tag>
+ <item>
+ <p>is an alias for none.</p>
+ </item>
+ <tag><c>true</c></tag>
+ <item>
+ <p>is an alias for debug.</p>
+ </item>
+ </taglist>
+ <p>The debug level of Mnesia itself, is also an application
+ parameter, thereby making it possible to start an Erlang system
+ in order to turn on Mnesia debug in the initial
+ start-up phase by using the following code:
+ </p>
+ <pre>
+ % erl -mnesia debug verbose
+ </pre>
+ </section>
+
+ <section>
+ <title>Concurrent Processes in Mnesia</title>
+ <p>Programming concurrent Erlang systems is the subject of
+ a separate book. However, it is worthwhile to draw attention to
+ the following features, which permit concurrent processes to
+ exist in a Mnesia system.
+ </p>
+ <p>A group of functions or processes can be called within a
+ transaction. A transaction may include statements that read,
+ write or delete data from the DBMS. A large number of such
+ transactions can run concurrently, and the programmer does not
+ have to explicitly synchronize the processes which manipulate
+ the data. All programs accessing the database through the
+ transaction system may be written as if they had sole access to
+ the data. This is a very desirable property since all
+ synchronization is taken care of by the transaction handler. If
+ a program reads or writes data, the system ensures that no other
+ program tries to manipulate the same data at the same time.
+ </p>
+ <p>It is possible to move tables, delete tables or reconfigure
+ the layout of a table in various ways. An important aspect of
+ the actual implementation of these functions is that it is
+ possible for user programs to continue to use a table while it
+ is being reconfigured. For example, it is possible to
+ simultaneously move a table and perform write operations to the
+ table . This is important for many applications that
+ require continuously available services. Refer to Chapter 4:
+ <seealso marker="Mnesia_chap4#trans_prop">Transactions and other access contexts</seealso> for more information.
+ </p>
+ </section>
+
+ <section>
+ <title>Prototyping</title>
+ <p>If and when we decide that we would like to start and manipulate
+ Mnesia, it is often easier to write the definitions and
+ data into an ordinary text file.
+ Initially, no tables and no data exist, or which
+ tables are required. At the initial stages of prototyping it
+ is prudent write all data into one file, process
+ that file and have the data in the file inserted into the database.
+ It is possible to initialize Mnesia with data read from a text file.
+ We have the following two functions to work with text files.
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>mnesia:load_textfile(Filename)</c> Which loads a
+ series of local table definitions and data found in the file
+ into Mnesia. This function also starts Mnesia and possibly
+ creates a new schema. The function only operates on the
+ local node.
+ </p>
+ </item>
+ <item>
+ <p><c>mnesia:dump_to_textfile(Filename)</c> Dumps
+ all local tables of a mnesia system into a text file which can
+ then be edited (by means of a normal text editor) and then
+ later reloaded.</p>
+ </item>
+ </list>
+ <p>These functions are of course much slower than the ordinary
+ store and load functions of Mnesia. However, this is mainly intended for minor experiments
+ and initial prototyping. The major advantages of these functions is that they are very easy
+ to use.
+ </p>
+ <p>The format of the text file is:
+ </p>
+ <pre>
+ {tables, [{Typename, [Options]},
+ {Typename2 ......}]}.
+
+ {Typename, Attribute1, Atrribute2 ....}.
+ {Typename, Attribute1, Atrribute2 ....}.
+ </pre>
+ <p><c>Options</c> is a list of <c>{Key,Value}</c> tuples conforming
+ to the options we could give to <c>mnesia:create_table/2</c>.
+ </p>
+ <p>For example, if we want to start playing with a small
+ database for healthy foods, we enter then following data into
+ the file <c>FRUITS</c>.
+ </p>
+ <codeinclude file="FRUITS" tag="%0" type="erl"></codeinclude>
+ <p>The following session with the Erlang shell then shows how
+ to load the fruits database.
+ </p>
+ <pre><![CDATA[
+ % erl
+ Erlang (BEAM) emulator version 4.9
+
+ Eshell V4.9 (abort with ^G)
+ 1> mnesia:load_textfile("FRUITS").
+ New table fruit
+ New table vegetable
+ {atomic,ok}
+ 2> mnesia:info().
+ ---> Processes holding locks <---
+ ---> Processes waiting for locks <---
+ ---> Pending (remote) transactions <---
+ ---> Active (local) transactions <---
+ ---> Uncertain transactions <---
+ ---> Active tables <---
+ vegetable : with 2 records occuping 299 words of mem
+ fruit : with 2 records occuping 291 words of mem
+ schema : with 3 records occuping 401 words of mem
+ ===> System info in version "1.1", debug level = none <===
+ opt_disc. Directory "/var/tmp/Mnesia.nonode@nohost" is used.
+ use fallback at restart = false
+ running db nodes = [nonode@nohost]
+ stopped db nodes = []
+ remote = []
+ ram_copies = [fruit,vegetable]
+ disc_copies = [schema]
+ disc_only_copies = []
+ [{nonode@nohost,disc_copies}] = [schema]
+ [{nonode@nohost,ram_copies}] = [fruit,vegetable]
+ 3 transactions committed, 0 aborted, 0 restarted, 2 logged to disc
+ 0 held locks, 0 in queue; 0 local transactions, 0 remote
+ 0 transactions waits for other nodes: []
+ ok
+ 3>
+ ]]></pre>
+ <p>Where we can see that the DBMS was initiated from a
+ regular text file.
+ </p>
+ </section>
+
+ <section>
+ <title>Object Based Programming with Mnesia</title>
+ <p>The Company database introduced in Chapter 2 has three tables
+ which store records (employee, dept, project), and three tables
+ which store relationships (manager, at_dep, in_proj). This is a
+ normalized data model, which has some advantages over a
+ non-normalized data model.
+ </p>
+ <p>It is more efficient to do a
+ generalized search in a normalized database. Some operations are
+ also easier to perform on a normalized data model. For example,
+ we can easily remove one project, as the following example
+ illustrates:
+ </p>
+ <codeinclude file="company.erl" tag="%13" type="erl"></codeinclude>
+ <p>In reality, data models are seldom fully normalized. A
+ realistic alternative to a normalized database model would be
+ a data model which is not even in first normal form. Mnesia
+ is very suitable for applications such as telecommunications,
+ because it is easy to organize data in a very flexible manner. A
+ Mnesia database is always organized as a set of tables. Each
+ table is filled with rows/objects/records. What sets Mnesia
+ apart is that individual fields in a record can contain any type
+ of compound data structures. An individual field in a record can
+ contain lists, tuples, functions, and even record code.
+ </p>
+ <p>Many telecommunications applications have unique requirements
+ on lookup times for certain types of records. If our Company
+ database had been a part of a telecommunications system, then it
+ could be that the lookup time of an employee <em>together</em>
+ with a list of the projects the employee is working on, should
+ be minimized. If this was the case, we might choose a
+ drastically different data model which has no direct
+ relationships. We would only have the records themselves, and
+ different records could contain either direct references to
+ other records, or they could contain other records which are not
+ part of the Mnesia schema.
+ </p>
+ <p>We could create the following record definitions:
+ </p>
+ <codeinclude file="company_o.hrl" tag="%0" type="erl"></codeinclude>
+ <p>An record which describes an employee might look like this:
+ </p>
+ <pre>
+ Me = #employee{emp_no= 104732,
+ name = klacke,
+ salary = 7,
+ sex = male,
+ phone = 99586,
+ room_no = {221, 015},
+ dept = 'B/SFR',
+ projects = [erlang, mnesia, otp],
+ manager = 114872},
+ </pre>
+ <p>This model only has three different tables, and the employee
+ records contain references to other records. We have the following
+ references in the record.
+ </p>
+ <list type="bulleted">
+ <item><c>'B/SFR'</c> refers to a <c>dept</c> record.
+ </item>
+ <item><c>[erlang, mnesia, otp]</c>. This is a list of three
+ direct references to three different <c>projects</c> records.
+ </item>
+ <item><c>114872</c>. This refers to another employee record.
+ </item>
+ </list>
+ <p>We could also use the Mnesia record identifiers (<c>{Tab, Key}</c>)
+ as references. In this case, the <c>dept</c> attribute would be
+ set to the value <c>{dept, 'B/SFR'}</c> instead of
+ <c>'B/SFR'</c>.
+ </p>
+ <p>With this data model, some operations execute considerably
+ faster than they do with the normalized data model in our
+ Company database. On the other hand, some other operations
+ become much more complicated. In particular, it becomes more
+ difficult to ensure that records do not contain dangling
+ pointers to other non-existent, or deleted, records.
+ </p>
+ <p>The following code exemplifies a search with a non-normalized
+ data model. To find all employees at department
+ <c>Dep</c> with a salary higher than <c>Salary</c>, use the following code:
+ </p>
+ <codeinclude file="company_o.erl" tag="%9" type="erl"></codeinclude>
+ <p>This code is not only easier to write and to understand, but it
+ also executes much faster.
+ </p>
+ <p>It is easy to show examples of code which executes faster if
+ we use a non-normalized data model, instead of a normalized
+ model. The main reason for this is that fewer tables are
+ required. For this reason, we can more easily combine data from
+ different tables in join operations. In the above example, the
+ <c>get_emps/2</c> function was transformed from a join operation
+ into a simple query which consists of a selection and a projection
+ on one single table.
+ </p>
+ </section>
+</chapter>
+