2011-12-01 3 views
2

J'ai conçu une base de données mnesia avec 5 tables différentes. L'idée est de simuler des requêtes à partir de nombreux nœuds (ordinateurs) et pas seulement un, pour le moment je peux exécuter une requête, mais j'ai juste besoin d'aide sur comment je peux faire telle que je demande des informations à plusieurs ordinateurs. Je teste l'évolutivité et je veux étudier les performances de Mnesia par rapport à d'autres bases de données. Toute idée sera très appréciée.Erlang mnesia accès à la base de données

Répondre

7

La meilleure façon de tester mnesia est à l'aide d'un travail fileté intensif à la fois sur la Erlang locale nœud où mnesia est en cours d'exécution et sur les nœuds distants. Habituellement, vous voulez avoir des nœuds distants en utilisant RPC calls dans lequel les lectures et les écritures sont en cours d'exécution sur les tables Mnesia. Bien sûr, avec une forte concurrence vient un compromis; la vitesse des transactions diminuera, beaucoup peuvent être retentés car les serrures peuvent être nombreuses à un moment donné; Mais Mnesia s'assurera que tous les processus reçoivent un {atomic,ok} pour chaque appel transactionnel qu'ils font.

Le concept
Je propose que nous avons une surcharge non-bloquant avec les deux lit et écrit dans dirigé à chaque table de mnesia par autant de processus que possible. Nous mesurons la différence de temps entre l'appel à la fonction write et le temps qu'il faut à notre abonné massivement Mnesia pour obtenir un événement Write. Ces événements sont envoyés par mnesia après une Transaction réussie et nous n'avons donc pas besoin d'interrompre les processus de travail/surcharge, mais de laisser un abonné Mnesia "fort" attendre les événements asynchrones qui rapportent des suppressions réussies et les écrit dès qu'ils surviennent.
La technique ici est que nous prenons l'horodatage au moment juste avant d'appeler une fonction d'écriture, puis nous notons le record key, le write CALL timestamp. Ensuite, notre abonné Mnesia noterait le record key, le write/read EVENT timestamp. Ensuite, la différence de temps entre ces deux horodatages (appelons-le: CALL-to-EVENT Time) nous donnerait une idée approximative de la charge ou de l'efficacité de notre travail. À mesure que les verrous augmentent avec la concurrence, nous devrions enregistrer le paramètre CALL-to-EVENT Time. Les processus faisant des écritures (illimitées) le feront simultanément tandis que ceux qui font des lectures continueront aussi à le faire sans interruption. Nous choisirons le nombre de processus pour chaque opération, mais laissons d'abord la place pour l'ensemble du cas de test.
Tout le concept ci-dessus est pour les opérations locales (processus en cours d'exécution sur le même nœud que Mnesia)

-> Simuler De nombreux nœuds
Eh bien, j'ai personnellement pas simulé Noeuds en Erlang, je l'ai toujours travaillé avec de vrais nœuds Erlang sur la même boîte ou sur plusieurs machines différentes dans un environnement en réseau.Cependant, je vous conseille de regarder attentivement ce module: http://www.erlang.org/doc/man/slave.html, concentrez-vous sur celui-ci ici: http://www.erlang.org/doc/man/ct_slave.html, et regardez les liens suivants lorsqu'ils parlent de créer, simuler et contrôler plusieurs nœuds sous un autre nœud parent (http://www.erlang.org/doc/man/pool.html, Erlang: starting slave node, https://support.process-one.net/doc/display/ERL/Starting+a+set+of+Erlang+cluster+nodes, http://www.berabera.info/oldblog/lenglet/howtos/erlangkerberosremctl/index.html). Je ne vais pas plonger dans une jungle de Noeuds Erlang ici parce que c'est aussi un autre sujet compliqué mais je vais me concentrer sur des tests sur le même nœud que Mnesia. Je suis venu avec le concept de test de Mnesia ci-dessus et ici, commence à le mettre en œuvre. Maintenant, tout d'abord, vous devez créer un plan de test pour chaque table (séparé). Cela devrait inclure à la fois les écritures et les lectures. Vous devez ensuite décider si vous souhaitez effectuer des opérations incorrectes ou des opérations transactionnelles sur les tables. Vous devez tester la vitesse de traversée d'une table mnesia par rapport à sa taille. Prenons un exemple d'une simple table de mnesia

 
-record(key_value,{key,value,instanceId,pid}). 

Nous voudrions avoir une fonction générale pour écrire dans notre tableau, ci-dessous:

 
write(Record)-> 
    %% Use mnesia:activity/4 to test several activity 
    %% contexts (and if your table is fragmented) 
    %% like the commented code below 
    %% 
    %% mnesia:activity(
    %%  transaction, %% sync_transaction | async_dirty | ets | sync_dirty 
    %%  fun(Y) -> mnesia:write(Y) end, 
    %%  [Record], 
    %%  mnesia_frag 
    %% ) 
    mnesia:transaction(fun() -> ok = mnesia:write(Record) end). 

Et pour notre lit, nous aurons :

 
read(Key)-> 
    %% Use mnesia:activity/4 to test several activity 
    %% contexts (and if your table is fragmented) 
    %% like the commented code below 
    %% 
    %% mnesia:activity(
    %%  transaction, %% sync_transaction | async_dirty| ets | sync_dirty 
    %%  fun(Y) -> mnesia:read({key_value,Y}) end, 
    %%  [Key], 
    %%  mnesia_frag 
    %% ) 
    mnesia:transaction(fun() -> mnesia:read({key_value,Key}) end). 
Maintenant, nous voulons écrire beaucoup d'enregistrements dans notre petite table. Nous avons besoin d'un générateur de clé. Ce générateur de clé sera notre propre générateur de chaîne pseudo-aléatoire. Cependant, nous avons besoin de notre générateur pour nous dire l'instant où il génère une clé afin que nous l'enregistrions. Nous voulons voir combien de temps il faut pour écrire une clé générée. Disons-le comme ceci:
 
timestamp()-> erlang:now().
str(XX)-> integer_to_list(XX).
generate_instance_id()-> random:seed(now()), guid() ++ str(crypto:rand_uniform(1, 65536 * 65536)) ++ str(erlang:phash2({self(),make_ref(),time()})).
guid()-> random:seed(now()), MD5 = erlang:md5(term_to_binary({self(),time(),node(), now(), make_ref()})), MD5List = binary_to_list(MD5), F = fun(N) -> f("~2.16.0B", [N]) end, L = lists:flatten([F(N) || N <- MD5List]), %% tell our massive mnesia subscriber about this generation InstanceId = generate_instance_id(), mnesia_subscriber ! {self(),{key,write,L,timestamp(),InstanceId}}, {L,InstanceId}.
Pour faire beaucoup d'écritures simultanées, nous avons besoin d'une fonction qui sera exécutée par de nombreux processus que nous allons engendrer. Dans cette fonction, il est souhaitable de ne pas mettre de fonctions de blocage telles que sleep/1 habituellement implémenté comme sleep(T)-> receive after T -> true end.. Une telle fonction ferait en sorte que l'exécution d'un processus se bloque pendant les millisecondes spécifiées. mnesia_tm est-ce que le verrou contrôle, réessaie, bloque, e.ct. au nom des processus pour éviter les verrous morts. Disons, nous voulons que chaque processus d'écrire un unlimited amount of records. Voici notre fonction:

 
-define(NO_OF_PROCESSES,20). 

start_write_jobs()-> 
    [spawn(?MODULE,generate_and_write,[]) || _ <- lists:seq(1,?NO_OF_PROCESSES)], 
    ok. 

generate_and_write()-> 
    %% remember that in the function ?MODULE:guid/0, 
    %% we inform our mnesia_subscriber about our generated key 
    %% together with the timestamp of the generation just before 
    %% a write is made. 
    %% The subscriber will note this down in an ETS Table and then 
    %% wait for mnesia Event about the write operation. Then it will 
    %% take the event time stamp and calculate the time difference 
    %% From there we can make judgement on performance. 
    %% In this case, we make the processes make unlimited writes 
    %% into our mnesia tables. Our subscriber will trap the events as soon as 
    %% a successful write is made in mnesia 
    %% For all keys we just write a Zero as its value
{Key,Instance} = guid(), write(#key_value{key = Key,value = 0,instanceId = Instance,pid = self()}), generate_and_write().

De même, voyons comment les tâches de lecture seront effectuées. Nous aurons un fournisseur de clés, ce fournisseur de clés continue de tourner autour de la table de Mnesia en ne sélectionnant que les touches, de haut en bas de la table, il continuera à tourner. Voici son code:

 
first()-> mnesia:dirty_first(key_value). 

next(FromKey)-> mnesia:dirty_next(key_value,FromKey). 

start_key_picker()-> register(key_picker,spawn(fun() -> key_picker() end)). 

key_picker()-> 
    try ?MODULE:first() of  
     '$end_of_table' -> 
      io:format("\n\tTable is empty, my dear !~n",[]), 
      %% lets throw something there to start with 
      ?MODULE:write(#key_value{key = guid(),value = 0}), 
      key_picker(); 
     Key -> wait_key_reqs(Key) 
    catch 
     EXIT:REASON -> 
      error_logger:error_info(["Key Picker dies",{EXIT,REASON}]), 
      exit({EXIT,REASON}) 
    end. 

wait_key_reqs('$end_of_table')-> 
receive 
    {From,<<"get_key">>} -> 
     Key = ?MODULE:first(), 
     From ! {self(),Key}, 
     wait_key_reqs(?MODULE:next(Key)); 
    {_,<<"stop">>} -> exit(normal) 
end; 
wait_key_reqs(Key)-> 
receive 
    {From,<<"get_key">>} -> 
     From ! {self(),Key}, 
     NextKey = ?MODULE:next(Key), 
     wait_key_reqs(NextKey); 
    {_,<<"stop">>} -> exit(normal) 
end. 

key_picker_rpc(Command)-> 
    try erlang:send(key_picker,{self(),Command}) of 
     _ -> 
      receive 
       {_,Reply} -> Reply 
      after timer:seconds(60) -> 
       %% key_picker hang, or too busy 
       erlang:throw({key_picker,hanged}) 
      end 
    catch 
     _:_ -> 
      %% key_picker dead 
      start_key_picker(), 
      sleep(timer:seconds(5)), 
      key_picker_rpc(Command) 
    end. 

%% Now, this is where the reader processes will be 
%% accessing keys. It will appear to them as though 
%% its random, because its one process doing the 
%% traversal. It will all be a game of chance 
%% depending on the scheduler's choice 
%% he who will have the next read chance, will 
%% win ! okay, lets get going below :) 

get_key()-> 
    Key = key_picker_rpc(<<"get_key">>), 

    %% lets report to our "massive" mnesia subscriber 
    %% about a read which is about to happen 
    %% together with a time stamp. 
    Instance = generate_instance_id(), 
    mnesia_subscriber ! {self(),{key,read,Key,timestamp(),Instance}}, 
    {Key,Instance}. 

Wow !!! Maintenant, nous devons créer la fonction où nous allons commencer tous les lecteurs.

 
-define(NO_OF_READERS,10). 

start_read_jobs()-> 
    [spawn(?MODULE,constant_reader,[]) || _ <- lists:seq(1,?NO_OF_READERS)], 
    ok. 

constant_reader()-> 
    {Key,InstanceId} = ?MODULE:get_key(), 
    Record = ?MODULE:read(Key), 
    %% Tell mnesia_subscriber that a read has been done so it creates timestamp 
    mnesia:report_event({read_success,Record,self(),InstanceId}), 
    constant_reader(). 

Maintenant, la plus grande partie; mnesia_subscriber !!! C'est un processus simple qui va s'inscrire à des événements simples. Obtenez la documentation sur les événements de Mnesia à partir du guide des utilisateurs de Mnesia. Voici l'abonné mnesia

 
-record(read_instance,{ 
     instance_id, 
     before_read_time, 
     after_read_time, 
     read_time  %% after_read_time - before_read_time 

    }). 

-record(write_instance,{ 
     instance_id, 
     before_write_time, 
     after_write_time, 
     write_time   %% after_write_time - before_write_time 
    }). 

-record(benchmark,{ 
     id,   %% {pid(),Key} 
     read_instances = [], 
     write_instances = [] 
    }). 

subscriber()-> 
    mnesia:subscribe({table,key_value, simple}), 

    %% lets also subscribe for system 
    %% events because events passing through 
    %% mnesia:event/1 will go via 
    %% system events. 

    mnesia:subscribe(system), 
    wait_events(). 

-include_lib("stdlib/include/qlc.hrl"). 

wait_events()-> 
receive 
    {From,{key,write,Key,TimeStamp,InstanceId}} -> 
     %% A process is just about to call 
     %% mnesia:write/1 and so we note this down 
     Fun = fun() -> 
       case qlc:e(qlc:q([X || X <- mnesia:table(benchmark),X#benchmark.id == {From,Key}])) of 
        [] -> 
         ok = mnesia:write(#benchmark{ 
           id = {From,Key}, 
           write_instances = [ 
             #write_instance{ 
              instance_id = InstanceId, 
              before_write_time = TimeStamp            
             }] 
           }), 
           ok; 
        [Here] -> 
         WIs = Here#benchmark.write_instances, 
         NewInstance = #write_instance{ 
             instance_id = InstanceId, 
             before_write_time = TimeStamp            
            }, 
         ok = mnesia:write(Here#benchmark{write_instances = [NewInstance|WIs]}), 
         ok       
       end 
      end, 
     mnesia:transaction(Fun), 
     wait_events();  
    {mnesia_table_event,{write,#key_value{key = Key,instanceId = I,pid = From},_ActivityId}} -> 
     %% A process has successfully made a write. So we look it up and 
     %% get timeStamp difference, and finish bench marking that write 
     WriteTimeStamp = timestamp(), 
     F = fun()-> 
       [Here] = mnesia:read({benchmark,{From,Key}}), 
       WIs = Here#benchmark.write_instances, 
       {_,WriteInstance} = lists:keysearch(I,2,WIs), 
       BeforeTmStmp = WriteInstance#write_instance.before_write_time, 
       NewWI = WriteInstance#write_instance{ 
          after_write_time = WriteTimeStamp, 
          write_time = time_diff(WriteTimeStamp,BeforeTmStmp) 
         }, 
       ok = mnesia:write(Here#benchmark{write_instances = [NewWI|lists:keydelete(I,2,WIs)]}), 
       ok 
      end, 
     mnesia:transaction(F), 
     wait_events();  
    {From,{key,read,Key,TimeStamp,InstanceId}} -> 
     %% A process is just about to do a read 
     %% using mnesia:read/1 and so we note this down 
     Fun = fun()-> 
       case qlc:e(qlc:q([X || X <- mnesia:table(benchmark),X#benchmark.id == {From,Key}])) of 
        [] -> 
         ok = mnesia:write(#benchmark{ 
           id = {From,Key}, 
           read_instances = [ 
             #read_instance{ 
              instance_id = InstanceId, 
              before_read_time = TimeStamp             
             }] 
           }), 
           ok; 
        [Here] -> 
         RIs = Here#benchmark.read_instances, 
         NewInstance = #read_instance{ 
             instance_id = InstanceId, 
             before_read_time = TimeStamp            
            }, 
         ok = mnesia:write(Here#benchmark{read_instances = [NewInstance|RIs]}), 
         ok 
       end 
      end, 
     mnesia:transaction(Fun), 
     wait_events(); 
    {mnesia_system_event,{mnesia_user,{read_success,#key_value{key = Key},From,I}}} -> 
     %% A process has successfully made a read. So we look it up and 
     %% get timeStamp difference, and finish bench marking that read 
     ReadTimeStamp = timestamp(), 
     F = fun()-> 
       [Here] = mnesia:read({benchmark,{From,Key}}), 
       RIs = Here#benchmark.read_instances, 
       {_,ReadInstance} = lists:keysearch(I,2,RIs), 
       BeforeTmStmp = ReadInstance#read_instance.before_read_time, 
       NewRI = ReadInstance#read_instance{ 
          after_read_time = ReadTimeStamp, 
          read_time = time_diff(ReadTimeStamp,BeforeTmStmp) 
         }, 
       ok = mnesia:write(Here#benchmark{read_instances = [NewRI|lists:keydelete(I,2,RIs)]}), 
       ok 
      end, 
     mnesia:transaction(F), 
     wait_events(); 
    _ -> wait_events(); 
end. 

time_diff({A2,B2,C2} = _After,{A1,B1,C1} = _Before)->   
    {A2 - A1,B2 - B1,C2 - C1}. 


D'accord! C'était énorme :) Nous avons donc fini avec l'abonné. Nous devons mettre le code qui va couronner le tout et exécuter les tests nécessaires. Maintenant, avec une analyse appropriée des enregistrements de la table de référence, vous obtiendrez un enregistrement des temps de lecture moyens, des temps d'écriture moyens e.t.c. Vous dessinez un graphique de ces temps par rapport au nombre croissant de processus. En augmentant le nombre de processus, vous découvrirez que les temps de lecture et d'écriture augmentent . Obtenez le code, lisez-le et utilisez-le. Vous ne pouvez pas utiliser tout cela, mais je suis sûr que vous pouvez ramasser nouveaux concepts à partir de là que d'autres y envoient des solutions. Utiliser les événements de Mnesia est la meilleure façon de tester les lectures et les écritures de Mnesia sans bloquer les processus d'écriture ou de lecture. Dans l'exemple ci-dessus, les processus de lecture et d'écriture sont hors de contrôle, en fait, ils s'exécuteront indéfiniment jusqu'à ce que vous terminiez la VM. Vous pouvez traverser la table de benchmark avec de bonnes formules pour utiliser les temps de lecture et d'écriture par instance de lecture ou d'écriture, puis calculer des moyennes, des variations e.t.c.




Test des ordinateurs distants, Simuler nœuds, analyse comparative par rapport aux autres SGBD peuvent ne pas être aussi pertinent simplement pour de nombreuses raisons. Les concepts, les motivations et les objectifs de Mnesia sont très différents de plusieurs types de types de bases de données existants, tels que: les DB orientés document, les SGBDR, les DB orientés objets e.t.c. Enfait, mnesia out à comparer avec une base de données tels que this one. Il s'agit d'un DBMs distribués avec des structures de données hybrides/non structurées qui appartiennent à Language Erlang. Benchmarking Mnesia contre un autre type de base de données peut ne pas être juste parce que son but est très différent de beaucoup et son couplage étroit avec Erlang/OTP. Cependant, une connaissance du fonctionnement de Mnesia, des contextes de transaction, de l'indexation, de la simultanéité et de la distribution peut être la clé d'une bonne conception de base de données. Mnesia peut stocker une structure de données très complexe. Rappelez-vous, plus une structure de données est complexe avec des informations imbriquées, plus le travail requis pour décompresser et extraire les informations dont vous avez besoin à l'exécution, ce qui signifie plus de cycles CPU et de la mémoire. Parfois, la normalisation avec Mnesia peut juste entraîner des performances médiocres et donc la mise en œuvre de ses concepts sont loin d'autres bases de données.
Il est bon que vous soyez intéressé par les performances Mnesia sur plusieurs machines (distribuées), cependant, les performances sont aussi bonnes que celles de Distributed Erlang. La grande chose est que l'atomicité est assurée pour chaque transaction. Des demandes simultanées provenant de nœuds distants peuvent être envoyées via des appels RPC. Rappelez-vous que si vous avez plusieurs répliques de Mnesia sur des machines différentes, les processus s'exécutant sur chaque nœud écriront sur ce même nœud, puis Mnesia continuera à partir de là avec sa réplication. Mnesia est très rapide à la réplication, à moins qu'un réseau ne soit vraiment mauvais et/ou que les nœuds ne soient pas connectés ou que le réseau soit partitionné à l'exécution. Mnesia assure la cohérence et l'atomicité des opérations CRUD.
Pour cette raison, les bases de données mnesia répliquées dépendent fortement de la disponibilité du réseau pour de meilleures performances. Tant que les nœuds Erlang restent connectés, les deux nœuds Mnesia ou plus auront toujours les mêmes données. La lecture sur un nœud vous permettra d'obtenir les informations les plus récentes. Des problèmes surviennent lorsqu'une déconnexion se produit et que chaque nœud enregistre l'autre comme s'il était en panne. Plus d'informations sur les performances de mnesia se trouve en suivant les liens suivants

http://igorrs.blogspot.com/2010/05/mnesia-one-year-later.html
http://igorrs.blogspot.com/2010/05/mnesia-one-year-later-part-2.html
http://igorrs.blogspot.com/2010/05/mnesia-one-year-later-part-3.html
http://igorrs.blogspot.com/2009/11/consistent-hashing-for-mnesia-fragments.html

En conséquence, les concepts sous-jacents mnesia ne peuvent être comparés à NDB d'Ericsson Base de données trouvée ici: http://ww.dolphinics.no/papers/abstract/ericsson.html, mais pas avec les SGBDR existants, ou les bases de données orientées document, etc. Ce sont mes pensées :) laisse attendre ce que les autres ont à dire .....

+1

merci beaucoup, vous m'avez donné vraiment presque toute l'information dont j'avais besoin.Je suis vraiment reconnaissant et je regarderai le code et tous les liens que vous avez fournis. Merci beaucoup – Onty

0

Vous commencez noeuds supplémentaires en utilisant la commande comme ceci:

erl -name [email protected] -cookie devel \ 
    -mnesia extra_db_nodes "['[email protected]']"\ 
    -s mnesia start 

où « [email protected] » est le nœud où mnesia est déjà configuré. Dans ce cas, toutes les tables seront accessibles à partir du nœud distant, mais vous pouvez effectuer des copies locales avec mnesia:add_table_copy/3.

Ensuite, vous pouvez utiliser spawn/2 ou spawn/4 pour lancer la génération de charge sur tous les nœuds avec quelque chose comme:

lists:foreach(fun(N) -> 
        spawn(N, fun() -> 
           %% generate some load 
           ok 
          end 
       end, 
    [ '[email protected]', '[email protected]' ] 
) 
+0

merci pour les suggestions :) je vraiment aprécie – Onty

Questions connexes