„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > CouchGO! – Erweitern von CouchDB mit einem in Go geschriebenen Abfrageserver

CouchGO! – Erweitern von CouchDB mit einem in Go geschriebenen Abfrageserver

Veröffentlicht am 06.08.2024
Durchsuche:937

CouchGO! — Enhancing CouchDB with Query Server Written in Go

Im letzten Monat habe ich aktiv an Proof-of-Concept-Projekten im Zusammenhang mit CouchDB gearbeitet, seine Funktionen erkundet und mich auf zukünftige Aufgaben vorbereitet. In dieser Zeit habe ich die CouchDB-Dokumentation mehrmals durchgesehen, um sicherzustellen, dass ich verstehe, wie alles funktioniert. Beim Durchlesen der Dokumentation bin ich auf die Aussage gestoßen, dass die Erstellung einer benutzerdefinierten Implementierung relativ einfach ist, obwohl CouchDB mit einem in JavaScript geschriebenen Standard-Abfrageserver ausgeliefert wird und es bereits benutzerdefinierte Lösungen gibt.

Ich habe schnell recherchiert und Implementierungen gefunden, die in Python, Ruby oder Clojure geschrieben sind. Da die gesamte Implementierung nicht allzu langwierig schien, habe ich beschlossen, mit CouchDB zu experimentieren und zu versuchen, meinen eigenen benutzerdefinierten Abfrageserver zu schreiben. Dazu habe ich Go als Sprache gewählt. Ich hatte bisher nicht viel Erfahrung mit dieser Sprache, außer mit der Verwendung von Go-Vorlagen in Helms Diagrammen, aber ich wollte etwas Neues ausprobieren und dachte, dieses Projekt wäre eine großartige Gelegenheit dafür.

Den Abfrageserver verstehen

Bevor ich mit der Arbeit begann, habe ich mir die CouchDB-Dokumentation noch einmal angesehen, um zu verstehen, wie der Abfrageserver tatsächlich funktioniert. Der Dokumentation zufolge ist der allgemeine Überblick über den Abfrageserver recht einfach:

Der Abfrageserver ist ein externer Prozess, der über das JSON-Protokoll über eine stdio-Schnittstelle mit CouchDB kommuniziert und alle Design-Funktionsaufrufe abwickelt […].

Die Struktur der von CouchDB an den Abfrageserver gesendeten Befehle kann ausgedrückt werden als [, ] oder ["ddoc", , [, ], [ , , …]] im Fall von Designdokumenten.

Im Grunde musste ich also eine Anwendung schreiben, die in der Lage ist, diese Art von JSON von STDIO zu analysieren, die erwarteten Vorgänge auszuführen und Antworten wie in der Dokumentation angegeben zurückzugeben. Es war viel Typumwandlung erforderlich, um eine breite Palette von Befehlen im Go-Code zu verarbeiten. Spezifische Details zu jedem Befehl finden Sie im Abschnitt „Query Server Protocol“ der Dokumentation.

Ein Problem, mit dem ich hier konfrontiert war, war, dass der Abfrageserver in der Lage sein sollte, beliebigen in Designdokumenten bereitgestellten Code zu interpretieren und auszuführen. Da ich wusste, dass Go eine kompilierte Sprache ist, erwartete ich, an diesem Punkt stecken zu bleiben. Zum Glück habe ich schnell das Yeagi-Paket gefunden, das Go-Code problemlos interpretieren kann. Es ermöglicht die Erstellung einer Sandbox und die Steuerung des Zugriffs darauf, welche Pakete in den interpretierten Code importiert werden können. In meinem Fall habe ich beschlossen, nur mein Paket namens Couchgo verfügbar zu machen, aber auch andere Standardpakete können problemlos hinzugefügt werden.

Wir stellen CouchGO vor!

Als Ergebnis meiner Arbeit entstand eine Anwendung namens CouchGO! aufgetaucht. Obwohl es dem Query Server Protocol folgt, handelt es sich nicht um eine Eins-zu-eins-Neuimplementierung der JavaScript-Version, da es über eigene Ansätze für die Handhabung von Designdokumentfunktionen verfügt.

Zum Beispiel gibt es in CouchGO! keine Hilfsfunktion wie „emit“. Um Werte auszugeben, geben Sie sie einfach von der Kartenfunktion zurück. Darüber hinaus folgt jede Funktion im Designdokument demselben Muster: Sie verfügt nur über ein Argument, bei dem es sich um ein Objekt handelt, das funktionsspezifische Eigenschaften enthält, und soll als Ergebnis nur einen Wert zurückgeben. Dieser Wert muss kein Grundwert sein; Abhängig von der Funktion kann es sich um ein Objekt, eine Karte oder sogar um einen Fehler handeln.

Um mit CouchGO! zu arbeiten, müssen Sie lediglich die ausführbare Binärdatei aus meinem GitHub-Repository herunterladen, sie irgendwo in der CouchDB-Instanz platzieren und eine Umgebungsvariable hinzufügen, die es CouchDB ermöglicht, CouchGO! zu starten. Verfahren.

Wenn Sie beispielsweise die ausführbare Couchgo-Datei im Verzeichnis /opt/couchdb/bin ablegen, würden Sie die folgende Umgebungsvariable hinzufügen, damit sie funktioniert.

export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"

Schreiben von Funktionen mit CouchGO!

Um schnell zu verstehen, wie man mit CouchGO! Funktionen schreibt, schauen wir uns die folgende Funktionsschnittstelle an:

func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }

Jede Funktion in CouchGO! folgt diesem Muster, wobei Func durch den entsprechenden Funktionsnamen ersetzt wird. Derzeit ist CouchGO! unterstützt die folgenden Funktionstypen:

  • Karte
  • Reduzieren
  • Filter
  • Aktualisieren
  • Validieren (validate_doc_update)

Sehen wir uns ein Beispiel-Designdokument an, das eine Ansicht mit Zuordnungs- und Reduzierungsfunktionen sowie eine Funktion „validate_doc_update“ angibt. Zusätzlich müssen wir angeben, dass wir Go als Sprache verwenden.

{
  "_id": "_design/ddoc-go",
  "views": {
    "view": {
      "map": "func Map(args couchgo.MapInput) couchgo.MapOutput {\n\tout := couchgo.MapOutput{}\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 1})\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 2})\n\tout = append(out, [2]interface{}{args.Doc[\"_id\"], 3})\n\t\n\treturn out\n}",
      "reduce": "func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput {\n\tout := 0.0\n\n\tfor _, value := range args.Values {\n\t\tout  = value.(float64)\n\t}\n\n\treturn out\n}"
    }
  },
  "validate_doc_update": "func Validate(args couchgo.ValidateInput) couchgo.ValidateOutput {\n\tif args.NewDoc[\"type\"] == \"post\" {\n\t\tif args.NewDoc[\"title\"] == nil || args.NewDoc[\"content\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Title and content are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif args.NewDoc[\"type\"] == \"comment\" {\n\t\tif args.NewDoc[\"post\"] == nil || args.NewDoc[\"author\"] == nil || args.NewDoc[\"content\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Post, author, and content are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif args.NewDoc[\"type\"] == \"user\" {\n\t\tif args.NewDoc[\"username\"] == nil || args.NewDoc[\"email\"] == nil {\n\t\t\treturn couchgo.ForbiddenError{Message: \"Username and email are required\"}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn couchgo.ForbiddenError{Message: \"Invalid document type\"}\n}",
  "language": "go"
}

Lassen Sie uns nun jede Funktion aufschlüsseln, beginnend mit der Kartenfunktion:

func Map(args couchgo.MapInput) couchgo.MapOutput {
  out := couchgo.MapOutput{}
  out = append(out, [2]interface{}{args.Doc["_id"], 1})
  out = append(out, [2]interface{}{args.Doc["_id"], 2})
  out = append(out, [2]interface{}{args.Doc["_id"], 3})

  return out
}

In CouchGO! gibt es keine Emit-Funktion; Stattdessen geben Sie ein Stück Schlüsselwert-Tupel zurück, wobei sowohl Schlüssel als auch Wert von jedem beliebigen Typ sein können. Das Dokumentobjekt wird nicht wie in JavaScript direkt an die Funktion übergeben; Vielmehr ist es in ein Objekt eingewickelt. Das Dokument selbst ist einfach eine Hashmap verschiedener Werte.

Als nächstes untersuchen wir die Reduzierungsfunktion:

func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput {
  out := 0.0
  for _, value := range args.Values {
    out  = value.(float64)
  }
  return out
}

Ähnlich wie JavaScript ist die Reduce-Funktion in CouchGO! akzeptiert Schlüssel, Werte und einen Rereduce-Parameter, alles verpackt in einem einzigen Objekt. Diese Funktion sollte einen einzelnen Wert eines beliebigen Typs zurückgeben, der das Ergebnis der Reduktionsoperation darstellt.

Schauen wir uns abschließend noch die Funktion „Validate“ an, die der Eigenschaft „validate_doc_update“ entspricht:

func Validate(args couchgo.ValidateInput) couchgo.ValidateOutput {
  if args.NewDoc["type"] == "post" {
    if args.NewDoc["title"] == nil || args.NewDoc["content"] == nil {
      return couchgo.ForbiddenError{Message: "Title and content are required"}
    }

    return nil
  }

  if args.NewDoc["type"] == "comment" {
    if args.NewDoc["post"] == nil || args.NewDoc["author"] == nil || args.NewDoc["content"] == nil {
      return couchgo.ForbiddenError{Message: "Post, author, and content are required"}
    }

    return nil
  }

  return nil
}

In dieser Funktion erhalten wir Parameter wie das neue Dokument, das alte Dokument, den Benutzerkontext und das Sicherheitsobjekt, alle verpackt in einem Objekt, das als Funktionsargument übergeben wird. Hier wird von uns erwartet, dass wir überprüfen, ob das Dokument aktualisiert werden kann, und andernfalls einen Fehler zurückgeben. Ähnlich wie bei der JavaScript-Version können wir zwei Arten von Fehlern zurückgeben: ForbiddenError oder UnauthorizedError. Wenn das Dokument aktualisiert werden kann, sollten wir Null zurückgeben.

Ausführlichere Beispiele finden Sie in meinem GitHub-Repository. Es ist wichtig zu beachten, dass die Funktionsnamen nicht willkürlich sind; Sie sollten immer mit der Art der Funktion übereinstimmen, die sie darstellen, z. B. Zuordnen, Reduzieren, Filtern usw.

CouchGO! Leistung

Auch wenn das Schreiben meines eigenen Abfrageservers eine wirklich unterhaltsame Erfahrung war, würde es wenig Sinn ergeben, wenn ich ihn nicht mit vorhandenen Lösungen vergleichen würde. Deshalb habe ich ein paar einfache Tests in einem Docker-Container vorbereitet, um zu überprüfen, wie viel schneller CouchGO! dürfen:

  • Indizieren von 100.000 Dokumenten (Indizieren in CouchDB bedeutet das Ausführen von Kartenfunktionen aus Ansichten)
  • Reduzierungsfunktion für 100.000 Dokumente ausführen
  • Änderungsfeed für 100.000 Dokumente filtern
  • Aktualisierungsfunktion für 1.000 Anfragen durchführen

Ich habe die Datenbank mit der erwarteten Anzahl von Dokumenten gesät und Antwortzeiten oder differenzierte Zeitstempelprotokolle aus dem Docker-Container mithilfe spezieller Shell-Skripte gemessen. Die Details der Implementierung finden Sie in meinem GitHub-Repository. Die Ergebnisse sind in der folgenden Tabelle dargestellt.

Prüfen CouchGO! CouchJS Schub
Indizierung 141.713s 421,529s 2,97x
Reduzierung 7672ms 15642ms 2,04x
Filtern 28,928s 80,594s 2,79x
Aktualisierung 7.742s 9,661s 1,25x

Wie Sie sehen, ist die Steigerung gegenüber der JavaScript-Implementierung erheblich: fast dreimal schneller bei der Indizierung, mehr als doppelt so schnell bei Reduzierungs- und Filterfunktionen. Der Boost ist für Update-Funktionen relativ gering, aber immer noch schneller als bei JavaScript.

Abschluss

Wie der Autor der Dokumentation versprochen hat, war das Schreiben eines benutzerdefinierten Abfrageservers nicht so schwierig, wenn man dem Query Server-Protokoll folgte. Auch wenn CouchGO! Obwohl es im Allgemeinen an einigen veralteten Funktionen mangelt, bietet es bereits in diesem frühen Entwicklungsstadium einen erheblichen Fortschritt gegenüber der JavaScript-Version. Ich glaube, dass es noch viel Raum für Verbesserungen gibt.

Wenn Sie den gesamten Code aus diesem Artikel an einem Ort benötigen, finden Sie ihn in meinem GitHub-Repository.

Vielen Dank, dass Sie diesen Artikel gelesen haben. Ich würde gerne Ihre Meinung zu dieser Lösung hören. Würden Sie es mit Ihrer CouchDB-Instanz verwenden oder verwenden Sie möglicherweise bereits einen maßgeschneiderten Abfrageserver? Ich würde mich freuen, in den Kommentaren davon zu hören.

Vergessen Sie nicht, sich meine anderen Artikel anzusehen, um weitere Tipps, Einblicke und andere Teile dieser Serie zu erhalten, sobald sie erstellt werden. Viel Spaß beim Hacken!

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/kishieel/couchgo-enhancing-couchdb-with-query-server-scribed-in-go-304n?1 Bei Verstößen wenden Sie sich bitte an [email protected] um es zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3