1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
|
<?xml version="1.0" encoding="latin1" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
<header>
<copyright>
<year>1997</year><year>2010</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> will 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,
[{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,
[{prim_dict, PrimKey, -11}], mnesia_frag).
ok
(a@sam)10> mnesia:activity(sync_dirty, Write,
[{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) ->
mnesia:read({sec_dict, PrimKey}, SecKey, read) end.
#Fun<erl_eval>
(a@sam)13> mnesia:activity(transaction, SecRead,
[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,
[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,
[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>, the atom <c>activity</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>,
<c>{mnesia_activity_event, Event}</c> for activity events, and
<c>{mnesia_table_event, Event}</c> for table events. What the various
event types mean 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>Activity Events</title>
<p>Currently, there is only one type of activity event:</p>
<taglist>
<tag><c>{complete, ActivityID}</c></tag>
<item>
<p>This event occurs when a transaction that caused a modification to the database
has completed. It is useful for determining when a set of table events
(see below) caused by a given activity have all been sent. Once the this event
has been received, it is guaranteed that no further table events with the same
ActivityID will be received. Note that this event may still be received even
if no table events with a corresponding ActivityID were received, depending on
the tables to which the receiving process is subscribed.</p>
<p>Dirty operations always only contain one update and thus no activity event is sent.</p>
</item>
</taglist>
</section>
<section>
<title>Table Events</title>
<p>The final 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>
|