5.2. 性能

对于最多数万个文档,无论你如何编写代码,通常都会发现 CouchDB 的性能良好。一旦你开始处理数百万个文档,你就需要更加小心。

5.2.1. 磁盘 I/O

5.2.1.1. 文件大小

文件大小越小,I/O 操作就越少,CouchDB 和操作系统可以缓存更多文件,复制、备份等操作速度就越快。因此,你应该仔细检查你正在存储的数据。例如,使用数百个字符长的键是愚蠢的,但如果你只使用单个字符的键,你的程序将难以维护。仔细考虑通过将数据放入视图中来复制的数据。

5.2.1.2. 磁盘和文件系统性能

使用更快的磁盘、条带化 RAID 阵列和现代文件系统都可以加快你的 CouchDB 部署。但是,有一个选项可以在磁盘性能成为瓶颈时提高你的 CouchDB 服务器的响应速度。来自 Erlang 文档的 file 模块

在支持线程的操作系统上,可以将文件操作在自己的线程中执行,允许其他 Erlang 进程与文件操作并行执行。参见 erl(1) 中的命令行标志 +A.

将此参数设置为大于零的数字可以使你的 CouchDB 安装即使在磁盘利用率很高的时期也能保持响应。设置此选项的最简单方法是通过 ERL_FLAGS 环境变量。例如,要为 Erlang 提供四个线程来执行 I/O 操作,请将以下内容添加到 (prefix)/etc/defaults/couchdb(或等效项)

export ERL_FLAGS="+A 4"

5.2.2. 系统资源限制

管理员在部署规模扩大时遇到的问题之一是系统和应用程序配置施加的资源限制。提高这些限制可以使你的部署超出默认配置的支持范围。

5.2.2.1. CouchDB 配置选项

5.2.2.1.1. max_dbs_open

在你的 配置(local.ini 或类似文件)中,熟悉 couchdb/max_dbs_open

[couchdb]
max_dbs_open = 100

此选项对一次可以打开的数据库数量设置上限。CouchDB 在内部对数据库访问进行引用计数,并在需要时关闭空闲数据库。有时需要一次保持打开的数据库数量超过默认值,例如在许多数据库将持续复制的部署中。

5.2.2.2. Erlang

即使你已经增加了 CouchDB 允许的最大连接数,Erlang 运行时系统默认情况下也不会允许超过 65536 个连接。将以下指令添加到 (prefix)/etc/vm.args(或等效项)将增加此限制(在本例中为 102400)

+Q 102400

请注意,在 Windows 上,Erlang 实际上不会将文件描述符限制增加到超过 8192(即系统头定义的 FD_SETSIZE 值)。在 macOS 上,限制可能低至 1024。参见 此提示以获取可能的解决方法此线程以获取更深入的解释.

5.2.2.3. 最大打开文件描述符 (ulimit)

通常,现代类 UNIX 系统可以轻松处理每个进程的非常大量的文件句柄(例如 100000)。不要害怕在你的系统上增加此限制。

增加这些限制的方法各不相同,具体取决于你的 init 系统和特定的操作系统版本。许多操作系统的默认值为 1024 或 4096。在具有许多数据库或许多视图的系统上,CouchDB 可以非常快地达到此限制。

对于基于 systemd 的 Linuxes(如 CentOS/RHEL 7、Ubuntu 16.04+、Debian 8 或更新版本),假设你从 systemd 启动 CouchDB,你必须通过编辑覆盖文件来覆盖上限。最佳实践是通过 systemctl edit couchdb 命令。将以下行添加到编辑器中的文件中

[Service]
LimitNOFILE=65536

…或你喜欢的任何值。要将此值增加到超过 65536,你还必须将 Erlang +Q 参数添加到你的 etc/vm.args 文件中,方法是添加以下行

+Q 102400

旧的 ERL_MAX_PORTS 环境变量被 CouchDB 附带的 Erlang 版本忽略。

如果你的系统设置为使用可插拔身份验证模块 (PAM),并且你没有从 systemd 启动 CouchDB,那么增加此限制很简单。例如,创建一个名为 /etc/security/limits.d/100-couchdb.conf 的文件,其中包含以下内容,将确保 CouchDB 可以一次打开多达 65536 个文件描述符

#<domain>    <type>    <item>    <value>
couchdb      hard      nofile    65536
couchdb      soft      nofile    65536

如果你使用的是我们的 Debian/Ubuntu sysvinit 脚本 (/etc/init.d/couchdb),你还需要为 root 用户提高限制

#<domain>    <type>    <item>    <value>
root         hard      nofile    65536
root         soft      nofile    65536

你可能还需要编辑 /etc/pam.d/common-session/etc/pam.d/common-session-noninteractive 文件以添加以下行

session required pam_limits.so

如果它不存在。

如果你的系统不使用 PAM,通常可以使用 ulimit 命令在自定义脚本中使用,以启动具有增加的资源限制的 CouchDB。典型的语法类似于 ulimit -n 65536.

5.2.3. 网络

