Warning: strpos() [function.strpos]: Empty needle in /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php on line 53

Warning: Cannot modify header information - headers already sent by (output started at /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php:53) in /home/dealer/kasat/pkg/cahir/web/html/inc/actions.php on line 163
erlang:database:mnesia [Krystian Bacławski Wiki]
 

Mnesia

ACID

  • Atomicity
  • Consistency
  • Isolation
  • Durability

Motywacja

Chcemy, żeby wymarzona baza spełniała następujące kryteria:

  • szybkie zapytania klucz-wartość,
  • skomplikowane zapytania dostępne, ale nie w czasie rzeczywistym (tylko dla administratora),
  • wydajność poprzez rozproszenie danych,
  • tolerowanie awarii na wysokim poziomie,
  • dynamiczna zmiana konfiguracji bazy danych (topologia węzłów, format tabel),
  • przechowywanie złożonych obiektów.

Zastosowania

Mnesia można wykorzystywać, gdy chcemy mieć:

  • szybkie wyszukiwanie par (klucz,wartość) na złożonych danych,
  • podzielone poziomo i zreplikowane tabele,
  • przezroczystość dostępu do rozproszonej bazy,
  • trwałość danych,
  • zmianę konfiguracji tabel w locie (położenie, format, funkcjonalność),
  • indeksowanie po kluczach drugorzędnych,
  • wysoką tolerancję systemu na awarie,
  • ścisłe powiązanie typów w bazie danych z typami erlangowymi,
  • relacyjne zapytania (join) bez wymagań czasu rzeczywistego.

W czym Mnesia spisuje się gorzej lub źle:

  • prosta baza danych typu (klucz,wartość) typu tablica mieszająca,
  • przechowywanie dużych ciągów binarnych,
  • przechowywanie logów (zamiast tego disk_log),
  • jako bardzo duża baza danych (dziesiątki i setki gigabajtów),
  • jako archiwum danych np. przechowywanie kopi zapasowej.

Baza danych

Pierwotna schema

Przed wystartowaniem aplikacji mnesia należy utworzyć bazę danych wraz z jej pierwszą tabelą. Tabela schema jest zawsze tabelą dyskową i przechowuje informacje o pozostałych tabelach ich formacie, lokacji, replikach, indeksach etc.

mnesia:create_schema(DiscNodes) -> ok | {error,Reason}
mnesia:delete_schema(DiscNodes) -> ok | {error,Reason}

Wszystkie węzły na liście DiscNodes powinny móc zapisywać dane na dysk i być w trakcie wywołania funkcji dostępne.

Aplikacja Mnesia

Następnie należy wystartować bazę danych, czyli de facto aplikację mnesia:

mnesia:start() -> ok | {error, Reason}
mnesia:stop() -> stopped

Informacje o bazie danych i tabelach

Następnie można sobie przeglądać informacje dot. bazy danych i dostępnych tabel:

mnesia:schema() -> ok
mnesia:schema(Tab) -> ok
mnesia:info() -> ok
mnesia:system_info(InfoKey) -> Info | exit({aborted, Reason})

Startowanie Mnesia

> node().
'test@ps3.prac.ii'
 
> mnesia:create_schema([node()]).
ok
 
> mnesia:start().
ok

Informacje o systemie

> mnesia:info().
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
schema         : with 1        records occupying 446      words of mem
===> System info in version "4.4.13", debug level = none <===
opt_disc. Directory "/home/cahir/Mnesia.test@ps3.prac.ii" is used.
use fallback at restart = false
running db nodes   = ['test@ps3.prac.ii']
stopped db nodes   = []
master node tables = []
remote             = []
ram_copies         = []
disc_copies        = [schema]
disc_only_copies   = []
[{'test@ps3.prac.ii',disc_copies}] = [schema]
2 transactions committed, 0 aborted, 0 restarted, 0 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
ok

Informacje o tabelach

