Funktionen zum Entwerfen eines Datenmodells für NoSQL

Einführung

Funktionen zum Entwerfen eines Datenmodells für NoSQL „Man muss so schnell laufen, wie man kann, nur um an Ort und Stelle zu bleiben,
Und um irgendwohin zu kommen, muss man mindestens doppelt so schnell laufen!“
(c) Alice im Wunderland

Vor einiger Zeit wurde ich gebeten, einen Vortrag zu halten Analysten unser Unternehmen zum Thema Entwurf von Datenmodellen, denn wenn wir lange (manchmal mehrere Jahre) an Projekten sitzen, verlieren wir den Blick für das, was um uns herum in der Welt der IT-Technologien passiert. In unserem Unternehmen (zufälligerweise) verwenden viele Projekte (zumindest im Moment) keine NoSQL-Datenbanken, daher habe ich ihnen in meinem Vortrag am Beispiel von HBase gesondert etwas Aufmerksamkeit geschenkt und versucht, die Präsentation des Materials daran auszurichten die sie noch nie benutzt haben, haben funktioniert. Insbesondere habe ich einige Merkmale des Datenmodelldesigns anhand eines Beispiels veranschaulicht, das ich vor einigen Jahren gelesen habe im Artikel „Introduction to HB ase Schema Design“ von Amandeep Khurana. Bei der Analyse von Beispielen habe ich mehrere Möglichkeiten zur Lösung desselben Problems verglichen, um dem Publikum die Hauptgedanken besser zu vermitteln.

Kürzlich habe ich mir „aus Nichtstun“ die Frage gestellt (das lange Maiwochenende in Quarantäne ist hierfür besonders begünstigend): Wie sehr werden theoretische Berechnungen mit der Praxis übereinstimmen? Eigentlich ist so die Idee zu diesem Artikel entstanden. Ein Entwickler, der mehrere Tage mit NoSQL gearbeitet hat, lernt möglicherweise nichts Neues daraus (und überspringt daher möglicherweise sofort die Hälfte des Artikels). Aber für AnalystenFür diejenigen, die noch nicht intensiv mit NoSQL gearbeitet haben, denke ich, dass es nützlich sein wird, um ein grundlegendes Verständnis der Funktionen des Entwurfs von Datenmodellen für HBase zu erlangen.

Beispielanalyse

Meiner Meinung nach muss man vor dem Einsatz von NoSQL-Datenbanken sorgfältig nachdenken und die Vor- und Nachteile abwägen. Oft lässt sich das Problem höchstwahrscheinlich mithilfe herkömmlicher relationaler DBMS lösen. Daher ist es besser, NoSQL nicht ohne triftige Gründe zu verwenden. Wenn Sie sich dennoch für den Einsatz einer NoSQL-Datenbank entschieden haben, sollten Sie berücksichtigen, dass die Designansätze hier etwas unterschiedlich sind. Vor allem einige davon könnten für diejenigen ungewöhnlich sein, die sich bisher nur mit relationalen DBMS beschäftigt haben (nach meinen Beobachtungen). In der „relationalen“ Welt beginnen wir normalerweise mit der Modellierung der Problemdomäne und denormalisieren das Modell erst dann, wenn nötig. In NoSQL wir sollte sofort die erwarteten Szenarien für die Arbeit mit Daten berücksichtigen und zunächst die Daten denormalisieren. Darüber hinaus gibt es noch eine Reihe weiterer Unterschiede, auf die im Folgenden eingegangen wird.

Betrachten wir das folgende „synthetische“ Problem, mit dem wir weiter arbeiten werden:

