カウチGO! — Go で書かれた Query Server による CouchDB の強化

PHPz
リリース: 2024-07-19 12:38:41
オリジナル
590 人が閲覧しました

CouchGO! — Enhancing CouchDB with Query Server Written in Go

この 1 か月間、私は CouchDB に関連する概念実証プロジェクトに積極的に取り組み、その機能を調査し、将来のタスクに備えてきました。この期間中、私は CouchDB のドキュメントを何度も読み、すべてがどのように機能するかを確実に理解しました。ドキュメントを読んでいると、CouchDB には JavaScript で書かれたデフォルトの Query Server が同梱されているにもかかわらず、カスタム実装の作成は比較的簡単で、カスタム ソリューションはすでに世の中に存在しているという記述を見つけました。

簡単に調査したところ、Python、Ruby、または Clojure で書かれた実装が見つかりました。実装全体はそれほど長くないようだったので、独自のカスタム Query Server を作成して CouchDB を実験することにしました。これを行うために、言語として Go を選択しました。 Helm のチャートで Go テンプレートを使用することを除いて、これまでこの言語をあまり使用した経験がありませんでしたが、何か新しいことを試してみたいと思い、このプロジェクトはその素晴らしい機会になると思いました。

クエリサーバーについて

作業を開始する前に、Query Server が実際にどのように動作するかを理解するために、CouchDB のドキュメントをもう一度見直しました。ドキュメントによると、Query Server の概要は非常に簡単です。

Query サーバーは、stdio インターフェイス上の JSON プロトコルを介して CouchDB と通信し、すべてのデザイン関数呼び出しを処理する外部プロセスです […]。

CouchDB によって Query Server に送信されるコマンドの構造は、[, <*arguments>] または ["ddoc", , [, < funcname>], [, , …]] (設計ドキュメントの場合)

基本的に、私がしなければならなかったのは、STDIO からこの種の JSON を解析し、期待される操作を実行し、ドキュメントで指定されている応答を返すことができるアプリケーションを作成することでした。 Go コードでさまざまなコマンドを処理するには、多くの型キャストが必要でした。各コマンドの具体的な詳細については、ドキュメントの「クエリ サーバー プロトコル」セクションを参照してください。

ここで私が直面した問題の 1 つは、クエリ サーバーが設計ドキュメントで提供された任意のコードを解釈して実行できる必要があるということでした。 Go がコンパイル言語であることを知っていたので、この時点で行き詰まるだろうと予想していました。ありがたいことに、Go コードを簡単に解釈できる Yeagi パッケージをすぐに見つけました。これにより、サンドボックスを作成し、解釈されたコードでインポートできるパッケージへのアクセスを制御できます。私の場合、couchgo というパッケージのみを公開することにしましたが、他の標準パッケージも簡単に追加できます。

CouchGOのご紹介です!

私の仕事の結果、CouchGO! というアプリケーションが完成しました。が現れた。これはクエリ サーバー プロトコルに従っていますが、設計ドキュメントの関数を処理するための独自のアプローチがあるため、JavaScript バージョンを 1 対 1 で再実装するものではありません。

たとえば、CouchGO! には、emit のようなヘルパー関数はありません。値を出力するには、map 関数から値を返すだけです。さらに、設計ドキュメント内の各関数は同じパターンに従います。関数固有のプロパティを含むオブジェクトである引数が 1 つだけあり、結果として 1 つの値のみを返すことになっています。この値はプリミティブである必要はありません。関数によっては、オブジェクト、マップ、またはエラーの場合もあります。

CouchGO! の使用を開始するには、GitHub リポジトリから実行可能バイナリをダウンロードし、CouchDB インスタンスのどこかに配置し、CouchDB が CouchGO! を開始できるようにする環境変数を追加するだけです。プロセス。

たとえば、couchgo 実行可能ファイルを /opt/couchdb/bin ディレクトリに配置する場合、それが機能するように次の環境変数を追加します。

export COUCHDB_QUERY_SERVER_GO="/opt/couchdb/bin/couchgo"
ログイン後にコピー

CouchGO で関数を書く!

CouchGO! で関数を記述する方法を簡単に理解するために、次の関数インターフェイスを見てみましょう:

func Func(args couchgo.FuncInput) couchgo.FuncOutput { ... }
ログイン後にコピー

CouchGOの各機能!はこのパターンに従い、Func が適切な関数名に置き換えられます。現在、CouchGO!は次の関数タイプをサポートしています:

  • 地図
  • 減らす
  • フィルター
  • アップデート
  • 検証 (validate_doc_update)

map 関数とreduce 関数、および validate_doc_update 関数を使用してビューを指定するサンプル デザイン ドキュメントを調べてみましょう。さらに、言語として Go を使用していることを指定する必要があります。

{
  "_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"
}
ログイン後にコピー

ここで、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
}
ログイン後にコピー

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.

CouchGO! Performance

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:

  • Index 100k documents (indexing in CouchDB means executing map functions from views)
  • Execute reduce function for 100k documents
  • Filter change feed for 100k documents
  • Perform update function for 1k requests

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.

Conclusion

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!

以上がカウチGO! — Go で書かれた Query Server による CouchDB の強化の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート