Verwendung des Git-Repositorys als Datenbank-Backend


119

Ich mache ein Projekt, das sich mit strukturierten Dokumentendatenbanken befasst. Ich habe einen Baum von Kategorien (~ 1000 Kategorien, bis zu ~ 50 Kategorien auf jeder Ebene), jede Kategorie enthält mehrere Tausend (bis zu ~ 10000) strukturierte Dokumente. Jedes Dokument besteht aus mehreren Kilobyte Daten in strukturierter Form (ich würde YAML bevorzugen, aber es kann genauso gut JSON oder XML sein).

Benutzer dieses Systems führen verschiedene Arten von Vorgängen aus:

  • Abrufen dieser Dokumente per ID
  • Suchen nach Dokumenten anhand einiger der darin enthaltenen strukturierten Attribute
  • Bearbeiten von Dokumenten (dh Hinzufügen / Entfernen / Umbenennen / Zusammenführen); Jeder Bearbeitungsvorgang sollte als Transaktion mit einem Kommentar aufgezeichnet werden
  • Anzeigen eines Verlaufs aufgezeichneter Änderungen für ein bestimmtes Dokument (einschließlich Anzeigen, wer, wann und warum das Dokument geändert wurde, Erhalten einer früheren Version - und wahrscheinlich Zurücksetzen auf diese, wenn dies angefordert wird)

Natürlich würde die traditionelle Lösung darin bestehen, eine Art Dokumentendatenbank (wie CouchDB oder Mongo) für dieses Problem zu verwenden. Diese Versionskontrolle (Verlauf) verleitete mich jedoch zu einer wilden Idee - warum sollte ich das gitRepository nicht als verwenden ? Datenbank-Backend für diese Anwendung?

Auf den ersten Blick könnte es so gelöst werden:

  • Kategorie = Verzeichnis, Dokument = Datei
  • Dokument per ID abrufen => Verzeichnisse wechseln + Datei in einer Arbeitskopie lesen
  • Bearbeiten von Dokumenten mit Bearbeitungskommentaren => Festschreiben durch verschiedene Benutzer + Speichern von Festschreibungsnachrichten
  • Verlauf => normales Git-Protokoll und Abrufen älterer Transaktionen
  • search => das ist ein etwas kniffligerer Teil, ich denke, es würde einen regelmäßigen Export einer Kategorie in eine relationale Datenbank mit Indizierung von Spalten erfordern, nach denen wir suchen können

Gibt es andere häufige Fallstricke bei dieser Lösung? Hat jemand bereits versucht, ein solches Backend zu implementieren (dh für alle gängigen Frameworks - RoR, node.js, Django, CakePHP)? Hat diese Lösung mögliche Auswirkungen auf die Leistung oder Zuverlässigkeit - dh ist nachgewiesen, dass Git viel langsamer als herkömmliche Datenbanklösungen ist oder es Fallstricke bei Skalierbarkeit / Zuverlässigkeit gibt? Ich gehe davon aus, dass ein Cluster solcher Server, die sich gegenseitig das Repository pushen / ziehen, ziemlich robust und zuverlässig sein sollte.

Sagen Sie mir im Grunde, ob diese Lösung funktioniert und warum sie funktioniert oder nicht.


Antworten:


58

Die Beantwortung meiner eigenen Frage ist nicht das Beste, aber da ich die Idee letztendlich fallen ließ, möchte ich auf die Gründe eingehen, die in meinem Fall funktioniert haben. Ich möchte betonen, dass diese Begründung möglicherweise nicht für alle Fälle gilt. Es liegt also am Architekten, zu entscheiden.

Im Allgemeinen ist der erste wichtige Punkt, den meine Frage übersieht, dass es sich um ein Mehrbenutzersystem handelt, das parallel arbeitet und gleichzeitig meinen Server mit einem Thin Client (dh nur einem Webbrowser) verwendet. Auf diese Weise muss ich für alle den Zustand aufrechterhalten . Es gibt verschiedene Ansätze für diesen einen, aber alle sind entweder zu ressourcenintensiv oder zu komplex für die Implementierung (und töten damit den ursprünglichen Zweck, alle harten Implementierungsmaterialien zu entladen, um sie überhaupt erst zu erledigen):

  • "Stumpfer" Ansatz: 1 Benutzer = 1 Status = 1 vollständige Arbeitskopie eines Repositorys, das der Server für den Benutzer verwaltet. Selbst wenn es sich um eine relativ kleine Dokumentendatenbank (z. B. 100 MiBs) mit ~ 100.000 Benutzern handelt, führt die Beibehaltung des vollständigen Repository-Klons für alle Benutzer dazu, dass die Datenträgerverwendung über das Dach läuft (dh 100.000 Benutzer mal 100 MB ~ 10 TiB). . Was noch schlimmer ist, das Klonen von 100-MiB-Repositorys dauert jedes Mal einige Sekunden, selbst wenn es auf ziemlich effektive Weise durchgeführt wird (dh nicht von Git verwendet wird und Sachen umgepackt und neu verpackt werden), was IMO nicht akzeptabel ist. Und noch schlimmer: Jede Bearbeitung, die wir auf einen Hauptbaum anwenden, sollte in das Repository jedes Benutzers gezogen werden. Dies ist (1) ein Ressourcenfresser, (2) kann im Allgemeinen zu ungelösten Bearbeitungskonflikten führen.

    Grundsätzlich kann es in Bezug auf die Disc-Nutzung so schlecht sein wie O (Anzahl der Änderungen × Daten × Anzahl der Benutzer), und eine solche Disc-Nutzung bedeutet automatisch eine ziemlich hohe CPU-Auslastung.

  • Ansatz "Nur aktive Benutzer": Arbeitskopie nur für aktive Benutzer verwalten. Auf diese Weise speichern Sie im Allgemeinen keinen vollständigen Repo-Klon pro Benutzer, sondern:

    • Wenn sich der Benutzer anmeldet, klonen Sie das Repository. Es dauert einige Sekunden und ~ 100 MiB Speicherplatz pro aktivem Benutzer.
    • Während der Benutzer weiterhin an der Site arbeitet, arbeitet er mit der angegebenen Arbeitskopie.
    • Wenn sich der Benutzer abmeldet, wird sein Repository-Klon als Zweig in das Haupt-Repository zurückkopiert, wodurch nur seine "nicht übernommenen Änderungen" gespeichert werden, sofern vorhanden, was ziemlich platzsparend ist.

    Daher erreicht die Disc-Nutzung in diesem Fall einen Spitzenwert bei O (Anzahl der Änderungen × Daten × Anzahl der aktiven Benutzer), was normalerweise ~ 100..1000-mal weniger ist als die Anzahl der Gesamtbenutzer, aber das An- und Abmelden wird komplizierter und langsamer Dies beinhaltet das Klonen eines Zweigs pro Benutzer bei jeder Anmeldung und das Zurückziehen dieser Änderungen beim Abmelden oder Ablaufen der Sitzung (was transaktional erfolgen sollte => fügt eine weitere Komplexitätsebene hinzu). In absoluten Zahlen werden 10 TiBs Disc-Verbrauch auf 10..100 GiBs gesenkt, was in meinem Fall vielleicht akzeptabel ist, aber wir sprechen jetzt wieder von einer ziemlich kleinen Datenbank mit 100 MiBs.

  • "Sparse Checkout" -Ansatz: Das Erstellen eines "Sparse Checkout" anstelle eines vollständigen Repo-Klons pro aktivem Benutzer hilft nicht viel. Dies spart möglicherweise etwa das 10-fache des Speicherplatzbedarfs, jedoch auf Kosten einer viel höheren CPU- / Disc-Last bei Operationen, die den Verlauf betreffen, wodurch der Zweck zunichte gemacht wird.

  • "Workers Pool" -Ansatz: Anstatt jedes Mal vollständige Klone für aktive Personen zu erstellen, halten wir möglicherweise einen Pool von "Worker" -Klonen bereit, die verwendet werden können. Auf diese Weise besetzt er jedes Mal, wenn sich ein Benutzer anmeldet, einen "Arbeiter", zieht dort seinen Zweig aus dem Haupt-Repo und befreit beim "Abmelden" den "Arbeiter", der einen cleveren Git-Hard-Reset durchführt, um wieder gerecht zu werden Ein Haupt-Repo-Klon, der bereit ist, von einem anderen Benutzer verwendet zu werden, der sich anmeldet. Hilft nicht viel bei der Disc-Nutzung (es ist immer noch ziemlich hoch - nur vollständiger Klon pro aktivem Benutzer), aber es beschleunigt zumindest das An- und Abmelden als Kosten von noch komplexer.

Beachten Sie jedoch, dass ich absichtlich die Anzahl der relativ kleinen Datenbanken und Benutzer berechnet habe: 100.000 Benutzer, 1.000 aktive Benutzer, 100 MiBs Gesamtdatenbank + Bearbeitungsverlauf, 10 MiBs Arbeitskopie. Wenn Sie sich prominente Crowd-Sourcing-Projekte ansehen, gibt es dort viel höhere Zahlen:

│              │ Users │ Active users │ DB+edits │ DB only │
├──────────────┼───────┼──────────────┼──────────┼─────────┤
│ MusicBrainz  │  1.2M │     1K/week  │   30 GiB │  20 GiB │
│ en.wikipedia │ 21.5M │   133K/month │    3 TiB │  44 GiB │
│ OSM          │  1.7M │    21K/month │  726 GiB │ 480 GiB │

Offensichtlich wäre dieser Ansatz für diese Daten- / Aktivitätsmengen völlig inakzeptabel.

Im Allgemeinen hätte es funktioniert, wenn man den Webbrowser als "dicken" Client verwenden könnte, dh Git-Operationen ausgeben und so ziemlich die vollständige Kaufabwicklung auf der Clientseite und nicht auf der Serverseite speichern könnte.

Es gibt auch andere Punkte, die ich verpasst habe, aber sie sind im Vergleich zum ersten nicht so schlecht:

  • Das Muster des Bearbeitungsstatus eines "dicken" Benutzers ist in Bezug auf normale ORMs wie ActiveRecord, Hibernate, DataMapper, Tower usw. umstritten.
  • So viel ich gesucht habe, es gibt keine freie Codebasis für diesen Ansatz für Git aus gängigen Frameworks.
  • Es gibt mindestens einen Dienst, der es irgendwie schafft, dies effizient zu erledigen - das ist offensichtlich Github -, aber leider ist ihre Codebasis Closed Source, und ich vermute stark, dass sie keine normalen Git-Server / Repo-Speichertechniken verwenden, dh sie sind im Grunde implementiert Alternative "Big Data" Git.

Also unterm Strich : es ist möglich, aber für die meisten aktuellen usecases es wird nicht überall in der Nähe der optimalen Lösung. Eine bessere Alternative wäre wahrscheinlich, wenn Sie Ihre eigene Implementierung des Dokumentbearbeitungsverlaufs in SQL zusammenfassen oder versuchen, eine vorhandene Dokumentendatenbank zu verwenden.


16
Wahrscheinlich ein bisschen spät zur Party, aber ich hatte eine ähnliche Anforderung und ging tatsächlich den Git-Weg. Nachdem ich ein bisschen mit den Git-Interna herumgegraben hatte, fand ich einen Weg, es zum Laufen zu bringen. Die Idee ist, mit einem nackten Repository zu arbeiten. Es gibt einige Nachteile, aber ich finde es praktikabel. Ich habe alles in einem Beitrag geschrieben, den Sie vielleicht überprüfen möchten (wenn überhaupt, aus Interesse): kenneth-truyers.net/2016/10/13/git-nosql-database
Kenneth