Es ist notwendig, eine Speicherstruktur für die Freundesliste der Benutzer eines abstrakten sozialen Netzwerks zu entwerfen. Der Einfachheit halber gehen wir davon aus, dass alle unsere Verbindungen gerichtet sind (wie auf Instagram, nicht auf Linkedin). Die Struktur sollte es Ihnen ermöglichen, effektiv:

  • Beantworten Sie die Frage, ob Benutzer A Benutzer B liest (Lesemuster).
  • Erlauben Sie das Hinzufügen/Entfernen von Verbindungen im Falle der Anmeldung/Abmeldung von Benutzer A von Benutzer B (Datenänderungsvorlage)

Natürlich gibt es viele Möglichkeiten, das Problem zu lösen. In einer regulären relationalen Datenbank würden wir höchstwahrscheinlich einfach eine Beziehungstabelle erstellen (möglicherweise typisiert, wenn wir beispielsweise eine Benutzergruppe speichern müssen: Familie, Arbeit usw., zu der dieser „Freund“ gehört) und diese optimieren Die Zugriffsgeschwindigkeit würde Indizes/Partitionierung hinzufügen. Höchstwahrscheinlich würde der Finaltisch etwa so aussehen:

user_id
Freund_ID

Wasja
Petya

Wasja
Olja

Im Folgenden werde ich der Klarheit und dem besseren Verständnis halber Namen anstelle von IDs angeben

Im Fall von HBase wissen wir Folgendes:

  • Eine effiziente Suche, die nicht zu einem vollständigen Tabellenscan führt, ist möglich ausschließlich per Schlüssel
    • Tatsächlich ist es deshalb eine schlechte Idee, SQL-Abfragen, die vielen bekannt sind, in solche Datenbanken zu schreiben. Technisch gesehen können Sie natürlich vom selben Impala aus eine SQL-Abfrage mit Joins und anderer Logik an HBase senden, aber wie effektiv wird das sein ...

Daher sind wir gezwungen, die Benutzer-ID als Schlüssel zu verwenden. Und mein erster Gedanke zum Thema „Wo und wie speichert man die Ausweise von Freunden?“ Vielleicht eine Idee, sie in Spalten zu speichern. Diese offensichtlichste und „naivste“ Option wird in etwa so aussehen (nennen wir es Option 1 (Standard)zur weiteren Referenz):

RowKey
Lautsprecher

Wasja
1: Petja
2: Olja
3: Dascha

Petya
1: Mascha
2: Wasja

Hier entspricht jede Zeile einem Netzwerkbenutzer. Die Spalten haben Namen: 1, 2, ... – entsprechend der Anzahl der Freunde, und die IDs der Freunde werden in den Spalten gespeichert. Es ist wichtig zu beachten, dass jede Zeile eine unterschiedliche Anzahl von Spalten hat. Im Beispiel in der Abbildung oben hat eine Zeile drei Spalten (1, 2 und 3) und die zweite nur zwei (1 und 2) – hier haben wir selbst zwei HBase-Eigenschaften verwendet, die relationale Datenbanken nicht haben:

  • die Möglichkeit, die Zusammensetzung von Spalten dynamisch zu ändern (Freund hinzufügen -> Spalte hinzufügen, Freund entfernen -> Spalte löschen)
  • Verschiedene Zeilen können unterschiedliche Spaltenzusammensetzungen haben

Überprüfen wir unsere Struktur auf Übereinstimmung mit den Anforderungen der Aufgabe:

  • Lesen von Daten: Um zu verstehen, ob Vasya Olya abonniert hat, müssen wir subtrahieren die ganze Linie mit dem Schlüssel RowKey = „Vasya“ und sortieren Sie die Spaltenwerte, bis wir Olya darin „treffen“. Oder durchlaufen Sie die Werte aller Spalten, „treffen“ Sie Olya nicht und geben Sie die Antwort „Falsch“ zurück.
  • Daten bearbeiten: Einen Freund hinzufügen: Für eine ähnliche Aufgabe müssen wir auch subtrahieren die ganze Linie Verwenden Sie den Schlüssel RowKey = „Vasya“, um die Gesamtzahl seiner Freunde zu berechnen. Diese Gesamtzahl der Freunde benötigen wir, um die Nummer der Spalte zu ermitteln, in die wir die ID des neuen Freundes eintragen müssen.
  • Daten ändern: Einen Freund löschen:
    • Muss subtrahiert werden die ganze Linie mit dem Schlüssel RowKey = „Vasya“ und sortieren Sie die Spalten, um diejenige zu finden, in der der zu löschende Freund eingetragen ist;
    • Als nächstes müssen wir nach dem Löschen eines Freundes alle Daten in eine Spalte „verschieben“, um keine „Lücken“ in ihrer Nummerierung zu bekommen.

Lassen Sie uns nun bewerten, wie produktiv diese Algorithmen sein werden, die wir auf der Seite der „bedingten Anwendung“ implementieren müssen O-Symbolik. Bezeichnen wir die Größe unseres hypothetischen sozialen Netzwerks als n. Dann beträgt die maximale Anzahl an Freunden, die ein Benutzer haben kann, (n-1). Wir können dieses (-1) für unsere Zwecke weiter vernachlässigen, da es im Rahmen der Verwendung von O-Symbolen keine Rolle spielt.

  • Lesen von Daten: Es ist notwendig, die gesamte Zeile zu subtrahieren und alle ihre Spalten im Grenzwert zu durchlaufen. Dies bedeutet, dass die obere Schätzung der Kosten ungefähr O(n) beträgt.
  • Daten bearbeiten: Einen Freund hinzufügen: Um die Anzahl der Freunde zu bestimmen, müssen Sie alle Spalten der Zeile durchlaufen und dann eine neue Spalte einfügen => O(n)
  • Daten ändern: Einen Freund löschen:
    • Ähnlich wie beim Addieren – Sie müssen alle Spalten im limit => O(n) durchgehen.
    • Nachdem wir die Spalten entfernt haben, müssen wir sie „verschieben“. Wenn Sie dies „frontal“ umsetzen, benötigen Sie im Limit bis zu (n-1) Operationen. Aber hier und im weiteren Verlauf des praktischen Teils werden wir einen anderen Ansatz verwenden, der eine „Pseudoverschiebung“ für eine feste Anzahl von Operationen implementiert – das heißt, es wird unabhängig von n eine konstante Zeit dafür aufgewendet. Diese konstante Zeit (genauer gesagt O(2)) kann im Vergleich zu O(n) vernachlässigt werden. Der Ansatz wird in der folgenden Abbildung veranschaulicht: Wir kopieren einfach die Daten aus der „letzten“ Spalte in die Spalte, aus der wir Daten löschen möchten, und löschen dann die letzte Spalte:
      Funktionen zum Entwerfen eines Datenmodells für NoSQL

Insgesamt erhielten wir in allen Szenarien eine asymptotische Rechenkomplexität von O(n).
Sie haben wahrscheinlich bereits bemerkt, dass wir fast immer die gesamte Zeile aus der Datenbank lesen müssen und in zwei von drei Fällen einfach alle Spalten durchgehen und die Gesamtzahl der Freunde berechnen müssen. Als Optimierungsversuch können Sie daher eine Spalte „Anzahl“ hinzufügen, in der die Gesamtzahl der Freunde jedes Netzwerkbenutzers gespeichert wird. In diesem Fall können wir nicht die gesamte Zeile lesen, um die Gesamtzahl der Freunde zu berechnen, sondern nur eine Spalte „Zählung“. Die Hauptsache ist, nicht zu vergessen, „count“ zu aktualisieren, wenn man Daten manipuliert. Das. wir verbessern uns Option 2 (Anzahl):

RowKey
Lautsprecher

Wasja
1: Petja
2: Olja
3: Dascha
zählen: 3

Petya
1: Mascha
2: Wasja

zählen: 2

Im Vergleich zur ersten Option:

  • Lesen von Daten: um eine Antwort auf die Frage „Liest Vasya Olya?“ zu erhalten. nichts hat sich geändert => O(n)
  • Daten bearbeiten: Einen Freund hinzufügen: Wir haben das Einfügen eines neuen Freundes vereinfacht, da wir jetzt nicht die gesamte Zeile lesen und über ihre Spalten iterieren müssen, sondern nur den Wert der Spalte „count“ usw. erhalten können. Bestimmen Sie sofort die Spaltennummer, um einen neuen Freund einzufügen. Dies führt zu einer Reduzierung der Rechenkomplexität auf O(1)
  • Daten ändern: Einen Freund löschen: Beim Löschen eines Freundes können wir diese Spalte auch verwenden, um die Anzahl der I/O-Vorgänge zu reduzieren, wenn die Daten eine Zelle nach links „verschoben“ werden. Es besteht jedoch immer noch die Notwendigkeit, die Spalten zu durchlaufen, um diejenige zu finden, die gelöscht werden muss, also => ​​​​O(n)
  • Andererseits müssen wir jetzt bei der Aktualisierung von Daten jedes Mal die Spalte „Anzahl“ aktualisieren, was jedoch eine konstante Zeit in Anspruch nimmt, die im Rahmen von O-Symbolen vernachlässigt werden kann

Im Allgemeinen erscheint Option 2 etwas optimaler, ähnelt aber eher „Evolution statt Revolution“. Um eine „Revolution“ durchzuführen, brauchen wir Option 3 (Spalte).
Stellen wir alles „auf den Kopf“: Wir weisen zu Spaltenname Benutzer-ID! Was in der Spalte selbst geschrieben wird, ist für uns nicht mehr wichtig, sei es die Nummer 1 (im Allgemeinen können dort nützliche Dinge gespeichert werden, z. B. die Gruppe „Familie/Freunde/usw.“). Dieser Ansatz mag den unvorbereiteten „Laien“, der noch keine Erfahrung im Umgang mit NoSQL-Datenbanken hat, überraschen, aber genau dieser Ansatz ermöglicht es Ihnen, das Potenzial von HBase bei dieser Aufgabe viel effektiver zu nutzen:

RowKey
Lautsprecher

Wasja
Petja: 1
Olja: 1
Dascha: 1

Petya
Mascha: 1
Wasja: 1

Hier erhalten wir gleich mehrere Vorteile. Um sie zu verstehen, analysieren wir die neue Struktur und schätzen die Rechenkomplexität ab:

  • Lesen von Daten: Um die Frage zu beantworten, ob Vasya Olya abonniert hat, reicht es aus, eine Spalte „Olya“ zu lesen: Wenn sie vorhanden ist, lautet die Antwort „True“, wenn nicht – „False“ => O(1)
  • Daten bearbeiten: Einen Freund hinzufügen: Einen Freund hinzufügen: Fügen Sie einfach eine neue Spalte „Freund-ID“ hinzu => O(1)
  • Daten ändern: Einen Freund löschen: Entfernen Sie einfach die Spalte „Freund-ID“ => O(1)

Wie Sie sehen, besteht ein wesentlicher Vorteil dieses Speichermodells darin, dass wir in allen von uns benötigten Szenarien mit nur einer Spalte arbeiten und so vermeiden, die gesamte Zeile aus der Datenbank zu lesen und darüber hinaus alle Spalten dieser Zeile aufzulisten. Wir könnten hier aufhören, aber...

Sie können sich wundern und den Weg der Leistungsoptimierung und Reduzierung der I/O-Vorgänge beim Zugriff auf die Datenbank noch ein wenig weitergehen. Was wäre, wenn wir die vollständigen Beziehungsinformationen direkt im Zeilenschlüssel selbst speichern würden? Das heißt, den Schlüssel wie folgt zusammensetzen: userID.friendID? In diesem Fall müssen wir nicht einmal die Spalten der Zeile lesen (Option 4 (Reihe)):

RowKey
Lautsprecher

Vasya.Petya
Petja: 1

Vasya.Olya
Olja: 1

Vasya.Dasha
Dascha: 1

Petja.Mascha
Mascha: 1

Petya.Vasya
Wasja: 1

Offensichtlich wird die Bewertung aller Datenmanipulationsszenarien in einer solchen Struktur wie in der vorherigen Version O(1) sein. Der Unterschied zu Option 3 liegt ausschließlich in der Effizienz der E/A-Vorgänge in der Datenbank.

Nun, die letzte „Verbeugung“. Es ist leicht zu erkennen, dass unser Zeilenschlüssel in Option 4 eine variable Länge hat, was sich möglicherweise auf die Leistung auswirken kann (hier erinnern wir uns, dass HBase Daten als Satz von Bytes speichert und Zeilen in Tabellen nach Schlüssel sortiert sind). Außerdem haben wir ein Trennzeichen, das in einigen Szenarien möglicherweise gehandhabt werden muss. Um diesen Einfluss zu beseitigen, können Sie Hashes von Benutzer-ID und Freund-ID verwenden. Da beide Hashes eine konstante Länge haben, können Sie sie einfach ohne Trennzeichen verketten. Dann sehen die Daten in der Tabelle so aus (Option 5 (Hash)):

RowKey
Lautsprecher

dc084ef00e94aef49be885f9b01f51c01918fa783851db0dc1f72f83d33a5994
Petja: 1

dc084ef00e94aef49be885f9b01f51c0f06b7714b5ba522c3cf51328b66fe28a
Olja: 1

dc084ef00e94aef49be885f9b01f51c00d2c2e5d69df6b238754f650d56c896a
Dascha: 1

1918fa783851db0dc1f72f83d33a59949ee3309645bd2c0775899fca14f311e1
Mascha: 1

1918fa783851db0dc1f72f83d33a5994dc084ef00e94aef49be885f9b01f51c0
Wasja: 1

Offensichtlich wird die algorithmische Komplexität der Arbeit mit einer solchen Struktur in den von uns betrachteten Szenarien dieselbe sein wie die von Option 4 – also O(1).
Fassen wir alle unsere Schätzungen der Rechenkomplexität insgesamt in einer Tabelle zusammen:

Einen Freund hinzufügen
Ich schaue nach einem Freund
Einen Freund entfernen

Option 1 (Standard)
O (n)
O (n)
O (n)

Option 2 (Anzahl)
O (1)
O (n)
O (n)

Option 3 (Spalte)
O (1)
O (1)
O (1)

Option 4 (Reihe)
O (1)
O (1)
O (1)

Option 5 (Hash)
O (1)
O (1)
O (1)

Wie Sie sehen, scheinen die Optionen 3–5 am besten geeignet zu sein und gewährleisten theoretisch die Ausführung aller erforderlichen Datenmanipulationsszenarien in konstanter Zeit. Unter den Bedingungen unserer Aufgabe besteht keine ausdrückliche Anforderung, eine Liste aller Freunde des Benutzers zu erhalten, aber bei realen Projektaktivitäten wäre es für uns als gute Analysten gut, zu „antizipieren“, dass eine solche Aufgabe auftreten könnte und „einen Strohhalm ausbreiten.“ Daher sind meine Sympathien auf der Seite von Option 3. Es ist jedoch sehr wahrscheinlich, dass diese Anfrage in einem realen Projekt bereits auf andere Weise hätte gelöst werden können. Ohne eine allgemeine Sicht auf das gesamte Problem ist es daher besser, dies nicht zu tun abschließende Schlussfolgerungen.

Vorbereitung des Experiments

Ich möchte die oben genannten theoretischen Argumente in der Praxis testen – das war das Ziel der Idee, die am langen Wochenende entstanden ist. Dazu ist es notwendig, die Betriebsgeschwindigkeit unserer „bedingten Anwendung“ in allen beschriebenen Szenarien zur Nutzung der Datenbank sowie die Zunahme dieser Zeit mit zunehmender Größe des sozialen Netzwerks (n) zu bewerten. Der Zielparameter, der uns interessiert und den wir im Experiment messen werden, ist die Zeit, die die „bedingte Anwendung“ für die Durchführung einer „Geschäftsoperation“ benötigt. Mit „Geschäftstransaktion“ meinen wir Folgendes:

  • Einen neuen Freund hinzufügen
  • Überprüfen, ob Benutzer A ein Freund von Benutzer B ist
  • Einen Freund entfernen

Unter Berücksichtigung der in der Erstaussage dargelegten Anforderungen ergibt sich somit das Verifizierungsszenario wie folgt:

  • Datenaufzeichnung. Generieren Sie zufällig ein anfängliches Netzwerk der Größe n. Um der „realen Welt“ näher zu kommen, ist die Anzahl der Freunde, die jeder Benutzer hat, ebenfalls eine Zufallsvariable. Messen Sie die Zeit, in der unsere „bedingte Anwendung“ alle generierten Daten in HBase schreibt. Teilen Sie dann die resultierende Zeit durch die Gesamtzahl der hinzugefügten Freunde – so erhalten wir die durchschnittliche Zeit für einen „Geschäftsvorgang“
  • Lesen von Daten. Erstellen Sie für jeden Benutzer eine Liste von „Persönlichkeiten“, für die Sie eine Antwort benötigen, unabhängig davon, ob der Benutzer sie abonniert hat oder nicht. Die Länge der Liste entspricht ungefähr der Anzahl der Freunde des Benutzers, und für die Hälfte der überprüften Freunde sollte die Antwort „Ja“ und für die andere Hälfte „Nein“ lauten. Die Prüfung erfolgt in einer solchen Reihenfolge, dass sich die Antworten „Ja“ und „Nein“ abwechseln (d. h. in jedem zweiten Fall müssen wir alle Spalten der Zeile für die Optionen 1 und 2 durchgehen). Die gesamte Screening-Zeit wird dann durch die Anzahl der getesteten Freunde dividiert, um die durchschnittliche Screening-Zeit pro Proband zu erhalten.
  • Datenlöschung. Entfernen Sie alle Freunde vom Benutzer. Darüber hinaus ist die Löschreihenfolge zufällig (das heißt, wir „mischen“ die ursprüngliche Liste, die zum Aufzeichnen der Daten verwendet wurde). Die gesamte Überprüfungszeit wird dann durch die Anzahl der entfernten Freunde dividiert, um die durchschnittliche Zeit pro Überprüfung zu erhalten.

Die Szenarien müssen für jede der fünf Datenmodelloptionen und für unterschiedliche Größen des sozialen Netzwerks ausgeführt werden, um zu sehen, wie sich die Zeit mit zunehmendem Wachstum ändert. Innerhalb eines n müssen die Verbindungen im Netzwerk und die Liste der zu prüfenden Benutzer natürlich für alle 5 Optionen gleich sein.
Zum besseren Verständnis finden Sie unten ein Beispiel generierter Daten für n=5. Der geschriebene „Generator“ erzeugt drei ID-Wörterbücher als Ausgabe:

  • Die erste dient zum Einfügen
  • Der zweite dient der Überprüfung
  • Drittens – zur Löschung

{0: [1], 1: [4, 5, 3, 2, 1], 2: [1, 2], 3: [2, 4, 1, 5, 3], 4: [2, 1]} # всего 15 друзей

{0: [1, 10800], 1: [5, 10800, 2, 10801, 4, 10802], 2: [1, 10800], 3: [3, 10800, 1, 10801, 5, 10802], 4: [2, 10800]} # всего 18 проверяемых субъектов

{0: [1], 1: [1, 3, 2, 5, 4], 2: [1, 2], 3: [4, 1, 2, 3, 5], 4: [1, 2]} # всего 15 друзей

Wie Sie sehen, sind alle IDs größer als 10 im zu überprüfenden Wörterbuch genau diejenigen, die mit Sicherheit die Antwort „Falsch“ liefern. Das Einfügen, Prüfen und Löschen von „Freunden“ erfolgt genau in der im Wörterbuch vorgegebenen Reihenfolge.

Das Experiment wurde auf einem Laptop mit Windows 10 durchgeführt, wobei HBase in einem Docker-Container und Python mit Jupyter Notebook im anderen lief. Docker wurden 2 CPU-Kerne und 2 GB RAM zugewiesen. Die gesamte Logik, wie die Emulation der „bedingten Anwendung“ und das „Piping“ zur Generierung von Testdaten und Zeitmessung, wurde in Python geschrieben. Die Bibliothek wurde für die Arbeit mit HBase verwendet Happybase, um Hashes (MD5) für Option 5 – Hashlib zu berechnen

Unter Berücksichtigung der Rechenleistung eines bestimmten Laptops wurde experimentell ein Start für n = 10, 30, … ausgewählt. 170 – wenn die Gesamtbetriebszeit des gesamten Testzyklus (alle Szenarien für alle Optionen für alle n) noch mehr oder weniger angemessen war und für eine Teeparty geeignet war (durchschnittlich 15 Minuten).

An dieser Stelle muss angemerkt werden, dass es in diesem Experiment nicht primär um die Bewertung absoluter Leistungszahlen geht. Selbst ein relativer Vergleich verschiedener zwei Optionen ist möglicherweise nicht ganz korrekt. Nun interessiert uns die Art der zeitlichen Änderung in Abhängigkeit von n, da es unter Berücksichtigung der obigen Konfiguration des „Prüfstands“ sehr schwierig ist, Zeitschätzungen zu erhalten, die vom Einfluss zufälliger und anderer Faktoren „bereinigt“ sind ( und eine solche Aufgabe wurde nicht gestellt).

Experimentergebnis

Der erste Test besteht darin, wie sich der Zeitaufwand für das Ausfüllen der Freundesliste verändert. Das Ergebnis finden Sie in der Grafik unten.
Funktionen zum Entwerfen eines Datenmodells für NoSQL
Die Optionen 3-5 zeigen erwartungsgemäß eine nahezu konstante „Geschäftstransaktionszeit“, die nicht vom Wachstum der Netzwerkgröße abhängt, und einen nicht unterscheidbaren Leistungsunterschied.
Option 2 zeigt ebenfalls eine konstante, aber etwas schlechtere Leistung, fast genau das Zweifache im Vergleich zu den Optionen 2–3. Und das kann nur freuen, denn es entspricht der Theorie – in dieser Version ist die Anzahl der I/O-Operationen zu/von HBase genau doppelt so hoch. Dies kann als indirekter Beweis dafür dienen, dass unser Prüfstand grundsätzlich eine gute Genauigkeit liefert.
Option 1 erweist sich erwartungsgemäß auch als die langsamste und zeigt einen linearen Anstieg der Zeit, die für das gegenseitige Hinzufügen zur Größe des Netzwerks aufgewendet wird.
Schauen wir uns nun die Ergebnisse des zweiten Tests an.
Funktionen zum Entwerfen eines Datenmodells für NoSQL
Die Optionen 3–5 verhalten sich wieder wie erwartet – konstante Zeit, unabhängig von der Größe des Netzwerks. Die Optionen 1 und 2 zeigen einen linearen Zeitanstieg mit zunehmender Netzwerkgröße und eine ähnliche Leistung. Darüber hinaus erweist sich Option 2 als etwas langsamer – offenbar aufgrund der Notwendigkeit, die zusätzliche Spalte „Anzahl“ Korrektur zu lesen und zu verarbeiten, was mit zunehmendem n deutlicher wird. Dennoch verzichte ich darauf, irgendwelche Schlussfolgerungen zu ziehen, da die Genauigkeit dieses Vergleichs relativ gering ist. Darüber hinaus änderten sich diese Verhältnisse (welche Option 1 oder 2 schneller ist) von Lauf zu Lauf (wobei die Art der Abhängigkeit und das „Kopf-an-Kopf-Rennen“ erhalten blieben).