发出和接收每个请求/响应都会产生延迟开销。通常,你应该以批处理方式执行请求。大多数 API 都有某种机制来执行批处理,通常是通过在请求主体中提供文档或键列表。小心选择批处理的大小。批处理越大,客户端花费在将项目编码为 JSON 的时间就越多,花费在解码这些响应的时间就越多。使用你自己的配置和典型数据进行一些基准测试,以找到最佳点。它很可能在 1 到 10000 个文档之间。

如果你有一个快速的 I/O 系统,你也可以使用并发性 - 同时进行多个请求/响应。这可以减轻组装 JSON、进行网络操作和解码 JSON 所涉及的延迟。

从 CouchDB 1.1.0 开始,用户经常报告与旧版本相比,文档的写入性能较低。主要原因是此版本附带了更新版本的 HTTP 服务器库 MochiWeb,默认情况下将 TCP 套接字选项 SO_NODELAY 设置为 false。这意味着发送到 TCP 套接字的小数据,例如对文档写入请求的回复(或读取非常小的文档),不会立即发送到网络 - TCP 会将其缓冲一段时间,希望它会被要求通过同一个套接字发送更多数据,然后一次性发送所有数据以提高性能。可以通过 httpd/socket_options 禁用此 TCP 缓冲行为

[httpd]
socket_options = [{nodelay, true}]

另请参见

批量 加载存储 API。

5.2.3.1. 连接限制

MochiWeb 处理 CouchDB 请求。默认最大连接数为 2048。要更改此限制,请使用 server_options 配置变量。 max 表示最大连接数。

[chttpd]
server_options = [{backlog, 128}, {acceptor_pool_size, 16}, {max, 4096}]

5.2.4. CouchDB

5.2.4.1. DELETE 操作

当您 DELETE 文档时,数据库将创建一个新的修订版,其中包含 _id_rev 字段以及 _deleted 标志。即使在 数据库压缩 之后,此修订版也将保留,以便可以复制删除操作。已删除的文档,与未删除的文档一样,会影响视图构建时间、PUTDELETE 请求时间以及数据库的大小,因为它们会增加 B+Tree 的大小。您可以在 database information 中查看已删除文档的数量。如果您的用例创建了大量已删除文档(例如,如果您存储短期数据,如日志条目、消息队列等),您可能需要定期切换到一个新的数据库并删除旧的数据库(一旦其中的条目全部过期)。

5.2.4.2. 文档的 ID

db 文件大小取决于您的文档和视图大小,但也取决于您的 _id 大小的倍数。不仅 _id 存在于文档中,而且它及其部分内容在 CouchDB 用于导航文件以首先找到文档的二叉树结构中被复制。举一个现实世界的例子,对于一个用户来说,从 16 字节的 ID 切换到 4 字节的 ID 使得数据库从 21GB 变为 4GB,其中包含 1000 万个文档(原始 JSON 文本从 2.5GB 变为 2GB)。

使用顺序(至少是排序的)ID 插入比使用随机 ID 插入更快。因此,您应该考虑自己生成 ID,按顺序分配它们,并使用消耗更少字节的编码方案。例如,可以用 4 个 62 进制数字(10 个数字、26 个小写字母、26 个大写字母)来表示需要 16 个十六进制数字才能表示的东西。

5.2.5. 视图

5.2.5.1. 视图生成

当有大量文档需要处理时,使用 JavaScript 查询服务器的视图生成速度非常慢。生成过程甚至不会饱和单个 CPU,更不用说您的 I/O 了。原因在于 CouchDB 服务器和独立的 couchjs 查询服务器之间的延迟,这极大地表明了在您的实现中消除延迟的重要性。

您可以让视图访问“陈旧”,但实际上无法确定何时会发生这种情况,从而为您提供快速响应,以及何时会更新视图,这将需要很长时间。(一个包含 1000 万个文档的数据库大约需要 10 分钟才能加载到 CouchDB 中,但大约需要 4 个小时才能完成视图生成)。

在集群中,“陈旧”请求由一组固定的分片服务,以便在请求之间为用户提供一致的结果。这带来了可用性权衡 - 这组固定的分片可能不是集群中最具响应性/可用性。如果您不需要这种一致性(例如,您的索引相对静态),您可以告诉 CouchDB 使用任何可用的副本,方法是指定 stable=false&update=false 而不是 stale=ok,或者 stable=false&update=lazy 而不是 stale=update_after

视图信息不会被复制 - 它是在每个数据库上重建的,因此您无法在单独的服务器上进行视图生成。

5.2.5.2. 内置的 Reduce 函数

如果您使用的是非常简单的视图函数,该函数仅执行求和或计数缩减,您可以通过简单地在函数声明中写入 _sum_count 来调用它们的本机 Erlang 实现。这将极大地加快速度,因为它减少了 CouchDB 和 JavaScript 查询服务器 之间的 IO。例如,正如 邮件列表中提到的,输出包含大约 78,000 个项目的(已索引和缓存)视图的时间从 60 秒减少到 4 秒。

之前

{
    "_id": "_design/foo",
    "views": {
        "bar": {
            "map": "function (doc) { emit(doc.author, 1); }",
            "reduce": "function (keys, values, rereduce) { return sum(values); }"
        }
    }
}

之后

{
    "_id": "_design/foo",
    "views": {
        "bar": {
            "map": "function (doc) { emit(doc.author, 1); }",
            "reduce": "_sum"
        }
    }
}