Ein weiterer Grund, warum ich dies nicht tue, sind die Abfragefunktionen. Dokumentenspeicher indizieren häufig Dokumente, sodass die Suche in ihnen einfach ist. Dies wird mit git nicht einfach sein.
FrankyHollywood

12

Ein interessanter Ansatz. Ich würde sagen, wenn Sie Daten speichern müssen, verwenden Sie eine Datenbank, kein Quellcode-Repository, das für eine ganz bestimmte Aufgabe ausgelegt ist. Wenn Sie Git sofort verwenden können, ist dies in Ordnung, aber Sie müssen wahrscheinlich eine Dokument-Repository-Ebene darüber erstellen. Sie können es also auch über eine herkömmliche Datenbank erstellen, oder? Und wenn es sich um eine integrierte Versionskontrolle handelt, an der Sie interessiert sind, warum nicht einfach eines der Open Source-Tools für das Dokument-Repository verwenden ? Es gibt viele zur Auswahl.

Wenn Sie sich trotzdem für das Git-Backend entscheiden, funktioniert es im Grunde genommen für Ihre Anforderungen, wenn Sie es wie beschrieben implementieren. Aber:

1) Sie haben "Cluster von Servern erwähnt, die sich gegenseitig drücken / ziehen" - ich habe eine Weile darüber nachgedacht und bin mir immer noch nicht sicher. Sie können nicht mehrere Repos als atomare Operation drücken / ziehen. Ich frage mich, ob es bei gleichzeitiger Arbeit die Möglichkeit eines Zusammenschlusses geben könnte.

2) Vielleicht brauchen Sie es nicht, aber eine offensichtliche Funktionalität eines Dokument-Repositorys, das Sie nicht aufgelistet haben, ist die Zugriffskontrolle. Möglicherweise können Sie den Zugriff auf einige Pfade (= Kategorien) über Submodule einschränken, aber wahrscheinlich können Sie den Zugriff auf Dokumentebene nicht einfach gewähren.


11

meine 2 Pence wert. Ein bisschen Sehnsucht, aber ...... Ich hatte eine ähnliche Anforderung in einem meiner Inkubationsprojekte. Ähnlich wie bei Ihnen waren meine wichtigsten Anforderungen eine Dokumentendatenbank (in meinem Fall XML) mit Dokumentversionierung. Es war für ein Mehrbenutzersystem mit vielen Anwendungsfällen für die Zusammenarbeit. Ich habe es vorgezogen, verfügbare OpenSource-Lösungen zu verwenden, die die meisten wichtigen Anforderungen erfüllen.

Um es auf den Punkt zu bringen: Ich konnte kein Produkt finden, das beides auf eine Weise bereitstellte, die skalierbar genug war (Anzahl der Benutzer, Nutzungsvolumen, Speicher- und Rechenressourcen). Ich war voreingenommen gegenüber Git für alle vielversprechenden Funktionen und (wahrscheinliche) Lösungen, die man daraus herstellen könnte. Da ich mehr mit der Git-Option spielte, wurde der Wechsel von einer Einzelbenutzerperspektive zu einer Mehrfachbenutzerperspektive (Milli) zu einer offensichtlichen Herausforderung. Leider konnte ich nicht wie Sie eine umfassende Leistungsanalyse durchführen. (.. faul / früh aufhören .... für Version 2, Mantra) Power to you!. Wie auch immer, meine voreingenommene Idee hat sich seitdem zur nächsten (immer noch voreingenommenen) Alternative gewandelt: einem Netz von Werkzeugen, die in ihren getrennten Bereichen, Datenbanken und Versionskontrolle die besten sind.

Während noch gearbeitet (... und leicht vernachlässigt), ist die verwandelte Version einfach dies.

  • im Frontend: (Userfacing) Verwenden Sie eine Datenbank für den Speicher der ersten Ebene (Schnittstelle zu Benutzeranwendungen).
  • Verwenden Sie im Backend ein Versionskontrollsystem (VCS) (wie git), um die Versionierung der Datenobjekte in der Datenbank durchzuführen

Im Wesentlichen würde es bedeuten, der Datenbank ein Versionskontroll-Plugin mit etwas Integrationskleber hinzuzufügen, den Sie möglicherweise entwickeln müssen, der jedoch viel einfacher sein kann.

Es würde (soll) funktionieren, dass der primäre Datenaustausch über mehrere Benutzeroberflächen über die Datenbank erfolgt. Das DBMS behandelt alle unterhaltsamen und komplexen Probleme wie Mehrbenutzer-, Parallelitäts-, Atomoperationen usw. Im Backend führt das VCS eine Versionskontrolle für einen einzelnen Satz von Datenobjekten durch (keine Parallelität oder Mehrbenutzerprobleme). Für jede effektive Transaktion in der Datenbank wird die Versionskontrolle nur für die Datensätze durchgeführt, die sich effektiv geändert hätten.

Der Schnittstellenkleber wird in Form einer einfachen Interworking-Funktion zwischen der Datenbank und dem VCS vorliegen. In Bezug auf das Design wäre ein einfacher Ansatz eine ereignisgesteuerte Schnittstelle, bei der Datenaktualisierungen aus der Datenbank die Versionskontrollprozeduren auslösen (Hinweis: Annahme von MySQL, Verwendung von Triggern und sys_exec () bla bla ...). In Bezug auf die Komplexität der Implementierung reicht es von einfach und effektiv (z. B. Skripterstellung) bis komplex und wunderbar (einige programmierte Connector-Schnittstellen). Alles hängt davon ab, wie verrückt Sie damit umgehen wollen und wie viel Schweißkapital Sie bereit sind, auszugeben. Ich denke, einfaches Scripting sollte die Magie bewirken. Um auf das Endergebnis, die verschiedenen Datenversionen, zuzugreifen, besteht eine einfache Alternative darin, einen Klon der Datenbank (eher einen Klon der Datenbankstruktur) mit den Daten zu füllen, auf die das Versions-Tag / id / hash im VCS verweist. Auch dieses Bit ist ein einfacher Abfrage- / Übersetzungs- / Zuordnungsjob einer Schnittstelle.

Es sind noch einige Herausforderungen und Unbekannte zu bewältigen, aber ich nehme an, dass die Auswirkungen und die Relevanz der meisten davon weitgehend von Ihren Anwendungsanforderungen und Anwendungsfällen abhängen. Einige sind möglicherweise keine Probleme. Einige der Probleme umfassen die Leistungsanpassung zwischen den beiden Schlüsselmodulen, der Datenbank und dem VCS, für eine Anwendung mit hochfrequenten Datenaktualisierungsaktivitäten, die Skalierung von Ressourcen (Speicher- und Verarbeitungsleistung) über die Zeit auf der Git-Seite als Daten und Benutzer wachsen: stetig, exponentiell oder schließlich Plateaus

Von dem Cocktail oben ist hier, was ich gerade braue

  • Verwenden von Git für das VCS (ursprünglich als gutes altes CVS für die Verwendung von nur Änderungssätzen oder Deltas zwischen 2 Versionen angesehen)
  • Verwenden von MySQL (aufgrund der stark strukturierten Natur meiner Daten, XML mit strengen XML-Schemata)
  • mit MongoDB herumspielen (um eine NoSQl-Datenbank auszuprobieren, die eng mit der in git verwendeten nativen Datenbankstruktur übereinstimmt)

Einige lustige Fakten - git macht tatsächlich klare Dinge, um die Speicherung zu optimieren, wie z. B. die Komprimierung und die Speicherung von nur Deltas zwischen der Revision von Objekten - JA, git speichert nur Änderungssätze oder Deltas zwischen Revisionen von Datenobjekten, wo dies anwendbar ist (es weiß wann und wie) . Referenz: Packdateien, tief im Bauch von Git-Interna - Die Überprüfung des Objektspeichers des Git (inhaltsadressierbares Dateisystem) zeigt auffallende Ähnlichkeiten (aus der Konzeptperspektive) mit noSQL-Datenbanken wie mongoDB. Auch hier kann es auf Kosten des Schweißkapitals interessantere Möglichkeiten für die Integration der 2 und die Leistungsoptimierung bieten

Wenn Sie so weit gekommen sind, lassen Sie mich wissen, ob das oben Gesagte auf Ihren Fall anwendbar ist und wie es zu einem Teil des Aspekts Ihrer letzten umfassenden Leistungsanalyse passen würde


4

Ich implementierte eine Ruby - Bibliothek oben auf libgit2dem diese leicht macht , ziemlich zu implementieren und zu erkunden. Es gibt einige offensichtliche Einschränkungen, aber es ist auch ein ziemlich befreiendes System, da Sie die vollständige Git-Toolchain erhalten.

Die Dokumentation enthält einige Ideen zu Leistung, Kompromissen usw.


2

Wie Sie bereits erwähnt haben, ist der Fall für mehrere Benutzer etwas schwieriger zu handhaben. Eine mögliche Lösung wäre die Verwendung benutzerspezifischer Git-Indexdateien

  • Keine separaten Arbeitskopien erforderlich (die Datenträgerverwendung ist auf geänderte Dateien beschränkt).
  • Keine zeitaufwändigen Vorarbeiten erforderlich (pro Benutzersitzung)

Der Trick besteht darin, die GIT_INDEX_FILEUmgebungsvariable von Git mit den Tools zum manuellen Erstellen von Git-Commits zu kombinieren :

Es folgt eine Lösungsübersicht (tatsächliche SHA1-Hashes in den Befehlen weggelassen):

# Initialize the index
# N.B. Use the commit hash since refs might changed during the session.
$ GIT_INDEX_FILE=user_index_file git reset --hard <starting_commit_hash>

#
# Change data and save it to `changed_file`
#

# Save changed data to the Git object database. Returns a SHA1 hash to the blob.
$ cat changed_file | git hash-object -t blob -w --stdin
da39a3ee5e6b4b0d3255bfef95601890afd80709

# Add the changed file (using the object hash) to the user-specific index
# N.B. When adding new files, --add is required
$ GIT_INDEX_FILE=user_index_file git update-index --cacheinfo 100644 <changed_data_hash> path/to/the/changed_file

# Write the index to the object db. Returns a SHA1 hash to the tree object
$ GIT_INDEX_FILE=user_index_file git write-tree
8ea32f8432d9d4fa9f9b2b602ec7ee6c90aa2d53

# Create a commit from the tree. Returns a SHA1 hash to the commit object
# N.B. Parent commit should the same commit as in the first phase.
$ echo "User X updated their data" | git commit-tree <new_tree_hash> -p <starting_commit_hash>
3f8c225835e64314f5da40e6a568ff894886b952

# Create a ref to the new commit
git update-ref refs/heads/users/user_x_change_y <new_commit_hash>

Abhängig von Ihren Daten könnten Sie einen Cron-Job verwenden, um die neuen Refs zusammenzuführen, masteraber die Konfliktlösung ist hier wohl der schwierigste Teil.

Ideen zur Vereinfachung sind willkommen.


Dies ist im Allgemeinen ein Ansatz, der nirgendwohin führt, es sei denn, Sie möchten ein umfassendes Konzept für Transaktionen und Benutzeroberflächen zur manuellen Konfliktlösung haben. Die allgemeine Idee für Konflikte besteht darin, den Benutzer dazu zu bringen, sie direkt beim Festschreiben zu lösen (dh "Entschuldigung, jemand anderes hat das von Ihnen bearbeitete Dokument bearbeitet -> sehen Sie sich seine Änderungen und Ihre Änderungen an und führen Sie sie zusammen"). Wenn Sie zwei Benutzern erlauben, sich erfolgreich zu verpflichten, und dann in einem asynchronen Cronjob herausfinden, dass die Dinge nach Süden gegangen sind, ist im Allgemeinen niemand verfügbar, um Probleme zu lösen.
GreyCat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.