diff options
Diffstat (limited to 'lib/mnesia/doc/src/Mnesia_chap5.xmlsrc')
-rw-r--r-- | lib/mnesia/doc/src/Mnesia_chap5.xmlsrc | 1398 |
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öm, Hans Nilsson and Hå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> + |