Nun, die letzte Grafik ist das Ergebnis von Entfernungstests.

Funktionen zum Entwerfen eines Datenmodells für NoSQL

Auch hier gibt es keine Überraschungen. Die Optionen 3–5 führen die Entfernung in konstanter Zeit durch.
Darüber hinaus zeigen die Optionen 4 und 5 interessanterweise im Gegensatz zu den vorherigen Szenarien eine merklich etwas schlechtere Leistung als Option 3. Offenbar ist der Zeilenlöschvorgang teurer als der Spaltenlöschvorgang, was im Allgemeinen logisch ist.

Die Optionen 1 und 2 zeigen erwartungsgemäß einen linearen Zeitanstieg. Gleichzeitig ist Option 2 durchweg langsamer als Option 1 – aufgrund der zusätzlichen I/O-Operation zur „Pflege“ der Zählspalte.

Allgemeine Schlussfolgerungen des Experiments:

  • Die Optionen 3–5 zeigen eine höhere Effizienz, da sie die Vorteile von HBase nutzen. Darüber hinaus unterscheidet sich ihre Leistung um eine Konstante voneinander und hängt nicht von der Größe des Netzwerks ab.
  • Der Unterschied zwischen den Optionen 4 und 5 wurde nicht erfasst. Dies bedeutet jedoch nicht, dass Option 5 nicht genutzt werden sollte. Es ist wahrscheinlich, dass das verwendete Versuchsszenario unter Berücksichtigung der Leistungsmerkmale des Prüfstands eine Erkennung nicht zuließ.
  • Die Art der Erhöhung des Zeitaufwands für die Durchführung von „Geschäftsvorgängen“ mit Daten bestätigte im Allgemeinen die zuvor erhaltenen theoretischen Berechnungen für alle Optionen.

Letzter Akt

Die durchgeführten groben Experimente sollten nicht als absolute Wahrheit angesehen werden. Es gibt viele Faktoren, die nicht berücksichtigt wurden und die Ergebnisse verfälschten (diese Schwankungen sind insbesondere in den Diagrammen bei kleiner Netzwerkgröße sichtbar). Zum Beispiel die Geschwindigkeit von Thrift, die von Happybase verwendet wird, der Umfang und die Methode zur Implementierung der Logik, die ich in Python geschrieben habe (ich kann nicht behaupten, dass der Code optimal geschrieben wurde und die Fähigkeiten aller Komponenten effektiv nutzt). die Funktionen von HBase-Caching, Hintergrundaktivität von Windows 10 auf meinem Laptop usw. Im Allgemeinen können wir davon ausgehen, dass alle theoretischen Berechnungen ihre Gültigkeit experimentell nachgewiesen haben. Nun, zumindest war es nicht möglich, sie mit einem solchen „Frontalangriff“ zu widerlegen.

Abschließend Empfehlungen für alle, die gerade erst anfangen, Datenmodelle in HBase zu entwerfen: Nutzen Sie Ihre bisherigen Erfahrungen bei der Arbeit mit relationalen Datenbanken und erinnern Sie sich an die „Gebote“:

  • Beim Entwerfen gehen wir von der Aufgabe und den Mustern der Datenmanipulation aus und nicht vom Domänenmodell
  • Effizienter Zugriff (ohne vollständigen Tabellenscan) – nur per Schlüssel
  • Denormalisierung
  • Verschiedene Zeilen können unterschiedliche Spalten enthalten
  • Dynamische Zusammensetzung der Lautsprecher

Source: habr.com

Kommentar hinzufügen