Einführung
Im Internet gibt es viele Informationen und Debatten über die Wahl des SQL / NOSQL-Ansatzes sowie über die Vor- und Nachteile des einen oder anderen KV-Speichers. Was Sie gerade lesen, ist kein Rocksdb-Handbuch oder eine Aufregung, um dieses Repository und meinen Treiber dafür zu verwenden. Ich möchte das Zwischenergebnis der Optimierung des NIF-Entwicklungsprozesses für Erlang mitteilen. Dieser Artikel stellt einen funktionsfähigen Treiber für rocksbb vor, der an mehreren Abenden entwickelt wurde.
In einem der Projekte bestand die Aufgabe darin, eine große Anzahl von Ereignissen zuverlässig zu verarbeiten. Jedes Ereignis dauert 50 bis 350 Bytes. Pro Knoten und Tag werden mehr als 80 Millionen Ereignisse generiert. Ich möchte nur darauf hinweisen, dass die Probleme der Fehlertoleranz bei der Nachrichtenübermittlung an Knoten nicht berücksichtigt werden. Eine der Verarbeitungsbeschränkungen ist auch die atomare und konsistente Änderung der Ereignisgruppe.
Somit sind die Hauptanforderungen an den Fahrer:
- Zuverlässigkeit
- Leistung
- Sicherheit (im kanonischen Sinne)
- Funktionalität:
- Alle grundlegenden kv-Funktionen
- Spaltenfamilien
- Transaktionen
- Datenkomprimierung
- Unterstützung für die flexible Speicherkonfiguration
- Minimale Codebasis
Übersicht bestehender Lösungen
- erocksdb ist eine Lösung von Leofs-Entwicklern. Zu den Vorteilen gehört die Genehmigung in einem realen Projekt. Durch Nachteile - eine veraltete Codebasis und mangelnde Transaktionsfähigkeit. Dieser Treiber basiert auf rocksbb 4.13.
- rockse hat eine Reihe von Einschränkungen, zum Beispiel das Fehlen von Konfigurationsoptionen, aber am wichtigsten ist, dass alle Schlüssel und Werte Zeichenfolgen sein müssen. Er ging nur als Beispiel für eine Reihe von Treibern in die Überprüfung ein, die die eine oder andere Funktion implementieren und eine andere einschränken.
- erlang-rocksbb ist ein voll ausgestattetes Projekt, dessen Entwicklung 2014 begann. Wie erocksdb wird in realen Projekten verwendet. Es hat eine große Codebasis in C / C ++ und eine breite Funktionalität. Dieser Treiber ist für die allgemeine Praxis und den Einsatz in den meisten Projekten geeignet.
Nach einer kurzen Analyse der aktuellen Situation mit erlang-Treibern für rocksbb wurde klar, dass keiner von ihnen die Projektanforderungen vollständig erfüllt. Obwohl es möglich wäre, erlang-rocksbb zu verwenden, gab es einige freie Abende und nach der erfolgreichen Entwicklung und Implementierung des Bloom-Filters für Rost und Neugier: Ist es möglich, alle Anforderungen des aktuellen Projekts zu implementieren und die meisten Funktionen in NIF in kurzer Zeit zu implementieren?
Rocker
Rocker ist ein NIF für Erlang und verwendet den Rust-Wrapper für Rocksdb. Hauptmerkmale sind Sicherheit, Leistung und eine minimale Codebasis. Schlüssel und Daten werden in binärer Form gespeichert, wodurch das Speicherformat nicht eingeschränkt wird. Derzeit ist das Projekt für den Einsatz in Lösungen von Drittanbietern geeignet.
Der Quellcode befindet sich im Projekt-Repository .
API-Übersicht
Basisöffnung
Das Arbeiten mit der Datenbank ist in zwei Modi möglich:
Der gesamte Schlüsselraum. In diesem Modus werden alle Ihre Schlüssel in einem Satz platziert. Mit Rocksdb können Sie Speicheroptionen für aktuelle Aufgaben flexibel konfigurieren. Abhängig von ihnen kann die Datenbank auf zwei Arten geöffnet werden:
Verwenden eines Standardsatzes von Optionen
rocker:open_default(<<"/project/priv/db_default_path">>) -> {ok, Db}.
Das Ergebnis dieser Operation ist ein Zeiger für die Arbeit mit der Datenbank, und die Datenbank wird für alle anderen Öffnungsversuche blockiert. Die Datenbank wird sofort nach dem Löschen dieses Zeigers automatisch entsperrt.
- Konfigurieren Sie entweder Optionen für die Aufgabe
{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 }).
- Aufteilung in mehrere Räume. Schlüssel werden in den sogenannten Spaltenfamilien gespeichert, und jede Spaltenfamilie kann unterschiedliche Optionen haben. Betrachten wir ein Beispiel für das Öffnen einer Datenbank mit Standardoptionen für alle Spaltenfamilien
{ok, Db} = case rocker:list_cf(BookDbPath) of {ok, CfList} -> rocker:open_cf_default(BookDbPath, CfList); _Else -> CfList = [], rocker:open_default(BookDbPath) end.
Basisentfernung
Um die Datenbank korrekt zu löschen, müssen Sie rocker:destroy(Path).
aufrufen rocker:destroy(Path).
In diesem Fall sollte die Basis nicht verwendet werden.
Datenbankwiederherstellung nach einem Fehler
Im Falle eines Systemausfalls kann die Datenbank mithilfe der Methode rocker:repair(Path)
wiederhergestellt werden. Dieser Vorgang besteht aus 4 Schritten:
- Dateisuche
- Stellen Sie Tische wieder her, indem Sie WAL spielen
- Metadaten extrahieren
- Deskriptordatensatz
Spaltenfamilie erstellen
Cf = <<"testcf1">>, rocker:create_cf_default(Db, Cf) -> ok.
Entfernen der Spaltenfamilie
Cf = <<"testcf1">>, rocker:drop_cf(Db, Cf) -> ok.
CRUD-Operationen
Schlüsseldateneingabe
rocker:put(Db, <<"key">>, <<"value">>) -> ok.
Daten per Schlüssel abrufen
rocker:get(Db, <<"key">>) -> {ok, <<"value">>} | notfound
Löschen von Schlüsseldaten
rocker:delete(Db, <<"key">>) -> ok.
Schlüsseldateneingabe in CF
rocker:put_cf(Db, <<"testcf">>, <<"key">>, <<"value">>) -> ok.
Abrufen von Schlüsseldaten innerhalb von CF.
rocker:get_cf(Db, <<"testcf">>, <<"key">>) -> {ok, <<"value">>} | notfound
Entfernen des CF-Schlüssels
rocker:delete_cf(Db, <<"testcf">>, <<"key">>) -> ok
Iteratoren
Wie Sie wissen, ist eines der Grundprinzipien von rocksbb die ordnungsgemäße Aufbewahrung von Schlüsseln. Diese Funktion ist bei realen Aufgaben sehr nützlich. Um es zu verwenden, benötigen wir Dateniteratoren. Rocksdb verfügt über mehrere Modi zum Übergeben von Daten (detaillierte Codebeispiele finden Sie in den Tests ):
- Vom Anfang der Tabelle. Der Rocker ist dafür im Iterator
{'start'}
- Vom Ende der Tabelle:
{'end'}
- Ausgehend von einem bestimmten Schlüssel vorwärts
{'from', Key, forward}
- Ausgehend von einem bestimmten Schlüssel zurück
{'from', Key, reverse}
Es ist anzumerken, dass diese Modi auch zum Durchlaufen der in Spaltenfamilien gespeicherten Daten funktionieren.
Erstellen Sie einen Iterator
rocker:iterator(Db, {'start'}) -> {ok, Iter}.
Iteratorprüfung
rocker:iterator_valid(Iter) -> {ok, true} | {ok, false}.
Erstellen eines Iterators für CF
rocker:iterator_cf(Db, Cf, {'start'}) -> {ok, Iter}.
Erstellen eines Präfix-Iterators
Der Präfix-Iterator erfordert die explizite Angabe der Länge des Präfixes beim Erstellen der Datenbank.
{ok, Db} = rocker:open(Path, #{ prefix_length => 3 }).
Ein Beispiel für die Erstellung eines Iterators mit dem Präfix "aaa":
{ok, Iter} = rocker:prefix_iterator(Db, <<"aaa">>).
Erstellen eines Präfix-Iterators für CF
Ähnlich wie beim vorherigen Präfix-Iterator ist für die Spaltenfamilie eine explizite prefix_length
erforderlich
{ok, Iter} = rocker:prefix_iterator_cf(Db, Cf, <<"aaa">>).
Holen Sie sich den nächsten Artikel
Die Methode gibt den nächsten Schlüssel / Wert zurück oder ok, wenn der Iterator abgeschlossen ist.
rocker:next(Iter) -> {ok, <<"key">>, <<"value">>} | ok
Transaktionen
Ein ziemlich häufiges Ereignis ist die Anforderung, Änderungen an einer Schlüsselgruppe gleichzeitig aufzuzeichnen. Mit Rocker können Sie CRUD-Operationen sowohl innerhalb eines gemeinsamen Satzes als auch innerhalb von CF kombinieren.
Dieses Beispiel zeigt die Arbeit mit Transaktionen:
{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">>} ]).
Leistung
Einen Leistungstest finden Sie in der Testsuite. Es werden ungefähr 30.000 RPS zum Schreiben und 200.000 RPS zum Lesen auf meinem Computer angezeigt. Unter realen Bedingungen können Sie 15 bis 20.000 RPS zum Schreiben und etwa 120.000 RPS zum Lesen mit einer durchschnittlichen Datengröße von etwa 1 KB pro Schlüssel erwarten, und die Gesamtzahl der Schlüssel beträgt mehr als 1 Milliarde.
Fazit
Die Entwicklung und Anwendung von Rocker in unserem Projekt ermöglichte es uns, die Reaktionszeit des Systems zu verkürzen, die Zuverlässigkeit zu erhöhen und die Neustartzeit zu verkürzen. Diese Vorteile wurden mit minimalen Entwicklungs- und Implementierungskosten erzielt.
Ich bin zu dem Schluss gekommen, dass für Erlang-Projekte, die optimiert werden müssen, die Verwendung von Rust optimal ist. Erlang schafft es, 95% des Codes schnell und effizient zu implementieren, während Rust 5% hemmend umschreibt / hinzufügt, ohne die Zuverlässigkeit des Gesamtsystems zu beeinträchtigen.
PS Es gibt positive Erfahrungen bei der Entwicklung von NIF für Arithmetik mit beliebiger Genauigkeit in Erlang, die in einem separaten Artikel beschrieben werden kann. Ich möchte klarstellen, ob das NIF-Thema zu Rust für die Community interessant ist.