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 stieß ich auf die Aussage, 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 erschien, 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.
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 [
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.
Als Ergebnis meiner Arbeit ist eine Anwendung namens CouchGO! entstanden. Obwohl es dem Query Server Protocol folgt, handelt es sich nicht um eine Eins-zu-eins-Reimplementierung 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; Je nach 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. Prozess.
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"
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:
Sehen wir uns ein Beispiel-Designdokument an, das eine Ansicht mit Karten- 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!, there is no emit function; instead, you return a slice of key-value tuples where both key and value can be of any type. The document object isn't directly passed to the function as in JavaScript; rather, it's wrapped in an object. The document itself is simply a hashmap of various values.
Next, let’s examine the reduce function:
func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput { out := 0.0 for _, value := range args.Values { out += value.(float64) } return out }
Similar to JavaScript, the reduce function in CouchGO! takes keys, values, and a rereduce parameter, all wrapped into a single object. This function should return a single value of any type that represents the result of the reduction operation.
Finally, let’s look at the Validate function, which corresponds to the validate_doc_update property:
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 this function, we receive parameters such as the new document, old document, user context, and security object, all wrapped into one object passed as a function argument. Here, we’re expected to validate if the document can be updated and return an error if not. Similar to the JavaScript version, we can return two types of errors: ForbiddenError or UnauthorizedError. If the document can be updated, we should return nil.
For more detailed examples, they can be found in my GitHub repository. One important thing to note is that the function names are not arbitrary; they should always match the type of function they represent, such as Map, Reduce, Filter, etc.
Even though writing my own Query Server was a really fun experience, it wouldn’t make much sense if I didn’t compare it with existing solutions. So, I prepared a few simple tests in a Docker container to check how much faster CouchGO! can:
I seeded the database with the expected number of documents and measured response times or differentiated timestamp logs from the Docker container using dedicated shell scripts. The details of the implementation can be found in my GitHub repository. The results are presented in the table below.
Test | CouchGO! | CouchJS | Boost |
---|---|---|---|
Indexing | 141.713s | 421.529s | 2.97x |
Reducing | 7672ms | 15642ms | 2.04x |
Filtering | 28.928s | 80.594s | 2.79x |
Updating | 7.742s | 9.661s | 1.25x |
As you can see, the boost over the JavaScript implementation is significant: almost three times faster in the case of indexing, more than twice as fast for reduce and filter functions. The boost is relatively small for update functions, but still faster than JavaScript.
As the author of the documentation promised, writing a custom Query Server wasn’t that hard when following the Query Server Protocol. Even though CouchGO! lacks a few deprecated functions in general, it provides a significant boost over the JavaScript version even at this early stage of development. I believe there is still plenty of room for improvements.
If you need all the code from this article in one place, you can find it in my GitHub repository.
Thank you for reading this article. I would love to hear your thoughts about this solution. Would you use it with your CouchDB instance, or maybe you already use some custom-made Query Server? I would appreciate hearing about it in the comments.
Don’t forget to check out my other articles for more tips, insights, and other parts of this series as they are created. Happy hacking!
Das obige ist der detaillierte Inhalt vonCouchGO! – Erweitern von CouchDB mit einem in Go geschriebenen Abfrageserver. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!