”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 沙发GO! — 使用 Go 编写的查询服务器增强 CouchDB

沙发GO! — 使用 Go 编写的查询服务器增强 CouchDB

发布于2024-08-06
浏览:902

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]删除
最新教程 更多>
  • HTML格式标签
    HTML格式标签
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    编程 发布于2025-07-12
  • 如何在JavaScript对象中动态设置键?
    如何在JavaScript对象中动态设置键?
    在尝试为JavaScript对象创建动态键时,如何使用此Syntax jsObj['key' i] = 'example' 1;不工作。正确的方法采用方括号: jsobj ['key''i] ='example'1; 在JavaScript中,数组是一...
    编程 发布于2025-07-12
  • PHP SimpleXML解析带命名空间冒号的XML方法
    PHP SimpleXML解析带命名空间冒号的XML方法
    在php 很少,请使用该限制很大,很少有很高。例如:这种技术可确保可以通过遍历XML树和使用儿童()方法()方法的XML树和切换名称空间来访问名称空间内的元素。
    编程 发布于2025-07-12
  • Java中假唤醒真的会发生吗?
    Java中假唤醒真的会发生吗?
    在Java中的浪费唤醒:真实性或神话?在Java同步中伪装唤醒的概念已经是讨论的主题。尽管存在这种行为的潜力,但问题仍然存在:它们实际上是在实践中发生的吗? Linux的唤醒机制根据Wikipedia关于伪造唤醒的文章,linux实现了pthread_cond_wait()功能的Linux实现,利用...
    编程 发布于2025-07-12
  • 如何从Python中的字符串中删除表情符号:固定常见错误的初学者指南?
    如何从Python中的字符串中删除表情符号:固定常见错误的初学者指南?
    从python import codecs import codecs import codecs 导入 text = codecs.decode('这狗\ u0001f602'.encode('utf-8'),'utf-8') 印刷(文字)#带有...
    编程 发布于2025-07-12
  • 如何使用替换指令在GO MOD中解析模块路径差异?
    如何使用替换指令在GO MOD中解析模块路径差异?
    在使用GO MOD时,在GO MOD 中克服模块路径差异时,可能会遇到冲突,其中可能会遇到一个冲突,其中3派对软件包将另一个带有导入套件的path package the Imptioned package the Imptioned package the Imported tocted pac...
    编程 发布于2025-07-12
  • 如何干净地删除匿名JavaScript事件处理程序?
    如何干净地删除匿名JavaScript事件处理程序?
    删除匿名事件侦听器将匿名事件侦听器添加到元素中会提供灵活性和简单性,但是当要删除它们时,可以构成挑战,而无需替换元素本身就可以替换一个问题。 element? element.addeventlistener(event,function(){/在这里工作/},false); 要解决此问题,请考虑...
    编程 发布于2025-07-12
  • 如何使用Python有效地以相反顺序读取大型文件?
    如何使用Python有效地以相反顺序读取大型文件?
    在python 中,如果您使用一个大文件,并且需要从最后一行读取其内容,则在第一行到第一行,Python的内置功能可能不合适。这是解决此任务的有效解决方案:反向行读取器生成器 == ord('\ n'): 缓冲区=缓冲区[:-1] ...
    编程 发布于2025-07-12
  • 在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    mysql-python安装错误:“ mysql_config找不到”“ 由于缺少MySQL开发库而出现此错误。解决此问题,建议在Ubuntu上使用该分发的存储库。使用以下命令安装Python-MysqldB: sudo apt-get安装python-mysqldb sudo pip in...
    编程 发布于2025-07-12
  • 如何检查对象是否具有Python中的特定属性?
    如何检查对象是否具有Python中的特定属性?
    方法来确定对象属性存在寻求一种方法来验证对象中特定属性的存在。考虑以下示例,其中尝试访问不确定属性会引起错误: >>> a = someClass() >>> A.property Trackback(最近的最新电话): 文件“ ”,第1行, AttributeError: SomeClass...
    编程 发布于2025-07-12
  • 同实例无需转储复制MySQL数据库方法
    同实例无需转储复制MySQL数据库方法
    在同一实例上复制一个MySQL数据库而无需转储在同一mySQL实例上复制数据库,而无需创建InterMediate sqql script。以下方法为传统的转储和IMPORT过程提供了更简单的替代方法。 直接管道数据 MySQL手动概述了一种允许将mysqldump直接输出到MySQL clie...
    编程 发布于2025-07-12
  • 为什么使用Firefox后退按钮时JavaScript执行停止?
    为什么使用Firefox后退按钮时JavaScript执行停止?
    导航历史记录问题:JavaScript使用Firefox Back Back 此行为是由浏览器缓存JavaScript资源引起的。要解决此问题并确保在后续页面访问中执行脚本,Firefox用户应设置一个空功能。 警报'); }; alert('inline Alert')...
    编程 发布于2025-07-12
  • 为什么HTML无法打印页码及解决方案
    为什么HTML无法打印页码及解决方案
    无法在html页面上打印页码? @page规则在@Media内部和外部都无济于事。 HTML:Customization:@page { margin: 10%; @top-center { font-family: sans-serif; font-weight: bo...
    编程 发布于2025-07-12
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式接口中实现垂直滚动元素的CSS高度限制问题:考虑一个布局,其中我们具有与用户垂直滚动一起移动的可滚动地图div,同时与固定的固定sidebar保持一致。但是,地图的滚动无限期扩展,超过了视口的高度,阻止用户访问页面页脚。$("#map").css({ marginT...
    编程 发布于2025-07-12
  • 哪种方法更有效地用于点 - 填点检测:射线跟踪或matplotlib \的路径contains_points?
    哪种方法更有效地用于点 - 填点检测:射线跟踪或matplotlib \的路径contains_points?
    在Python Matplotlib's path.contains_points FunctionMatplotlib's path.contains_points function employs a path object to represent the polygon.它...
    编程 发布于2025-07-12

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3