Rocker - pilote rocksdb pour Erlang

Présentation


Sur Internet, il y a beaucoup d'informations et de débats sur le choix de l'approche sql / nosql, ainsi que sur les avantages et les inconvénients de l'un ou l'autre stockage KV. Ce que vous lisez en ce moment n'est pas un manuel ou une agitation de rocksdb pour utiliser ce référentiel et mon pilote pour cela. Je voudrais partager le résultat intermédiaire de l'optimisation du processus de développement du NIF pour Erlang. Cet article présente un pilote utilisable pour rocksdb, développé sur quelques soirées.


Ainsi, dans l'un des projets, la tĂąche s'est posĂ©e de traiter de maniĂšre fiable un grand nombre d'Ă©vĂ©nements. Chaque Ă©vĂ©nement prend de 50 Ă  350 octets, plus de 80 millions d'Ă©vĂ©nements sont gĂ©nĂ©rĂ©s par nƓud et par jour. Je veux juste noter que les problĂšmes de tolĂ©rance aux pannes de la remise des messages aux nƓuds ne sont pas pris en compte. Une des restrictions de traitement est Ă©galement la modification atomique et cohĂ©rente du groupe d'Ă©vĂ©nements.


Ainsi, les principales exigences pour le conducteur sont:


  1. Fiabilité
  2. Performances
  3. Sécurité (au sens canonique)
  4. Fonctionnalité:
    • Toutes les fonctions kv de base
    • Familles de colonnes
    • Les transactions
    • Compression des donnĂ©es
    • Prise en charge de la configuration de stockage flexible
  5. Base de code minimale

Aperçu des solutions existantes


  • erocksdb est une solution des dĂ©veloppeurs de leofs. Les avantages incluent l'approbation dans un projet rĂ©el. Par contre - une base de code obsolĂšte et un manque de transactionnalitĂ©. Ce pilote est basĂ© sur rocksdb 4.13.
  • rockse a un certain nombre de limitations, par exemple, le manque d'options de configuration, mais surtout, toutes les clĂ©s et valeurs doivent ĂȘtre des chaĂźnes. Il est entrĂ© dans l'examen uniquement Ă  titre d'exemple d'un certain nombre de pilotes qui implĂ©mentent l'une ou l'autre fonctionnelle et en limitent une autre.
  • erlang-rocksdb est un projet complet, dont le dĂ©veloppement a commencĂ© en 2014. Comme erocksdb est utilisĂ© dans des projets rĂ©els. Il a une grande base de code en C / C ++ et de nombreuses fonctionnalitĂ©s. Ce pilote convient Ă  la pratique gĂ©nĂ©rale et Ă  l'utilisation dans la plupart des projets.

AprĂšs une analyse rapide de la situation actuelle avec les pilotes erlang pour rocksdb, il est devenu clair qu'aucun d'entre eux ne rĂ©pond pleinement aux exigences du projet. Bien qu'il soit possible d'utiliser erlang-rocksdb, quelques soirĂ©es gratuites sont apparues, et aprĂšs le dĂ©veloppement et la mise en Ɠuvre rĂ©ussis du filtre Bloom sur Rust et la curiositĂ©: est-il possible de mettre en Ɠuvre toutes les exigences du projet actuel et de mettre en Ɠuvre la plupart des fonctions dans NIF dans un court laps de temps?


Rocker


Rocker est un NIF pour Erlang, utilisant le wrapper Rust pour rocksdb. Les fonctionnalitĂ©s clĂ©s sont la sĂ©curitĂ©, les performances et une base de code minimale. Les clĂ©s et les donnĂ©es sont stockĂ©es sous forme binaire, ce qui n'impose aucune restriction sur le format de stockage. Pour le moment, le projet peut ĂȘtre utilisĂ© dans des solutions tierces.
Le code source se trouve dans le référentiel du projet .


Présentation de l'API


Ouverture de la base


Travailler avec la base de données est possible en deux modes:


  1. L'espace clĂ© total. Dans ce mode, toutes vos clĂ©s seront placĂ©es dans un seul jeu. Rocksdb vous permet de configurer de maniĂšre flexible les options de stockage pour les tĂąches en cours. Selon eux, la base de donnĂ©es peut ĂȘtre ouverte de deux maniĂšres:


    • en utilisant un ensemble standard d'options


      rocker:open_default(<<"/project/priv/db_default_path">>) -> {ok, Db}. 

      Le résultat de cette opération sera un pointeur pour travailler avec la base de données et la base de données sera bloquée pour toute autre tentative d'ouverture. La base de données sera automatiquement déverrouillée immédiatement aprÚs avoir effacé ce pointeur.


    • soit configurer les options de la tĂąche
       {ok, Db} = rocker:open(<<"/project/priv/db_path">>, #{ create_if_missing => true, set_max_open_files => 1000, set_use_fsync => false, set_bytes_per_sync => 8388608, optimize_for_point_lookup => 1024, set_table_cache_num_shard_bits => 6, set_max_write_buffer_number => 32, set_write_buffer_size => 536870912, set_target_file_size_base => 1073741824, set_min_write_buffer_number_to_merge => 4, set_level_zero_stop_writes_trigger => 2000, set_level_zero_slowdown_writes_trigger => 0, set_max_background_compactions => 4, set_max_background_flushes => 4, set_disable_auto_compactions => true, set_compaction_style => universal }). 

  2. Répartition en plusieurs espaces. Les clés sont stockées dans les familles de colonnes, et chaque famille de colonnes peut avoir différentes options. Prenons un exemple d'ouverture d'une base de données avec des options standard pour toutes les familles de colonnes
     {ok, Db} = case rocker:list_cf(BookDbPath) of {ok, CfList} -> rocker:open_cf_default(BookDbPath, CfList); _Else -> CfList = [], rocker:open_default(BookDbPath) end. 

Retrait de la base


Pour supprimer correctement la base de donnĂ©es, vous devez appeler rocker:destroy(Path). Dans ce cas, la base ne doit pas ĂȘtre utilisĂ©e.


Récupération de la base de données aprÚs un échec


En cas de dĂ©faillance du systĂšme, la base de donnĂ©es peut ĂȘtre restaurĂ©e Ă  l'aide de la mĂ©thode rocker:repair(Path) . Ce processus comprend 4 Ă©tapes:


  1. recherche de fichiers
  2. restaurer des tables en jouant Ă  WAL
  3. extraire les métadonnées
  4. fiche descripteur

Création d'une famille de colonnes


 Cf = <<"testcf1">>, rocker:create_cf_default(Db, Cf) -> ok. 

Suppression de la famille de colonnes


 Cf = <<"testcf1">>, rocker:drop_cf(Db, Cf) -> ok. 

Opérations CRUD


Saisie des données clés

 rocker:put(Db, <<"key">>, <<"value">>) -> ok. 

Obtention de données par clé

 rocker:get(Db, <<"key">>) -> {ok, <<"value">>} | notfound 

Suppression des données clés

 rocker:delete(Db, <<"key">>) -> ok. 

Saisie de données clés dans CF

 rocker:put_cf(Db, <<"testcf">>, <<"key">>, <<"value">>) -> ok. 

Récupération des données clés au sein des FC

 rocker:get_cf(Db, <<"testcf">>, <<"key">>) -> {ok, <<"value">>} | notfound 

Retrait de la clé CF

 rocker:delete_cf(Db, <<"testcf">>, <<"key">>) -> ok 

Itérateurs


Comme vous le savez, l'un des principes de base de rocksdb est le stockage ordonnĂ© des clĂ©s. Cette fonctionnalitĂ© est trĂšs utile dans les tĂąches rĂ©elles. Pour l'utiliser, nous avons besoin d'itĂ©rateurs de donnĂ©es. Rocksdb dispose de plusieurs modes de transmission de donnĂ©es (des exemples de code dĂ©taillĂ©s peuvent ĂȘtre trouvĂ©s dans les tests ):


  • Depuis le dĂ©but du tableau. Le rocker en est responsable dans l'itĂ©rateur {'start'}
  • De la fin du tableau: {'end'}
  • À partir d'une clĂ© spĂ©cifique en avant {'from', Key, forward}
  • À partir d'une clĂ© spĂ©cifique en arriĂšre {'from', Key, reverse}

Il convient de noter que ces modes fonctionnent également pour le transfert des données stockées dans les familles de colonnes.


Créer un itérateur

 rocker:iterator(Db, {'start'}) -> {ok, Iter}. 

Vérification de l'itérateur

 rocker:iterator_valid(Iter) -> {ok, true} | {ok, false}. 

Création d'un itérateur pour CF

 rocker:iterator_cf(Db, Cf, {'start'}) -> {ok, Iter}. 

Création d'un itérateur de préfixe

L'itérateur de préfixe nécessite de spécifier explicitement la longueur du préfixe lors de la création de la base de données.


 {ok, Db} = rocker:open(Path, #{ prefix_length => 3 }). 

Un exemple de création d'un itérateur en utilisant le préfixe «aaa»:


 {ok, Iter} = rocker:prefix_iterator(Db, <<"aaa">>). 

Création d'un itérateur de préfixe pour CF

Similaire à l'itérateur de préfixe précédent, il nécessite explicitement prefix_length pour la famille de colonnes


 {ok, Iter} = rocker:prefix_iterator_cf(Db, Cf, <<"aaa">>). 

Obtenez l'article suivant

La méthode retourne la clé / valeur suivante, ou ok si l'itérateur se termine.


 rocker:next(Iter) -> {ok, <<"key">>, <<"value">>} | ok 

Les transactions


Un événement assez courant est la nécessité d'enregistrer simultanément les modifications apportées à un groupe de clés. Rocker vous permet de combiner les opérations CRUD à la fois dans un ensemble commun et dans les FC.
Cet exemple illustre l'utilisation des transactions:


 {ok, 6} = rocker:tx(Db, [ {put, <<"k1">>, <<"v1">>}, {put, <<"k2">>, <<"v2">>}, {delete, <<"k0">>, <<"v0">>}, {put_cf, Cf, <<"k1">>, <<"v1">>}, {put_cf, Cf, <<"k2">>, <<"v2">>}, {delete_cf, Cf, <<"k0">>, <<"v0">>} ]). 

Performances


Vous pouvez trouver un test de performances dans la suite de tests. Il affiche environ 30k RPS pour l'écriture et 200k RPS pour la lecture sur ma machine. En conditions réelles, vous pouvez vous attendre à 15-20k RPS pour l'écriture et environ 120k RPS pour la lecture avec une taille de données moyenne d'environ 1 Ko par clé et le nombre total de clés est supérieur à 1 milliard.


Conclusion


Le dĂ©veloppement et l'application de Rocker dans notre projet nous ont permis de rĂ©duire le temps de rĂ©ponse du systĂšme, d'augmenter la fiabilitĂ© et de rĂ©duire le temps de redĂ©marrage. Ces avantages ont Ă©tĂ© obtenus avec des coĂ»ts de dĂ©veloppement et de mise en Ɠuvre minimes.


Pour ma part, j'ai conclu que pour les projets Erlang nécessitant une optimisation, l'utilisation de Rust est optimale. Erlang parvient à implémenter rapidement et efficacement 95% du code, tandis que Rust réécrit / ajoute un inhibiteur de 5% sans compromettre la fiabilité globale du systÚme.


PS Il existe une expĂ©rience positive dans le dĂ©veloppement de NIF pour l'arithmĂ©tique de prĂ©cision arbitraire Ă  Erlang, qui peut ĂȘtre Ă©crit dans un article sĂ©parĂ©. Je voudrais clarifier si le sujet du NIF sur la rouille est intĂ©ressant pour la communautĂ©?

Source: https://habr.com/ru/post/fr413353/


All Articles