Au cours du mois dernier, j'ai travaillé activement sur des projets de validation de principe liés à CouchDB, explorant ses fonctionnalités et préparant les tâches futures. Au cours de cette période, j'ai parcouru la documentation CouchDB plusieurs fois pour m'assurer de comprendre comment tout fonctionne. En lisant la documentation, je suis tombé sur une déclaration selon laquelle, malgré la livraison de CouchDB avec un serveur de requêtes par défaut écrit en JavaScript, la création d'une implémentation personnalisée est relativement simple et des solutions personnalisées existent déjà dans la nature.
J'ai fait quelques recherches rapides et trouvé des implémentations écrites en Python, Ruby ou Clojure. Comme l’implémentation entière ne m’a pas semblé trop longue, j’ai décidé d’expérimenter CouchDB en essayant d’écrire mon propre serveur de requêtes personnalisé. Pour ce faire, j'ai choisi Go comme langage. Je n'ai pas eu beaucoup d'expérience avec ce langage auparavant, à l'exception de l'utilisation de modèles Go dans les graphiques Helm, mais je voulais essayer quelque chose de nouveau et j'ai pensé que ce projet serait une excellente opportunité pour cela.
Avant de commencer le travail, j'ai revisité la documentation de CouchDB une fois de plus pour comprendre comment fonctionne réellement le serveur de requêtes. Selon la documentation, la présentation générale du serveur de requêtes est assez simple :
Le serveur de requête est un processus externe qui communique avec CouchDB via le protocole JSON via une interface stdio et gère tous les appels de fonctions de conception […].
La structure des commandes envoyées par CouchDB au serveur de requête peut être exprimée sous la forme [
Donc, fondamentalement, ce que je devais faire était d'écrire une application capable d'analyser ce type de JSON à partir de STDIO, d'effectuer les opérations attendues et de renvoyer les réponses comme spécifié dans la documentation. Il y avait beaucoup de conversion de type impliquée pour gérer un large éventail de commandes dans le code Go. Des détails spécifiques sur chaque commande peuvent être trouvés dans la section Protocole du serveur de requête de la documentation.
Un problème auquel j'ai été confronté ici était que le serveur de requêtes devait être capable d'interpréter et d'exécuter du code arbitraire fourni dans les documents de conception. Sachant que Go est un langage compilé, je m'attendais à être bloqué à ce stade. Heureusement, j'ai rapidement trouvé le package Yeagi, capable d'interpréter facilement le code Go. Il permet de créer un bac à sable et de contrôler l'accès aux packages pouvant être importés dans le code interprété. Dans mon cas, j'ai décidé d'exposer uniquement mon package appelé couchgo, mais d'autres packages standard peuvent également être facilement ajoutés.
Grâce à mon travail, une application appelée CouchGO! émergé. Bien qu'il suive le protocole Query Server, il ne s'agit pas d'une réimplémentation individuelle de la version JavaScript car il a ses propres approches pour gérer les fonctions des documents de conception.
Par exemple, dans CouchGO !, il n'y a pas de fonction d'assistance comme émettre. Pour émettre des valeurs, vous les renvoyez simplement depuis la fonction map. De plus, chaque fonction du document de conception suit le même modèle : elle n'a qu'un seul argument, qui est un objet contenant des propriétés spécifiques à la fonction, et est censée renvoyer une seule valeur en conséquence. Il n'est pas nécessaire que cette valeur soit une primitive ; selon la fonction, il peut s'agir d'un objet, d'une carte ou même d'une erreur.
Pour commencer à travailler avec CouchGO !, il vous suffit de télécharger le binaire exécutable depuis mon référentiel GitHub, de le placer quelque part dans l'instance CouchDB et d'ajouter une variable d'environnement qui permet à CouchDB de démarrer CouchGO ! processus.
Par exemple, si vous placez l'exécutable couchgo dans le répertoire /opt/couchdb/bin, vous ajouterez la variable d'environnement suivante pour lui permettre de fonctionner.
export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"
Pour comprendre rapidement comment écrire des fonctions avec CouchGO !, explorons l'interface de fonction suivante :
func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }
Chaque fonction de CouchGO! suivra ce modèle, où Func est remplacé par le nom de fonction approprié. Actuellement, CouchGO! prend en charge les types de fonctions suivants :
Examinons un exemple de document de conception qui spécifie une vue avec des fonctions de mappage et de réduction, ainsi qu'une fonction validate_doc_update. De plus, nous devons préciser que nous utilisons Go comme langage.
{ "_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" }
Maintenant, décomposons chaque fonction en commençant par la fonction map :
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 }
Dans CouchGO !, il n'y a pas de fonction d'émission ; à la place, vous renvoyez une tranche de tuples clé-valeur où la clé et la valeur peuvent être de n'importe quel type. L'objet document n'est pas directement transmis à la fonction comme en JavaScript ; il est plutôt enveloppé dans un objet. Le document lui-même est simplement une table de hachage de différentes valeurs.
Ensuite, examinons la fonction de réduction :
func Reduce(args couchgo.ReduceInput) couchgo.ReduceOutput { out := 0.0 for _, value := range args.Values { out = value.(float64) } return out }
Semblable à JavaScript, la fonction de réduction de CouchGO! prend des clés, des valeurs et un paramètre de réduction, le tout regroupé dans un seul objet. Cette fonction doit renvoyer une valeur unique de n'importe quel type qui représente le résultat de l'opération de réduction.
Enfin, regardons la fonction Validate, qui correspond à la propriété validate_doc_update :
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 }
Dans cette fonction, nous recevons des paramètres tels que le nouveau document, l'ancien document, le contexte utilisateur et l'objet de sécurité, le tout regroupé dans un seul objet transmis comme argument de fonction. Ici, nous devons valider si le document peut être mis à jour et renvoyer une erreur dans le cas contraire. Semblable à la version JavaScript, nous pouvons renvoyer deux types d'erreurs : ForbiddenError ou UnauthorizedError. Si le document peut être mis à jour, nous devrions renvoyer nil.
Pour des exemples plus détaillés, ils peuvent être trouvés dans mon référentiel GitHub. Une chose importante à noter est que les noms de fonctions ne sont pas arbitraires ; ils doivent toujours correspondre au type de fonction qu'ils représentent, tel que Map, Réduire, Filtrer, etc.
Même si écrire mon propre serveur de requêtes a été une expérience vraiment amusante, cela n'aurait pas beaucoup de sens si je ne le comparais pas aux solutions existantes. J'ai donc préparé quelques tests simples dans un conteneur Docker pour vérifier à quel point CouchGO! peut:
J'ai ensemencé la base de données avec le nombre attendu de documents et mesuré les temps de réponse ou les journaux d'horodatage différenciés du conteneur Docker à l'aide de scripts shell dédiés. Les détails de l'implémentation peuvent être trouvés dans mon référentiel GitHub. Les résultats sont présentés dans le tableau ci-dessous.
Test | CanapéGO ! | CoucheJS | Booster |
---|---|---|---|
Indexage | 141,713s | 421,529s | 2,97x |
Réduire | 7672 ms | 15642 ms | 2,04x |
Filtration | 28,928s | 80,594s | 2,79x |
Mise à jour | 7,742s | 9,661 s | 1,25x |
Comme vous pouvez le constater, l'amélioration par rapport à l'implémentation de JavaScript est significative : presque trois fois plus rapide dans le cas de l'indexation, plus de deux fois plus rapide pour les fonctions de réduction et de filtrage. Le boost est relativement faible pour les fonctions de mise à jour, mais reste plus rapide que JavaScript.
Comme l'auteur de la documentation l'a promis, écrire un serveur de requêtes personnalisé n'était pas si difficile en suivant le protocole du serveur de requêtes. Même si CouchGO! En général, il lui manque quelques fonctions obsolètes, mais il offre un avantage significatif par rapport à la version JavaScript, même à ce stade précoce de développement. Je pense qu'il y a encore beaucoup de place à l'amélioration.
Si vous avez besoin de tout le code de cet article en un seul endroit, vous pouvez le trouver dans mon référentiel GitHub.
Merci d'avoir lu cet article. J'aimerais connaître votre avis sur cette solution. L'utiliseriez-vous avec votre instance CouchDB, ou peut-être utilisez-vous déjà un serveur de requêtes personnalisé ? J'apprécierais d'en entendre parler dans les commentaires.
N'oubliez pas de consulter mes autres articles pour plus de conseils, d'informations et d'autres parties de cette série au fur et à mesure de leur création. Bon piratage !
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3