」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 沙發GO! — 使用 Go 編寫的查詢伺服器增強 CouchDB

沙發GO! — 使用 Go 編寫的查詢伺服器增強 CouchDB

發佈於2024-08-06
瀏覽:463

CouchGO! — Enhancing CouchDB with Query Server Written in Go

在过去的一个月里,我一直在积极从事与 CouchDB 相关的概念验证项目,探索其功能并为未来的任务做准备。在此期间,我多次阅读了 CouchDB 文档,以确保我了解一切是如何工作的。在阅读文档时,我发现了这样的说法:尽管 CouchDB 附带了用 JavaScript 编写的默认查询服务器,但创建自定义实现相对简单,并且自定义解决方案已经存在。

我做了一些快速研究,发现了用 Python、Ruby 或 Clojure 编写的实现。由于整个实现看起来并不太长,因此我决定通过尝试编写自己的自定义查询服务器来尝试 CouchDB。为此,我选择 Go 作为语言。除了在 Helm 图表中使用 Go 模板之外,我之前对这种语言没有太多经验,但我想尝试一些新东西,并认为这个项目将是一个很好的机会。

了解查询服务器

在开始工作之前,我再次回顾了 CouchDB 文档,以了解查询服务器的实际工作原理。根据文档,查询服务器的高级概述非常简单:

查询服务器是一个外部进程,它通过 stdio 接口通过 JSON 协议与 CouchDB 进行通信,并处理所有设计函数调用 [...]。

CouchDB 发送到查询服务器的命令结构可以表示为 [, ] 或 ["ddoc", , [, ], [ , , …]] 在设计文档的情况下。

所以基本上,我要做的就是编写一个能够从 STDIO 解析此类 JSON、执行预期操作并返回文档中指定的响应的应用程序。 Go 代码中涉及大量类型转换来处理各种命令。有关每个命令的具体详细信息可以在文档的查询服务器协议部分找到。

我在这里遇到的一个问题是查询服务器应该能够解释和执行设计文档中提供的任意代码。知道 Go 是一种编译语言,我预计会在这一点上陷入困​​境。值得庆幸的是,我很快就找到了 Yeagi 包,它能够轻松解释 Go 代码。它允许创建沙箱并控制对可以在解释代码中导入的包的访问。就我而言,我决定仅公开我的名为 couchgo 的包,但也可以轻松添加其他标准包。

介绍 CouchGO!

作为我工作的成果,开发了一个名为 CouchGO! 的应用程序!出现了。尽管它遵循查询服务器协议,但它不是 JavaScript 版本的一对一重新实现,因为它有自己的方法来处理设计文档功能。

例如,在CouchGO!中,没有像emit这样的辅助函数。要发出值,您只需从映射函数返回它们即可。此外,设计文档中的每个函数都遵循相同的模式:它只有一个参数,该参数是一个包含特定于函数的属性的对象,并且应该只返回一个值作为结果。该值不必是原始值;根据函数的不同,它可能是一个对象、一个映射,甚至是一个错误。

要开始使用 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
}

在CouchGO!中,没有emit函数;相反,您返回一个键值元组切片,其中键和值可以是任何类型。文档对象并不像 JavaScript 中那样直接传递给函数;而是直接传递给函数。相反,它被包裹在一个对象中。文档本身只是各种值的哈希图。

接下来我们来看看reduce函数:

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

与JavaScript类似,CouchGO中的reduce函数!接受键、值和 rereduce 参数,所有这些都包装在一个对象中。此函数应返回表示归约运算结果的任何类型的单个值。

最后我们看一下Validate函数,它对应的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
}

在此函数中,我们接收新文档、旧文档、用户上下文和安全对象等参数,所有这些参数都包装到作为函数参数传递的一个对象中。在这里,我们需要验证文档是否可以更新,如果不能更新则返回错误。与 JavaScript 版本类似,我们可以返回两种类型的错误:ForbiddenError 或 UnauthorizedError。如果文档可以更新,我们应该返回nil。

有关更详细的示例,可以在我的 GitHub 存储库中找到。需要注意的一件重要事情是函数名称不是任意的;它们应该始终匹配它们所代表的函数类型,例如 Map、Reduce、Filter 等。

沙发GO!表现

尽管编写自己的查询服务器是一种非常有趣的体验,但如果我不将其与现有解决方案进行比较,那就没有多大意义。因此,我在 Docker 容器中准备了一些简单的测试,以检查 CouchGO 的速度有多快!能:

  • 索引 100k 文档(CouchDB 中的索引意味着从视图执行映射函数)
  • 对100k个文档执行reduce函数
  • 过滤 10 万个文档的更改源
  • 对1k个请求执行更新功能

我使用专用 shell 脚本将预期数量的文档植入数据库,并测量响应时间或区分 Docker 容器的时间戳日志。实现的详细信息可以在我的 GitHub 存储库中找到。结果如下表所示。

测试 CouchGO! CouchJS 促进
索引 141.713s 421.529s 2.97x
减少 7672ms 15642ms 2.04x
过滤 28.928s 80.594s 2.79x
更新中 7.742s 9.661s 1.25x

正如您所看到的,JavaScript 实现的提升是显着的:索引的速度几乎是原来的三倍,reduce 和过滤函数的速度是原来的两倍多。对于更新函数来说,提升相对较小,但仍然比 JavaScript 更快。

结论

正如文档作者所承诺的那样,遵循查询服务器协议时编写自定义查询服务器并不那么困难。尽管 CouchGO!一般来说,缺少一些已弃用的函数,即使在开发的早期阶段,它也比 JavaScript 版本提供了显着的提升。我相信还有很大的改进空间。

如果您需要将本文中的所有代码集中到一个地方,您可以在我的 GitHub 存储库中找到它。

感谢您阅读本文。我很想听听您对此解决方案的想法。您会将其与 CouchDB 实例一起使用吗?或者您可能已经使用了一些定制的查询服务器?我很高兴在评论中听到它。

不要忘记查看我的其他文章,以获取更多提示、见解以及本系列的其他部分。快乐黑客!

版本聲明 本文轉載於:https://dev.to/kishieel/couchgo-enhancing-couchdb-with-query-server-written-in-go-304n?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • Spark DataFrame添加常量列的妙招
    Spark DataFrame添加常量列的妙招
    在Spark Dataframe ,將常數列添加到Spark DataFrame,該列具有適用於所有行的任意值的Spark DataFrame,可以通過多種方式實現。使用文字值(SPARK 1.3)在嘗試提供直接值時,用於此問題時,旨在為此目的的使用column方法可能會導致錯誤。 df.with...
    程式設計 發佈於2025-05-16
  • 如何將來自三個MySQL表的數據組合到新表中?
    如何將來自三個MySQL表的數據組合到新表中?
    mysql:從三個表和列的新表創建新表 答案:為了實現這一目標,您可以利用一個3-way Join。 選擇p。 *,d.content作為年齡 來自人為p的人 加入d.person_id = p.id上的d的詳細信息 加入T.Id = d.detail_id的分類法 其中t.taxonomy ...
    程式設計 發佈於2025-05-16
  • eval()vs. ast.literal_eval():對於用戶輸入,哪個Python函數更安全?
    eval()vs. ast.literal_eval():對於用戶輸入,哪個Python函數更安全?
    稱量()和ast.literal_eval()中的Python Security 在使用用戶輸入時,必須優先確保安全性。強大的Python功能Eval()通常是作為潛在解決方案而出現的,但擔心其潛在風險。 This article delves into the differences betwee...
    程式設計 發佈於2025-05-16
  • 使用jQuery如何有效修改":after"偽元素的CSS屬性?
    使用jQuery如何有效修改":after"偽元素的CSS屬性?
    在jquery中了解偽元素的限制:訪問“ selector 嘗試修改“:”選擇器的CSS屬性時,您可能會遇到困難。 This is because pseudo-elements are not part of the DOM (Document Object Model) and are th...
    程式設計 發佈於2025-05-16
  • 如何克服PHP的功能重新定義限制?
    如何克服PHP的功能重新定義限制?
    克服PHP的函數重新定義限制在PHP中,多次定義一個相同名稱的函數是一個no-no。嘗試這樣做,如提供的代碼段所示,將導致可怕的“不能重新列出”錯誤。 但是,PHP工具腰帶中有一個隱藏的寶石:runkit擴展。它使您能夠靈活地重新定義函數。 runkit_function_renction_...
    程式設計 發佈於2025-05-16
  • 如何將MySQL數據庫添加到Visual Studio 2012中的數據源對話框中?
    如何將MySQL數據庫添加到Visual Studio 2012中的數據源對話框中?
    在Visual Studio 2012 儘管已安裝了MySQL Connector v.6.5.4,但無法將MySQL數據庫添加到實體框架的“ DataSource對話框”中。為了解決這一問題,至關重要的是要了解MySQL連接器v.6.5.5及以後的6.6.x版本將提供MySQL的官方Visual...
    程式設計 發佈於2025-05-16
  • 大批
    大批
    [2 數組是對象,因此它們在JS中也具有方法。 切片(開始):在新數組中提取部分數組,而無需突變原始數組。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    程式設計 發佈於2025-05-16
  • 如何在Java字符串中有效替換多個子字符串?
    如何在Java字符串中有效替換多個子字符串?
    在java 中有效地替換多個substring,需要在需要替換一個字符串中的多個substring的情況下,很容易求助於重複應用字符串的刺激力量。 However, this can be inefficient for large strings or when working with nu...
    程式設計 發佈於2025-05-16
  • Java數組中元素位置查找技巧
    Java數組中元素位置查找技巧
    在Java數組中檢索元素的位置 利用Java的反射API將數組轉換為列表中,允許您使用indexof方法。 (primitives)(鏈接到Mishax的解決方案) 用於排序陣列的數組此方法此方法返回元素的索引,如果發現了元素的索引,或一個負值,指示應放置元素的插入點。
    程式設計 發佈於2025-05-16
  • CSS可以根據任何屬性值來定位HTML元素嗎?
    CSS可以根據任何屬性值來定位HTML元素嗎?
    靶向html元素,在CSS 中使用任何屬性值,在CSS中,可以基於特定屬性(如下所示)基於特定屬性的基於特定屬性的emants目標元素: 字體家庭:康斯拉斯(Consolas); } 但是,出現一個常見的問題:元素可以根據任何屬性值而定位嗎?本文探討了此主題。 的目標元素有任何任何屬性值,...
    程式設計 發佈於2025-05-16
  • 如何有效地轉換PHP中的時區?
    如何有效地轉換PHP中的時區?
    在PHP 利用dateTime對象和functions DateTime對象及其相應的功能別名為時區轉換提供方便的方法。例如: //定義用戶的時區 date_default_timezone_set('歐洲/倫敦'); //創建DateTime對象 $ dateTime = ne...
    程式設計 發佈於2025-05-16
  • Java字符串非空且非null的有效檢查方法
    Java字符串非空且非null的有效檢查方法
    檢查字符串是否不是null而不是空的 if(str!= null && str.isementy())二手: if(str!= null && str.length()== 0) option 3:trim()。 isement(Isement() trim whitespace whites...
    程式設計 發佈於2025-05-16
  • 用戶本地時間格式及時區偏移顯示指南
    用戶本地時間格式及時區偏移顯示指南
    在用戶的語言環境格式中顯示日期/時間,並使用時間偏移在向最終用戶展示日期和時間時,以其localzone and格式顯示它們至關重要。這確保了不同地理位置的清晰度和無縫用戶體驗。以下是使用JavaScript實現此目的的方法。 方法:推薦方法是處理客戶端的Javascript中的日期/時間格式化和...
    程式設計 發佈於2025-05-16
  • 切換到MySQLi後CodeIgniter連接MySQL數據庫失敗原因
    切換到MySQLi後CodeIgniter連接MySQL數據庫失敗原因
    無法連接到mySQL數據庫:故障排除錯誤消息要調試問題,建議將以下代碼添加到文件的末尾.//config/database.php並查看輸出: ... ... 迴聲'... echo '<pre>'; print_r($db['default']); echo '</pr...
    程式設計 發佈於2025-05-16
  • 在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在JTable中維護jtable單元格渲染後,在JTable中,在JTable中實現自定義單元格渲染和編輯功能可以增強用戶體驗。但是,至關重要的是要確保即使在編輯操作後也保留所需的格式。 在設置用於格式化“價格”列的“價格”列,用戶遇到的數字格式丟失的“價格”列的“價格”之後,問題在設置自定義單元...
    程式設計 發佈於2025-05-16

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3