>  mnesia:schema().
-- Properties for schema table ---
access_mode          -> read_write
active_replicas      -> ['test@ps3.prac.ii']
arity                -> 3
attributes           -> [table,cstruct]
checkpoints          -> []
commit_work          -> []
cookie               -> {{1275,916367,458876},'test@ps3.prac.ii'}
disc_copies          -> ['test@ps3.prac.ii']
disc_only_copies     -> []
frag_properties      -> []
index                -> []
load_by_force        -> false
load_node            -> 'test@ps3.prac.ii'
load_order           -> 0
load_reason          -> initial
local_content        -> false
local_tables         -> [schema]
master_nodes         -> []
memory               -> 446
ram_copies           -> []
record_name          -> schema
record_validation    -> {schema,3,set}
setorbag             -> set
size                 -> 1
snmp                 -> []
storage_type         -> disc_copies
subscribers          -> []
tables               -> [schema]
user_properties      -> []
version              -> {{2,0},[]}
where_to_commit      -> [{'test@ps3.prac.ii',disc_copies}]
where_to_read        -> 'test@ps3.prac.ii'
where_to_write       -> ['test@ps3.prac.ii']
wild_pattern         -> {schema,'_','_'}

Tabele

Tworzenie

mnesia:create_table(TableName, TableDef) -> {atomic, ok} | {aborted, Reason}

Jako pierwszy parametr przekazujemy nazwę tabeli, jako drugi jej definicję w postaci listy krotek.

Opcje tworzenia tabeli:

  • na którym węźle jaka kopia:
    • {ram_copies, Nodes} – tylko w pamięci,
    • {disk_copies, Nodes} – w pamięci z synchronizacją na dysk,
    • {disk_only_copies, Nodes} – tylko na dysku,
  • typ tabeli:
    • {type, set} – zbiór,
    • {type, ordered_set} – zbiór uporządkowany (ale nie dla dla tabel dyskowych – ograniczenie DETS),
    • {type, bag} – multizbiór,
  • {record_name, Name} – nazwa rekordu przechowywanego w tabeli,
  • {attributes, [AttributeName]} – nazwy pól rekordu,
  • {index, [Index]} – indeksy na poszczególnych atrybutach (nazwy atrybutów lub indeksy) – na użytek QLC,
  • {local_content, true | false} – zawartość tabeli przechowywana lokalnie (każdy węzeł posiada różną zawartość),
  • {load_order, Priority} – im wyższy priorytet tym wcześniej tabela załaduje się do pamięci,
  • {access_mode, read_only | read_write} – tryb dostępu do tabeli.

Domyślnie tabela jest tworzona z parametrami:

[ {type, set}, {record_name, TableName}, {attributes, [key,val]} ]

więc minimalnie musimy podać atrybuty rekordu:

[ {attributes, record_info(fields, TableName) ]

Przykład

-record(employee, {emp_no, name, salary, sex, phone, room_no}).
 
create_employee_table() ->
  mnesia:create_table(employee, [{attributes, record_info(fields, employee)}]).

Informacje o tabeli

mnesia:table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})

Tabela pracowników po dodaniu kilku rekordów

> mnesia:table_info(employee,all).
[{access_mode,read_write},
 {active_replicas,[cahir@cirilla]},
 {arity,7},
 {attributes,[emp_no,name,salary,sex,phone,room_no]},
 {checkpoints,[]},
 {commit_work,[]},
 {cookie,{{1275,987750,8684},cahir@cirilla}},
 {cstruct,{cstruct,employee,set,
                   [cahir@cirilla],
                   [],[],0,read_write,[],[],false,employee,
                   [emp_no,name,salary,sex,phone,room_no],
                   [],[],
                   {{1275,987750,...},cahir@cirilla},
                   {{2,...},[]}}},
 {disc_copies,[]},
 {disc_only_copies,[]},
 {frag_properties,[]},
 {index,[]},
 {load_by_force,false},
 {load_node,cahir@cirilla},
 {load_order,0},
 {load_reason,{dumper,create_table}},
 {local_content,false},
 {master_nodes,[]},
 {memory,686},
 {ram_copies,[cahir@cirilla]},
 {record_name,employee},
 {record_validation,{employee,7,set}},
 {type,set},
 {size,8},
 {snmp,[]},
 {storage_type,ram_copies},
 {subscribers,[]},
 {user_properties,...},
 {...}|...]

Modyfikacje schematu

Wszystkie z poniższych operacji można wykonywać na działającej bazie danych. Niżej wymienione operacje są przezroczyste – ale tylko z punktu widzenia programisty, a nie wydajności.

Replikacja

Na wybranych węzłach możemy usuwać lub dodawać repliki tabel określonego typu:

mnesia:add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}
mnesia:del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}

Indeksowanie

W dowolnym momencie możemy dodawać lub usuwać indeksy:

mnesia:add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}
mnesia:del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}

Atrybuty

Możemy również dokonać konwersji całej tabeli.

mnesia:transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}
mnesia:transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}

Gdzie Fun jest funkcją aplikowaną do każdego rekordu tabeli i ma za zadanie skonwertować go do nowej postaci. Przy pomocy mnesia:tranform_table/4 możemy dokonać również zmiany nazwy rekordu przechowywanego w schemacie tabeli.

Zmiana uprawnień

mnesia:change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}

Zmiana typu repliki tabeli

mnesia:change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}

Przeniesienie repliki tabeli z jednego węzła na drugi

mnesia:move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}

Zmiana porządku wczytywania tabeli

mnesia:change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}

Punkty kontrolne i kopie zapasowe

Punkty kontrolne zachowują widok bazy danych w pewnym momencie jej życia.

mnesia:activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}
  • {name,Name} – nazwa punktu kontrolnego,
  • {max,MaxTabs} – z repliką,
  • {min,MinTabs} – bez repliki,
  • {allow_remote, true|false} – ogranicza punkt kontrolny do lokalnego węzła.
mnesia:backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}

Utworzony punkt kontrolny może posłużyć do wykonania kopii zapasowej bazy danych.

mnesia:deactivate_checkpoint(Name) -> ok | {error, Reason}

Po wykonaniu kopii zapasowej należy usunąć punkt kontrolny by nie zajmował zasobów.

Kopii zapasowej całej bazy danych można dokonać w jednym kroku używając funkcji:

mnesia:backup(Opaque [, BackupMod]) -> ok | {error,Reason}

Może się okazać, że odtworzenie kopii zapasowej wymaga transformacji danych (np. mamy starą wersję kopii, w której tabele mają inną arność rekordów). Można się posłużyć poniższą funkcją do przeglądania lub modyfikacji kopii zapasowej:

mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}

Ostatecznie najważniejszą operacją jest odtworzenie tabel z kopii zapasowej co robi się poniższą funkcją:

mnesia:restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}

Pozostałe operacje

Czyszczenie tabeli

mnesia:clear_table(Tab) -> {aborted, R} | {atomic, ok}

Usuwanie tabeli

mnesia:delete_table(Tab) ->  {aborted, Reason} | {atomic, ok}

Wymuszona synchronizacja z dyskiem

mnesia:dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}

Oczekiwanie na start bazy danych

mnesia:wait_for_tables(TabList,Timeout) -> ok | {timeout, BadTabList} | {error, Reason}

Można dać trochę czasu na to, żeby tabele załadowały się do pamięci operacyjnej, albo żeby się uspójniły w przypadku awarii.

Brak spójności tabel między wieloma węzłami

Zapytania modyfikujące idą do dwóch lub więcej węzłów. Normalnie wszystko jest dobrze, ale w przypadku awarii łącza między węzłami lub pod dużym obciążeniem mogą wystąpić problemy. Replika tabeli traci spójność między węzłami - jak sobie z tym poradzić. Nie ma złotego środka - trzeba wybrać, którąś z wersji i już.

mnesia:set_master_nodes(MasterNodes) -> ok | {error, Reason}
mnesia:set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}

Operacje na rekordach

Zapytania

Pobieranie uchwytu QLC do tabeli:

mnesia:table(Tab, [Option]) -> QueryHandle

Obowiązują te same opcje co przy tabelach ETS i DETS.

Wykorzystanie mnesia:table/1

qlc:q([E#employee.name || E <- mnesia:table(employee), E#employee.sex == female])

Blokady

mnesia:lock(LockItem, LockKind) -> Nodes | ok | transaction abort

Parametr LockItem opisuje obiekt, na którym będzie zakładana blokada:

  • {table, Tab} – tabela na wszystkich węzłach, na których istnieje,
  • {global, GlobalKey, Nodes} – obiekt pomocniczy na podanych węzłach.

Istnieją trzy rodzaje blokad:

  • write – zabrania innym dostępu zarówno do odczytu i zapisu – używane tylko do operacji modyfikujących,
  • read – blokuje tabelę do odczytu, inne procesy mogą czytać tabelę, ale nie mogą modyfikować,
  • sticky_write – blokada do zapisu, która nie znika po zakończeniu transakcji (uzyskiwanie blokady do zapisu jest kosztowne – optymalizacja :!:).
mnesia:read_lock_table(Tab) -> ok | transaction abort   % równoważne => mnesia:lock({table, Tab}, read).
mnesia:write_lock_table(Tab) -> ok | transaction abort  % równoważne => mnesia:lock({table, Tab}, write).

Transakcje

mnesia:transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}
mnesia:sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}

Parametr Fun jest funkcją, wewnątrz której programista zawiera wszystkie operacje, które mają się wykonać w obrębie jednej transakcji. Jeśli zostanie podany parametr Args będący listą to zostanie on zaaplikowany do Fun.

Ponieważ Mnesia wykrywa wszelkiego rodzaju blokady, czasami transakcje będą musiały być zrestartowane. Domyślna ilość restartów to infinity, możemy to ograniczyć.

Funkcja mesia:sync_transaction/3 działa identycznie jak mnesia:transaction/3 poza tym, że najpierw uspójni zawartość bazy z dyskiem.

Przykład prostej transakcji

> females() ->
    F = fun() ->
            Q = qlc:q([E#employee.name || E <- mnesia:table(employee), E#employee.sex == female]),
            qlc:e(Q)
    end,
    mnesia:transaction(F).
 
{atomic,["Carlsson Tuula","Fedoriw Anna"]}

Operacje transakcyjne

Normalnie operacje na Mnesia należy organizować w transakcje, które są niczym innym jak funkcjami. Trzeba uważać, żeby przypadkiem te funkcje nie miały efektów ubocznych!

Przerwanie transakcji

mnesia:abort(Reason) -> transaction abort

Odczyt

mnesia:all_keys(Tab) -> KeyList | transaction abort
mnesia:read(Tab, Key, LockKind) -> transaction abort | RecordList
mnesia:index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList

Dopasowanie

mnesia:index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList
mnesia:match_object(Tab, Pattern, LockKind) -> transaction abort | RecList

Jako Pattern przyjmuje głowę zapytania dopasowującego.

Selekcja

mnesia:select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]
mnesia:select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'
mnesia:select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'

Zapis

mnesia:write(Tab, Record, LockKind) -> transaction abort | ok

Trawersowanie

mnesia:foldl(Function, Acc, Table) -> NewAcc | transaction abort
mnesia:foldr(Function, Acc, Table) -> NewAcc | transaction abort
mnesia:first(Tab) -> Key | transaction abort
mnesia:last(Tab) -> Key | transaction abort
mnesia:next(Tab, Key) -> Key | transaction abort
mnesia:prev(Tab, Key) -> Key | transaction abort

Usuwanie

mnesia:delete(Tab, Key, LockKind) -> transaction abort | ok
mnesia:delete_object(Tab, Record, LockKind) -> transaction abort | ok

Operacje poza transakcjami

Istnieją szybsze operacje beztransakcyjne, ale mogą popsuć spójność danych - sami musimy się martwić o właściwości ACID. Poważną zaletą stosowania operacji beztransakcyjnych jest szybkość (o dziesiętny rząd większa niż dla transakcyjnych). Lista operacji jest następująca:

Przy odpowiednim zachowaniu można zapewnić spójność nawet dla operacji beztransakcyjnych. Jak? Wielu czytających, jeden piszący. Serializacja zapytań modyfikujących w jednym procesie.

Odczyt

mnesia:dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})
mnesia:dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}
mnesia:dirty_index_read(Tab, SecondaryKey, Pos)

Usuwanie

mnesia:dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})
mnesia:dirty_delete_object(Tab, Record)

Trawersowanie

mnesia:dirty_first(Tab) -> Key | exit({aborted, Reason})
mnesia:dirty_last(Tab) -> Key | exit({aborted, Reason})
mnesia:dirty_next(Tab, Key) -> Key | exit({aborted, Reason})
mnesia:dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})

Dopasowanie

mnesia:dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})
mnesia:dirty_index_match_object(Tab, Pattern, Pos)

Selekcja

mnesia:dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}

Aktualizacja licznika

mnesia:dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})

Zapis

mnesia:dirty_write(Tab, Record) -> ok | exit({aborted, Reason})

Źródła

 
erlang/database/mnesia.txt · Last modified: 2010/06/08 15:13 by Krystian Bacławski
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-No Derivative Works 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki