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:
- Fiabilité
- Performances
- Sécurité (au sens canonique)
- 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
- 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:
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 }).
- 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:
- recherche de fichiers
- restaurer des tables en jouant Ă WAL
- extraire les métadonnées
- 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Ă©?