CouchDB 原生 Erlang 查询服务器(Native Erlang Query Server)完整指南

📅 发布时间:2026/10/9 10:06:53
CouchDB 原生 Erlang 查询服务器(Native Erlang Query Server)完整指南
数据库文档数据库后端【免费下载链接】couchdbSeamless multi-primary syncing database with an intuitive HTTP/JSON API, designed for reliability项目地址https://gitcode.com/gh_mirrors/co/couchdb点击查看免费下载CouchDB 默认通过外部进程Query Server执行设计文档Design Document中的函数例如 JavaScript 视图。而原生 Erlang 查询服务器Native Erlang Query Server允许你直接用 Erlang 编写 map/reduce、list、show、filter 等函数让它们以 Erlang 函数的形式直接运行在 CouchDB 虚拟机内部绕过 stdio 通信与 JSON 序列化/反序列化的往返开销。本文以官方文档 erlang.rst 为主体结合仓库源码详细讲解其启用方式、运行时 APIEmit、FoldRows、GetRow、Log、Send、Start与底层实现原理帮助你安全、正确地用 Erlang 编写高性能的 CouchDB 视图与列表函数。1. 什么是原生 Erlang 查询服务器CouchDB 将设计文档中函数的计算委托给外部查询服务器——一种通过标准输入/输出与 CouchDB 通信、使用基于行的 JSON 消息协议的独立 OS 进程参见 query-servers.rst。默认查询服务器由 JavaScript 编写通过 SpiderMonkey/QuickJS 引擎运行。与之不同CouchDB 还内置了一个原生NativeErlang 查询服务器核心模块为 couch_native_process.erl它以gen_server形式运行在 CouchDB BEAM 虚拟机内部。正如 query-servers.rst 中的说明The Native Erlang Query Server allows runningddocswritten in Erlang natively, bypassing stdio communication and JSON serialization/deserialization round trip overhead.也就是说Erlang 函数既不经过外部进程也不经过 JSON 编解码而是直接以 Erlang 闭包的形式在 CouchDB 进程内执行因此比 JavaScript 函数更快官方文档原文Erlang functions are faster than JavaScript ones。重要前提Erlang 查询服务器默认是关闭的disabled by default。这是出于安全考虑——不同于 JavaScript 查询服务器运行在沙箱中Erlang 查询服务器不在沙箱模式下运行Erlang 代码对 OS、文件系统和网络拥有完全访问权限可能带来安全隐患。官方文档 erlang.rst 开篇即提示The Erlang query server is disabled by default. Read configuration guide about reasons why and how to enable it.因此启用前请务必评估运行代码的来源可信度尤其是他人编写的函数。2. 启用与配置2.1 修改 local.ini 启用在local.ini中添加[native_query_servers]小节并设置开关[native_query_servers] enable_erlang_query_server true修改后需要重启 CouchDB 服务才能生效。在默认配置 default.ini 中该选项以注释形式存在默认值为false[native_query_servers] ;enable_erlang_query_server false2.2 源码中的启用判定逻辑在 couch_proc_manager.erl 中可以看到启用判定与注册逻辑native_query_server_enabled() - % 1. [native_query_server] enable_erlang_query_server true | false % 2. if [native_query_server] erlang {couch_native_process, start_link, []} - pretend true as well NativeEnabled config:get_boolean(native_query_servers, enable_erlang_query_server, false), NativeLegacyConfig config:get(native_query_servers, erlang, ), NativeLegacyEnabled NativeLegacyConfig : {couch_native_process, start_link, []}, NativeEnabled orelse NativeLegacyEnabled. maybe_configure_erlang_native_servers() - case native_query_server_enabled() of true - ets:insert(?SERVERS, [ {ERLANG, {couch_native_process, start_link, []}} ]); _Else - ok end.由此可见有两种启用方式新方式推荐enable_erlang_query_server true遗留方式设置erlang {couch_native_process, start_link, []}此时会被视为已启用源码注释称为 pretend true as well。启用后ERLANG语言被注册为{couch_native_process, start_link, []}即直接在 CouchDB 内部启动couch_native_process的 gen_server。而其他外部查询服务器如 JavaScript则是通过couch_os_process:start_link(Command)启动 OS 进程见 couch_proc_manager.erl。2.3 在查询中使用 Erlang设计文档通过language字段声明使用的查询服务器语言参见 ddocs.rst。将language设为erlang后该设计文档中的 map、reduce、list、show、filter 等函数均以 Erlang 源码字符串形式存储并由原生 Erlang 查询服务器执行。3. 一个完整的 map/reduce 示例官方文档给出一个统计每个修订版本号对应的文档数量的经典示例。先向数据库添加若干文档然后创建视图%% Map Function fun({Doc}) - K,_/binary proplists:get_value(_rev, Doc, null), V proplists:get_value(_id, Doc, null), Emit(K, V) end. %% Reduce Function fun(Keys, Values, ReReduce) - length(Values) end.Map 函数接收一个文档以 Erlang proplist 形式表示从_rev字段中提取修订号前缀K,_/binary模式匹配出1-、2-等版本号以修订号为 key、_id为 value 调用Emit发射键值对Reduce 函数则简单地返回length(Values)即每个修订号下的文档总数。视图运行成功后即可看到每个修订版本号对应的文档数量列表。4. 运行时 API 详解原生 Erlang 查询服务器向你的 Erlang 函数注入一组内置绑定函数binding。这些绑定的实际定义位于 couch_native_process.erl 的bindings/2,3函数中。下面逐一讲解官方文档定义的六个 API。4.1 Emit(Id, Value)向视图索引进程发射key-value键值对是 map 函数的核心输出手段。fun({Doc}) - K,_/binary proplists:get_value(_rev, Doc, null), V proplists:get_value(_id, Doc, null), Emit(K, V) end.底层实现中每次Emit调用都会把[Id, Value]追加到以函数签名为 key 的进程字典列表中见 couch_native_process.erlEmit fun(Id, Value) - Curr erlang:get(Sig), erlang:put(Sig, [[Id, Value] | Curr]) end,这也解释了为什么同一个文档可以多次调用Emit——每次调用都会产生一条独立的索引记录最终在map_doc命令处理时通过lists:reverse(erlang:get(Sig))返回全部发射结果见 couch_native_process.erl。4.2 FoldRows(Fun, Acc)用于在 list 函数中迭代视图的所有行。Fun是处理函数对象Acc是Fun上一次返回的累加值。官方文档示例——逐行打印前一个与当前文档 idfun(Head, {Req}) - Fun fun({Row}, Acc) - Id couch_util:get_value(id, Row), Send(list_to_binary(io_lib:format(Previous doc id: ~p~n, [Acc]))), Send(list_to_binary(io_lib:format(Current doc id: ~p~n, [Id]))), {ok, Id} end, FoldRows(Fun, nil), end.注意内部函数必须以{ok, NewAcc}返回继续迭代或以{stop, NewAcc}提前终止迭代首次调用时Acc传入nil。4.3 GetRow()从相关视图结果中取出下一行row。FoldRows的底层实现正是基于GetRow递归构建的官方文档给出了其背景实现对应源码 couch_native_process.erlfoldrows(GetRow, ProcRow, Acc) - case GetRow() of nil - {ok, Acc}; Row - case (catch ProcRow(Row, Acc)) of {ok, Acc2} - foldrows(GetRow, ProcRow, Acc2); {stop, Acc2} - {ok, Acc2} end end.GetRow的注入实现见 couch_native_process.erl它会先把已累积的Send分块通过{self(), chunks, ...}消息发出start_list_resp负责触发 list 的start响应然后阻塞等待{Self, list_row, Row}或{Self, list_end}消息若超过进程超时时间默认 5000ms来自evstate.timeout则抛出{timeout, list_pid_getrow}。4.4 Log(Msg)以INFO级别记录一条日志消息。官方文档示例在 map 函数中记录文档 idfun({Doc}) - K,_/binary proplists:get_value(_rev, Doc, null), V proplists:get_value(_id, Doc, null), Log(lists:flatten(io_lib:format(Hello from ~s doc!, [V]))), Emit(K, V) end.map 函数运行后CouchDB 日志例如/var/log/couchdb/couch.log中会出现如下记录[Sun, 04 Nov 2012 11:33:58 GMT] [info] [0.9144.2] Hello from 8d300b86622d67953d102165dbe99467 doc!源码中Log绑定直接转发到couch_log:info(Msg, [])见 couch_native_process.erl因此消息会进入 CouchDB 的标准日志体系可用于在批量索引时输出调试信息。4.5 Send(Chunk)向响应中发送单个字符串分块Chunk用于 list 函数逐块输出内容。fun(Head, {Req}) - Send(Hello,), Send( ), Send(Couch), ! end.上述函数产生的响应为Hello, Couch!实现上Send与Emit类似把分块累积到进程字典并在 list 结束时通过lists:reverse汇总返回见 couch_native_process.erl。list 函数的最后一个表达式作为最后的输出分块此处为!与前面Send的分块拼接成完整响应。4.6 Start(Headers)初始化 list 函数的响应头。Headers是响应对象response object的 proplist。在这个阶段可以定义响应状态码和响应头。官方文档示例——返回 302 重定向到 CouchDB 官网fun(Head, {Req}) - Start({[{code, 302}, {headers, {[ {Location, http://couchdb.apache.org}] }} ]}), Relax! end.实现中Start把响应头存入进程字典的list_headers见 couch_native_process.erl随后在start_list_resp/2中作为start响应的组成部分发送给 CouchDB见 couch_native_process.erl若未调用Start则默认使用{[{headers, {[]}}]}空响应头。响应对象含code、headers等字段的完整定义可参见设计文档相关章节 ddocs.rst。4.7 其他可用绑定除了文档中列出的六个 API源码bindings/3还注入了一个DDoc绑定当执行涉及设计文档的函数如 validate_doc_update、show、list时会把{DDoc, DDoc}加入绑定列表见 couch_native_process.erl使 Erlang 函数能够访问当前设计文档的完整内容。5. 底层原理Erlang 源码如何被编译执行原生查询服务器的一个关键机制是动态编译用户提供的 Erlang 源码字符串。在 couch_native_process.erl 的makefun/3中可以看到完整流程makefun(_State, Source, BindFuns) when is_list(BindFuns) - FunStr binary_to_list(Source), {ok, Tokens, _} erl_scan:string(FunStr), Form case (catch erl_parse:parse_exprs(Tokens)) of {ok, [ParsedForm]} - ParsedForm; ... end, Bindings lists:foldl( fun({Name, Fun}, Acc) - erl_eval:add_binding(Name, Fun, Acc) end, erl_eval:new_bindings(), BindFuns ), {value, Fun, _} erl_eval:expr(Form, Bindings), Fun.流程为erl_scan:string对源码做词法分析 →erl_parse:parse_exprs解析为抽象语法树 → 通过erl_eval:add_binding将Emit、Log、Send等绑定函数注入求值环境 →erl_eval:expr求值得到真正的 Erlang 函数闭包。每个函数都会基于源码计算 MD5 签名couch_hash:md5_hash(Source)该签名作为进程字典中累积发射结果的 key同时用于缓存与去重见 couch_native_process.erl。5.1 支持的设计函数类型从ddoc/3的分发逻辑couch_native_process.erl可以看出原生 Erlang 查询服务器完整支持以下设计函数函数类型调用形态说明validate_doc_updateFun(NewDoc, OldDoc, UserCtx, SecObj)文档写入校验rewritesFun(Req)URL 重写规则filtersFun(Doc, Req)变更复制过滤viewsmapFun(Doc)视图 mapshowsFun(Doc, Req)show 函数updatesFun(Doc, Req)update 函数返回[JsonDoc, JsonResp]listsFun(Head, Req)list 函数配合Start/Send/GetRow/FoldRows使用list 函数会被放入独立 spawn 的进程中执行spawn_link并借助消息传递与GetRow实现拉取式逐行读取视图结果见 couch_native_process.erl。6. 相关查询服务器配置参数虽然 Erlang 查询服务器运行在 CouchDB 内部但它仍属于查询服务器体系受 query_server_config 小节中通用参数的影响[query_server_config] commit_freq 5 ; 视图索引变更落盘延迟秒默认 5 os_process_limit 100 ; 查询服务器 OS 进程硬上限默认 100 os_process_soft_limit 100 ; 查询服务器 OS 进程软上限默认 100 reduce_limit true ; Reduce 溢出控制默认 true其中os_process_limit/os_process_soft_limit主要约束外部 OS 进程型查询服务器对于原生 Erlang 查询服务器进程池的获取/归还同样经由 couch_proc_manager.erl 管理enable_erlang_query_server开关决定ERLANG是否出现在进程池可用的语言注册表中。reduce_limit则控制Reduce overflow错误当 reduce 函数输出比输入还大时抛出错误见 couch_query_servers.erl 中check_sum_overflow对OutSize 4906且OutSize * 2 InSize的判定。7. 安全权衡与适用建议最后再次强调官方文档反复提醒的安全要点默认禁用出于安全限制Erlang 查询服务器默认不启用default.ini 中enable_erlang_query_server默认false。无沙箱与 JavaScript 查询服务器不同Erlang 查询服务器不运行在沙箱模式Erlang 代码对 OS、文件系统和网络拥有完全访问权限可能引发安全问题。性能优势Erlang 函数比 JavaScript 函数更快因为它绕过了 stdio 通信和 JSON 编解码开销。使用建议仅在信任代码来源的前提下启用避免运行他人编写的未知 Erlang 设计函数生产环境中如需类似能力可考虑在受控环境中对设计文档的提交进行审计。从源码结构看couch_native_process的设计目标正如其模块注释所述提供最小可用的原生视图服务器the smallest possible native view-server——它暴露了足以让 Erlang 服务器充当完整视图服务器的函数但不包含额外的辅助函数期望第三方扩展在其之上构建更友好的封装层couch_native_process.erl。理解这一层边界有助于你正确评估在什么场景下使用原生 Erlang 查询服务器以及如何与 JavaScript 查询服务器、Mango 查询引擎进行组合选型。赞分享数据库文档数据库后端【免费下载链接】couchdbSeamless multi-primary syncing database with an intuitive HTTP/JSON API, designed for reliability项目地址https://gitcode.com/gh_mirrors/co/couchdb点击查看免费下载相关推荐CouchDB 查询服务器Query Server完整配置指南环境变量、进程池、Erlang 原生查询与 Mango/Search 调优CouchDB 查询服务器Query Server完整配置指南环境变量、进程池、Erlang 原生查询与 Mango/Search 调优 导读 查询服务器数据库文档数据库后端Erlang Language Server为Erlang开发者提供的强大语言服务Erlang Language Server为Erlang开发者提供的强大语言服务 Erlang Language Server简称Erlang LS是一免费音乐下载神器MusicDownload让你的音乐收藏从此无忧免费音乐下载神器MusicDownload让你的音乐收藏从此无忧 在数字音乐时代你是否也曾为寻找高品质音乐资源而烦恼MusicDownload作为一款终极上一篇番茄小说下载工具快速上手一个ID换整本小说5种格式、3种运行方式全讲清下一篇抖音批量下载完整指南把一个博主的 100 条作品搬进本地创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考