本文档为翻译版本,仅供参考,一切内容请以原文为准。

POSTGRESQL / MONGODB 性能基准测试

目录 (CONTENTS)

  • // 关于本基准测试
    • 引言
    • OnGres 道德政策
    • 作者
  • // 执行摘要:基准测试关键发现
    • 事务基准测试
    • OLTP 基准测试
    • OLAP 基准测试
  • // 方法论与基准测试
    • 引言与目标
    • 执行的基准测试
    • 涉及的技术简介
    • 自动化基础设施
  • // 事务基准测试
    • 基准测试描述
    • MongoDB 事务的限制
    • 事务隔离级别的讨论
    • 基准测试结果
  • // OLTP 基准测试
    • 基准测试描述
    • 初步考量
    • 基准测试结果
  • // OLAP 基准测试
    • 基准测试描述
    • 基准测试结果

关于本基准测试

引言

基准测试并非易事。数据库的基准测试,更难。对遵循不同方法(关系型 vs 文档型)的数据库进行基准测试,则是难上加难。其原因有很多,且已在业界被广泛讨论。

尽管存在这些困难,市场仍然需要这类基准测试。尽管 MongoDB 和 PostgreSQL 提供了不同的数据模型,但许多开发者和组织在选择平台时仍面临挑战。虽然可以从多个方面对它们进行比较,但性能无疑是主要的区别之一——甚至可以说是最主要的一个。

那么,如何才能利用一个信息丰富的基准测试来辅助技术选型决策,同时又能提供一个公平的竞争环境,让两种技术在“同类比较”的场景中一较高下呢?为实现这些目标,本次基准测试基于以下标准执行:

  • 透明性与可复现性。 用于运行基准测试的框架是完全自动化的,并已作为开源项目发布。任何人都可以使用相同的工具复现相同的结果,或者更改参数和配置,得出自己的结论。由于它被设计为在公有云上运行,所以任何人都可以使用。测试中同时使用了公共数据集和生成的数据集。

  • 多重基准测试。 选择单一的基准测试或测试类型只能从一个角度呈现结果。本次基准测试包括了对类 OLTP 工作负载、类 OLAP 工作负载的测试,以及针对事务的专项基准测试。

  • 公平性。 评估公平性是困难的,特别是当某个数据库在给定的测试或基准测试技术上更“原生”时。但如今,这两种数据库都具有相当的“多模态”特性:PostgreSQL 通过其 jsonb 数据类型支持文档,而 MongoDB 通过其连接($lookup)和事务支持来支持“关系型”工作负载。同样,有些基准测试更适合其中一种模型。因此,我们只测试了两种数据库都具备和/或明确宣传的共同功能。

  • 详细分析。 本白皮书包含了对每个结果的详细分析。所有测试在运行时都并行收集了系统监控信息,以捕获可能有助于结果讨论的运行时信息。

OnGres 道德政策

本次基准测试由 OnGres (www.ongres.com) 根据 OnGres 道德政策进行。EnterpriseDB (www.enterprisedb.com) 赞助了这项工作。

该政策保证遵守以下要求:

  • 不偏袒任何一种技术。
  • 不编辑或省略任何结果。
  • 工作赞助方不干预工作的策略、实施或执行。
  • 结果可由外部第三方验证。
  • 所有工作均以最高程度的专业精神和独立性进行。

免责声明:OnGres 是一家精通 PostgreSQL 的公司,因此可能在基于 Postgres 的系统方面表现出更高程度的知识。

作者

项目总监: Álvaro Hernández Tortosa [email protected]

DBA 及开发人员: Francis del Carmen Santiago Cermeño, Emanuel Calvo, Gerardo Herzig, Sergio Alejandro Ostapowicz, Matteo Melli, Jorge Daniel Fernández. {fcsantiago,emanuel,saomec,gerardo.herzig,matteom,jorgedf}@ongres.com

执行摘要:基准测试关键发现

本项目旨在比较 PostgreSQL 和 MongoDB Community Server 在几种不同工作负载下的性能,同时尽量使其模拟生产场景。项目的另一个目标是自动化所有基准测试基础设施,以便第三方可以轻松复现结果。所有软件均已作为开源项目发布。

我们考虑了三种基准测试:

  • 事务基准测试。 一个定制开发的基准测试,灵感来源于 MongoDB Pymongo-Transactions 项目,模拟了一个航空预订系统。
  • 用于 OLTP 工作负载的 Sysbench, 考虑了内存中数据集和 2TB 数据集两种情况。
  • 基于 JSON 的 OLAP 基准测试, 在 PostgreSQL 中使用 jsonb,对一年的 GitHub 归档数据执行四次自定义查询,以衡量查询性能。

在几乎所有的基准测试中,PostgreSQL 11 都比 MongoDB 4.0 更快。吞吐量更高,在某些基准测试中,性能提升从百分之几十到一至两个数量级不等。在测量延迟的基准测试中,PostgreSQL 的延迟也更低。

关于这三个基准测试的更详细发现,总结如下。

事务基准测试

  • 不使用事务的 MongoDB 运行在相当于“读未提交 (READ UNCOMMITTED)”的隔离模式下。这会导致非一致性读、查询中跳过文档或读取非持久化数据等现象发生。避免这些不一致性给应用程序开发者带来了巨大的负担。
  • 使用事务的 MongoDB 提供了“可串行化 (SERIALIZABLE)”的隔离保证。PostgreSQL 则在“读已提交 (READ COMMITTED)”和“可串行化 (SERIALIZABLE)”两种隔离级别下进行了测试。结果发现,与 PostgreSQL 的“读已提交”相比,MongoDB 慢了一个数量级(4-15倍);与 PostgreSQL 的“可串行化”相比(即在完全相同的隔离保证级别下比较),MongoDB 慢了 4-14 倍。PostgreSQL 的“可串行化”比“读已提交”稍慢,但差距不大。PostgreSQL 每秒处理超过 2 万个事务,而 MongoDB 不到 2 千个。
  • PostgreSQL 的 99% 分位延迟低于 50 毫秒,最低可小于 1 毫秒。在所有并发级别下,一半的事务(中位数)耗时小于 10 毫秒。MongoDB 的延迟要高得多:中位延迟比 PostgreSQL 高 5-20 倍;99% 分位延迟则差 7-17 倍。MongoDB 的最低中位延迟在最佳情况下(无并发)为 5 毫秒,并在多种场景下飙升至数百毫秒。

OLTP 基准测试

  • PostgreSQL 的性能在很大程度上取决于并发用户(连接)数。当调整到最佳连接数时,其性能非常出色,但如果连接数过多导致不堪重负,性能会下降。建议使用 PgBouncer 或其他类似的连接池。在这种情况下,PostgreSQL 的性能稳定在接近最大性能的水平。这是生产环境中的推荐模式。
  • 对于 XL 测试(2TB 数据库,无法完全放入内存),PostgreSQL 在所有场景中都优于 MongoDB。如果使用 PgBouncer,其速度快 25-40 倍。
  • 对于内存中测试(4GB 数据库,可完全放入内存),PostgreSQL 的性能比 MongoDB 快 2-3 倍。这适用于 XFS 和 ZFS 文件系统。如果不使用 PgBouncer,PostgreSQL 仅在最佳并发用户数下比 MongoDB 快,但这种用法不推荐。当连接数增加时,其性能会比 MongoDB 慢 3-4 倍。

OLAP 基准测试

一个包含 2 亿条记录的 JSON 文档数据集被分别加载到 MongoDB(原生方式)和 PostgreSQL(使用 jsonb 数据类型)中。两种数据库都创建了适当的索引。

运行了四个查询。在其中三个查询中,PostgreSQL 的速度快了 35-53%,而在另一个查询中慢了 22%。

未来的工作可能包括使用典型的 OLAP/BI 工作负载的 TB 级数据集重新运行此基准测试。还可进行一项新测试,将 JSON 数据转换为 PostgreSQL 中规范化的关系型模式,使用纯 SQL(不使用 jsonb)。根据已有的工作,这可能会使 PostgreSQL 的查询速度显著加快,并且更贴近现实,因为现实中通常会设计一个合适的关系型模式。更多信息请参见详细的 OLAP 结论部分。

方法论与基准测试

引言与目标

本项目旨在对 PostgreSQL 11.1 和 MongoDB 4.0 进行性能比较。主要目标是在相同的数据设计下,分析它们在不同大小的活动数据集上的能力和性能,并测试事务性能。

数据库也无法摆脱进行比较时固有的复杂性,特别是当引擎为不同目的或系统而设计,或具有不同的功能集和驱动程序可用性时。

MongoDB 和 PostgreSQL 在引擎和概念上都如同不同的语言,使得这两个系统完全不同,但有时它们被用于相同的目的,以利用各自的特定功能。

对于 YCSB 和 Sysbench 等通用工具的结果,可能需要持保留态度,因为它们的驱动程序工作方式不同,报告和行为也各异。尽管如此,所有工具的配置都在所有测试中进行了复制,通过编程方式完成,并通过多次运行进行了验证和确认。

某些配置、代码和技术可能会被其他专家改进。虽然结果可能会受到挑战,但它们已经过同行评审和重复测试的验证,这些测试得出了本报告中显示的数据。我们欢迎这种挑战,并希望我们的开源方法能促进这一点。

执行的基准测试

主要执行了三种类型的基准测试:

  • 事务基准测试。 测试 MongoDB 4.0 在运行多文档 ACID 事务方面的新功能,测量性能和延迟。
  • OLTP。 在内存中和更大的(2TB)数据集上,对来自不同数量并发用户的小型操作进行查询性能和延迟测试。
  • OLAP。 在大型数据集上运行 4 个长时间查询的性能(持续时间)。

MongoDB 和 PostgreSQL 都使用了单节点配置,并启用了复制和日志记录。未使用 MongoDB 的分片功能,因为在 4.0 版本中不支持跨分片事务。未来可以将 MongoDB 分片与 PostgreSQL 分片解决方案之间的性能比较作为一个基准测试项目,待 MongoDB 分片支持事务后一并进行。

涉及的技术简介

PostgreSQL 11

PostgreSQL 是一个强大的开源对象关系型数据库系统,它使用并扩展了 SQL 语言,结合了许多可以安全存储和扩展最复杂数据工作负载的功能。PostgreSQL 起源于 1986 年加州大学伯克利分校的 POSTGRES 项目,核心平台至今已有超过 30 年的活跃开发历史。

PostgreSQL 以其久经考验的架构、可靠性、数据完整性、强大的功能集和可扩展性赢得了良好的声誉。该软件因其在提供高性能和创新解决方案方面的一致性而得到开源社区的奉献。PostgreSQL 运行在所有主流操作系统上,自 2001 年起就符合 ACID 标准,并拥有强大的附加组件,如流行的 PostGIS 地理空间数据库扩展。PostgreSQL 成为许多人和组织首选的开源关系型数据库也就不足为奇了。

它默认是事务性的(并且不支持读未提交隔离)。它支持多种数据类型,包括 jsonb(一种二进制格式的 json),这在其中一个模仿 MongoDB 进行 OLAP 查询的基准测试中被使用。

对于内存中的数据集,可以通过 Cstore 在 Postgres 中访问内存中的列式数据存储,作为加速内存中聚合的一种机制。它不包含在原生发行版中,也未在本基准测试中使用。但像许多其他可插拔扩展一样,只需几个步骤就可以将其附加到 Postgres 实例上。

MongoDB 4.0

所有基准测试都使用了 MongoDB Community Server¹ 4.0 版本。

MongoDB 是一个文档存储数据库,允许您将数据存储在灵活的、类 JSON 的文档中,这意味着字段可以在不同文档之间变化,数据结构也可以随时间改变。[^1]

其最强大的特性之一是文档模型能够映射到您应用程序代码中的对象,使数据易于处理。

4.0 版本具有新的聚合能力和事务支持,这丰富了分析功能并允许多文档一致性。

MongoDB 的核心是一个分布式数据库,因此高可用性、水平扩展和地理分布都是内置且易于使用的。

值得一提的是,本文档中测试的引擎是 WiredTiger,这是默认引擎。MongoDB 支持可插拔引擎,其企业版中有一个用于内存数据集的引擎。

云提供商:Amazon Web Services (AWS)

我们选择 AWS 来运行基准测试,因为它被认为是云提供商中最受欢迎的。在为基准测试选择资源时,只选择了稳定的实例类型(中高端机器)。

为了隔离客户端的影响,所有测试都在客户端/服务器基础设施下进行,从而将数据库与来自客户端的任何干扰隔离开来。

自动化基础设施

根据设计,所有基准测试都被设计为自动和编程化运行。我们使用了现代的“基础设施即代码”(IaC)模式。目标是让所有测试都能被第三方轻松复现。所有使用的代码都作为开源项目发布,并存放在两个仓库中:

  • 基准测试平台:用于自动化运行所有基准测试的代码:https://gitlab.com/ongresinc/benchplatform
  • 事务基准测试:https://gitlab.com/ongresinc/txbenchmark

自动化基础设施是用 Terraform 编码的,为所有引擎设置了并排对等的基建设施。从头开始设置只需要在仓库的根目录下放置一个 .env 文件,并运行 make setup 命令来部署可复现的基准测试。

加载和运行阶段都已自动化,因此所有快照都可以在代码中完全复现。此外,结果可以在 S3 端点找到和探索,按目标和基准测试 ID (uuid) 组织。

实例规格

所有基准测试都使用了以下实例类型:

客户端规格

型号 vCPU* 内存 (GiB) 存储 (GiB) 专用 EBS 带宽 (Mbps) 网络性能 (Gbps)
c5.xlarge 4 8 仅 EBS 高达 3,500 高达 10
客户端没有挂载卷。

数据节点规格

型号 vCPU* 内存 (GiB) 存储 (GiB) 专用 EBS 带宽 (Mbps) 网络性能 (Gbps)
m5.4xlarge 16 64 仅 EBS 3,500 高达 10
数据卷:io1,预留 IOPS 数量取决于测试。

所有 m5 实例具有以下规格:

  • 高达 3.1 GHz 的 Intel Xeon Platinum 处理器
  • Intel AVX†, Intel AVX2†, Intel Turbo
  • EBS 优化
  • 增强网络†

MongoDB 配置

唯一设置的配置是数据目录,该目录在独立的卷(/opt/data)上使用。通常,MongoDB 不需要或不会从显著的调优中受益。

PostgreSQL 配置

PostgreSQL 应用了内存和连接设置的基本调优参数,以及一些通常的生产环境值。没有进行重大的调优工作:

max_connections = 600
shared_buffers = 12GB
temp_buffers = 256MB
wal_level = replica
checkpoint_timeout = 15min         # 范围 30s-1d
max_wal_size = 100GB
min_wal_size = 1GB
checkpoint_completion_target = 0.9
wal_keep_segments = 0
seq_page_cost = 1.0                # 在任意尺度上测量
random_page_cost = 1.3             # 我们使用 io1, NVME
effective_cache_size = 36GB
default_statistics_target = 200

请注意,max_connections 被设置得很高,以测试不同数量的传入连接。OLTP 基准测试将详细展示不同并发用户数的结果。

PgBouncer 配置

如 OLTP 基准测试中所述,当处理大量客户端连接时,使用像 PgBouncer 这样的负载均衡器进行连接池管理被认为是 PostgreSQL 的最佳实践。本次基准测试使用的配置如下:

pool_mode = transaction
server_reset_query_always = 0
ignore_startup_parameters = extra_float_digits
max_client_conn = 5000
default_pool_size = 50
min_pool_size = 50
max_db_connections = 50
server_idle_timeout = 20
client_idle_timeout = 20
idle_transaction_timeout = 20

事务基准测试

MongoDB 宣布支持多文档 ACID 事务是其 4.0 版本的主要特性之一,如果不是最主要的话。本基准测试的目标是比较一个默认支持 ACID 事务的系统 PostgreSQL 与使用等效事务隔离级别的 MongoDB 4.0。

基准测试描述

鉴于 MongoDB 对事务的支持相当新,还没有现成的基准测试来检验这一能力。由于行业标准基准测试 sysbench 被用于 OLTP 基准测试,我们最初尝试修改 sysbench 的代码以添加事务支持。但由于该基准测试使用的驱动程序的限制,这一努力没有成功。

为了支持这一分析,我们从头创建了一个新的基准测试,并将其开源:https://gitlab.com/ongresinc/txbenchmark。它是用 Java 开发的,计划基于 MongoDB 已提出的一个测试/基准进行扩展。具体来说,它模仿了《Introduction to MongoDB Transactions in Python》中提出的类似场景,该场景催生了 pymongo-transactions 软件的创建。

该基准测试模拟用户购买机票并生成相应记录的过程。我们没有使用完全合成的数据,而是使用了一些基于 LSV 网站(Laboratoire Spécification et Vérification;飞机和航空公司数据库)上可用数据的真实数据²。这使得基准测试更有可能代表真实世界的场景。[^2]

它使用了最流行的 MongoDB 和 PostgreSQL 的 Java 驱动程序:分别为 mongo-java-driver 和 PgJDBC。实际测试的代码位于两个文件中:MongoFlightBenchmark.java 和 PostgresFlightBenchmark.java。两个数据库都使用自定义脚本生成,静态数据(航班时刻表和飞机信息)在测试运行前会自动预加载。

用户事务被设计得简单,但尽可能地模拟了用户购买机票的真实世界案例。它还被设计为跨越多个文档,以检验 MongoDB 4.0 新的事务功能。该事务由以下命令组成,按顺序在单个事务中执行:

  1. 从表/集合中选择一个随机的航班时刻表。将其与飞机数据进行连接(PostgreSQL 中使用 INNER JOIN,MongoDB 中使用 $lookup)以获取该航班时刻表所服务飞机的容量(座位数)。
  2. 向座位表中插入一个用户 ID(随机)、相关的航班时刻表数据和日期(随机生成)。
  3. 向支付表中插入一个对座位表的引用(用户 ID)和支付金额(航班时长的倍数)。
  4. Upsert (对于 PostgreSQL 是 INSERT … ON CONFLICT DO UPDATE …) 审计表中的相应条目,该表包含航班时刻表、日期和已占用座位数(需要加一)。

分配给座位条目的日期是一个固定的日子,因此在并发事务中尝试更新同一(航班时刻表,日期)对的审计表条目时,会频繁发生冲突。这是预期的,并允许基准测试在高隔离场景下检验事务中止,以及并发和锁定管理。经证实,基准测试的成本并未被此处的争用所主导。在 PostgreSQL 和 MongoDB 上都创建了适当的索引以优化查找表的搜索。

该基准测试在客户端-服务器架构下进行(基于 Java 的基准测试工具作为客户端,服务器为 PostgreSQL 或 MongoDB)。两者位于同一个云 AZ(可用区)。使用的文件系统均为 XFS,采用默认选项。MongoDB 需要启用副本集,即使在单节点上运行,因为这是事务的要求。

基准测试报告了三个主要数据集:

  • 吞吐量(每秒事务数)。测试有时间限制(5分钟)。
  • 事务重试次数,后转换为占总事务数的百分比。
  • 事务的百分位延迟。

MongoDB 事务的限制

MongoDB 事务支持有几个有据可查的操作限制。最相关的有:

  • 事务应在 1 分钟内运行完毕。虽然这个时间可以通过配置延长,但建议不要这样做。基准测试中没有检验长事务,但使用开发的软件³是可以做到的。[^3]
  • 事务的 oplog 不能大于 16MB。这意味着如果事务插入、修改或删除了大量文档,并且这些变更信息——格式化为比原始变更更冗长的 oplog——超过了 16MB,事务就会失败。
  • 写冲突。MongoDB 事务的运行方式使得两个并发事务很可能发生写冲突。当这种情况发生时,MongoDB 会中止其中一个。用户必须重试被回滚的事务。发现在同等隔离级别(SERIALIZABLE)下,MongoDB 比 PostgreSQL 更频繁地出现这种情况。这被列为限制,因为它给用户带来了负担并降低了性能。
  • 分片集群尚不支持事务⁴。这在本基准测试中不相关。[^4]

事务隔离级别的讨论

ACID 中的“I”指定了隔离级别。经典的单节点隔离级别有详细的文档记录。然而,选择一个给定的隔离级别会产生深远的影响,无论是在性能方面,还是更重要的,在数据库将为应用程序提供的数据保证方面。

不使用事务的 MongoDB 在等同于“读未提交 (READ UNCOMMITTED)”的隔离级别下运行。这个隔离级别非常弱,除了明显的影响,如仅限单文档操作、非可串行化操作外,还可能导致严重的不良行为,例如:

  • 不一致的读。你读取的数据不是“冻结”的,可能包含并发操作的结果作为结果集的一部分。
  • 跳过文档。一个正在被更新的文档可能不会出现在查询结果中,而它本应出现。
  • 读取稍后将从数据库中删除的数据。

请注意,即使在单节点配置中也可能发生这些影响。然而,使用事务,MongoDB 确实提供了等同于“可串行化 (SERIALIZABLE)”隔离级别的保证,这是最严格的单节点隔离级别。这并不总是最理想的级别,因为它会降低性能并产生可能需要重新执行的事务回滚。实现其他中间隔离级别将是可取的。与 PostgreSQL 相比,这个隔离级别的性能特性是本基准测试的主要关注点。

下表描述了隔离级别、它们可能关联的数据现象,以及每个数据库支持哪些级别:

数据库 隔离级别 脏读 丢失更新 不可重复读 幻读
MongoDB (无事务) 读未提交 可能 可能 可能 可能
MongoDB (有事务) 可串行化 受保护 受保护 受保护 受保护
PostgreSQL 读已提交 受保护 可能 可能 可能
可重复读 受保护 受保护 受保护 可能
可串行化 受保护 受保护 受保护 受保护

红色单元表示在给定隔离级别下可能出现的数据效应。绿色单元表示在该隔离级别下对数据效应的保护。在表格左侧,白色单元表示支持的隔离级别,黑色表示不支持的。

除非使用事务,否则 MongoDB 会暴露于所有可能的不良数据现象中,而在使用事务时,它则受到保护。PostgreSQL 为用户提供了更细粒度的选择,直接实现了三个隔离级别。

MongoDB 极力强调数据建模的必要性。当然,通过非规范化所有数据并使用数据建模将所有相关数据嵌入“父”文档中,可以避免这些效应。由于即使不使用事务,单文档操作也是 ACID 原子的,因此这可以避免所述现象。然而,如果想要避免这些现象,这就限制了数据建模的选择,并带来了其自身的缺点(数据冗余、需要更多的存储和I/O、查询相关数据的灵活性降低等)。总而言之,MongoDB 用户可以在以下选项中选择:

  • 在没有事务的情况下运行多文档操作。这会将这些操作暴露于已描述的效应中,如不一致的读或交错操作。
  • 在任何地方都使用嵌入数据建模模式,并利用单文档事务是 ACID 的事实。这可能导致其他缺点,如数据冗余。
  • 使用事务,并利用“可串行化 (SERIALIZABLE)”隔离级别提供的强保证。这会带来一些性能下降,正如基准测试结果将显示的那样。

基准测试结果

吞吐量

下图描述了运行事务的 MongoDB 与 PostgreSQL 之间的性能比较:

xychart-beta
    title "Transaction performance (PG @READ COMMITTED)"
    x-axis "并发客户端数" [1, 2, 4, 8, 16, 32, 64, 128, 256]
    y-axis "Transactions per Second (tx/s)" 0 --> 30000
    bar [1007,1936,2873,5445,9815,17278,24171,25636,23402]
    bar "aassd" [203,372,641,1168,1684,1707,1759,1786,1750]
并发客户端数 PostgreSQL TPS MongoDB TPS
1 1,007 203
2 1,936 372
4 2,873 641
8 5,445 1,168
16 9,815 1,684
32 17,278 1,707
64 24,171 1,759
128 25,636 1,786
256 23,402 1,750

有几个相关的结论:

  • 在任何并发级别下,PostgreSQL 都比 MongoDB 快一个数量级(4到15倍)。
  • 服务器有16个CPU,可以看到性能在128个并发线程左右达到峰值,两种情况都是如此。当存在I/O或内存等待时,总有比CPU数量更多的并发空间。但在更高的并发级别下,性能会下降,延迟会显著增加(稍后将显示)。这就是为什么为PostgreSQL使用连接池是一个好习惯⁵。请注意,在任何情况下,PostgreSQL仍然比MongoDB快一个数量级以上。[^5]

有人可能会说,这里 MongoDB 提供了比 PostgreSQL 更高的隔离保证,后者运行在“读已提交 (READ COMMITTED)”隔离模式下。这是事实;但“读已提交”是 PostgreSQL 中最常用的隔离级别,并且在许多情况下提供了足够的保证来防止不良现象,包括本基准测试的事务。

然而,我们也对 PostgreSQL 在可直接比较的隔离模式——“可串行化 (SERIALIZABLE)”下重复了基准测试:

xychart-beta
    title "事务性能 (PG @SERIALIZABLE)"
    x-axis "并发客户端数" [1, 2, 4, 8, 16, 32, 64, 128, 256]
    y-axis "Transactions per Second (tx/s)" 0 --> 30000
    bar [993,1880,2992,5472,9923,17915,24173,23200,20994]
    bar [203,372,641,1168,1684,1707,1759,1786,1750]
并发客户端数 PostgreSQL TPS MongoDB TPS
1 993 203
2 1,880 372
4 2,992 641
8 5,472 1,168
16 9,923 1,684
32 17,915 1,707
64 24,173 1,759
128 23,200 1,786
256 20,994 1,750

结果与之前的基准测试没有显著差异(PostgreSQL 仍然快 4-14 倍)。可以看出,PostgreSQL 在“可串行化 (SERIALIZABLE)”隔离模式下的性能比“读已提交 (READ COMMITTED)”模式慢,这是预期的,因为它提供了更高的保证。但这如何能更精确地解释呢?

在高隔离级别下,并发效应(无论实现方式如何不同)可能导致数据库在两个事务发生写冲突时不得不中止其中一个。这导致用户必须重试事务,从而明显降低性能。在此基准测试期间,两种数据库经历的重试次数是多少?

并发数 PostgreSQL #重试次数 PostgreSQL #重试次数/秒 PostgreSQL 重试/事务百分比 MongoDB #重试次数 MongoDB #重试次数/秒 MongoDB 重试/事务百分比
1 0 0 0% 0 0 0.0%
2 38 0 0.0% 8 0 0.0%
4 162 1 0.0% 30 0 0.0%
8 608 2 0.0% 148 0 0.0%
16 2,152 7 0.1% 453 2 0.1%
32 9,466 32 0.2% 638 2 0.1%
64 25,970 87 0.4% 964 3 0.2%
128 35,545 118 0.5% 1218 4 0.2%
256 36,903 123 0.6% 2903 10 0.6%

该表显示了绝对重试次数、它们的频率(每秒重试次数)以及它们占已处理事务总数的百分比。请注意,在“读已提交 (READ COMMITTED)”模式下,PostgreSQL 没有经历任何重试(这是预期的)。

重试次数随着并发性的增加而增加(冲突的概率增加)。这会降低性能,正如所见,并且也显示出延迟增加。开发的基准测试程序有几个可调参数,可以增加或减少冲突的概率:

  • booking-sleep: 在事务中引入一个休眠。使用值:0。
  • day-range: 指定所有预订发生的时间跨度的天数。使用值:1。

未来的工作可能包括引入一些休眠(以增加冲突)和/或扩大日期范围(以减少冲突)来重复测试。

有趣的是,在最大并发级别下,当以占总成功事务的百分比来衡量时,PostgreSQL 和 MongoDB 经历的重试次数大致相同。由于冲突的概率随着有效处理的事务数量(可能呈指数级)增加,因此可以得出结论,MongoDB 更倾向于重试事务。这与 MongoDB 关于事务和锁定的文档中的预期一致,该文档指出“默认情况下,事务等待最多 5 毫秒以获取事务中操作所需的锁。如果事务在 5 毫秒内无法获取其所需的锁,则事务中止”。此行为可以通过设置 maxTransactionLockRequestTimeoutMillis 参数来更改。

延迟

除了原始吞吐量之外,本次基准测试中用户感知的事务操作延迟是多少?如果用于对延迟敏感的 OLTP 应用程序(如大多数电子商务⁶和许多其他应用程序),用户感知的延迟将是数据库延迟、应用程序和网络层延迟的总和。如果需要多文档事务(如本基准测试模拟的飞机预订系统);和/或当需要“读未提交 (READ UNCOMMITTED)”之外的高隔离级别时,两种数据库观察到的延迟是多少?[^6]

以下图表和数据表示事务延迟及其百分位数,显示的是 PostgreSQL 在“读已提交 (READ COMMITTED)”(默认)隔离级别下的情况。50%表示中位延迟(一半的请求比这个数字快)。95%、99%和99.9%代表相应的百分位数(例如,99.9%的百分位数是每1000个事务中1个事务延迟的下限)。

PostgreSQL 延迟 (READ COMMITTED)

请注意,纵轴是对数刻度。

并发线程数 PostgreSQL 百分位延迟 (READ COMMITTED) (ms) 50.0% 95.0% 99.0% 99.9%
1 1.0 1.1 1.2 1.7
2 1.0 1.2 1.3 2.0
4 1.4 1.7 1.8 3.3
8 1.5 1.8 1.9 4.0
16 1.7 1.9 2.1 5.5
32 1.8 2.3 2.8 7.5
64 2.5 3.8 5.7 11.7
128 4.5 8.4 11.6 26.9
256 9.3 21.1 34.9 184.5

以下是 PostgreSQL 在“可串行化 (SERIALIZABLE)”隔离级别下运行的相同结果。

PostgreSQL 延迟 (SERIALIZABLE)

并发线程数 PostgreSQL 百分位延迟 (SERIALIZABLE) (ms) 50.0% 95.0% 99.0% 99.9%
1 1.0 1.1 1.2 1.5
2 1.0 1.2 1.4 2.2
4 1.3 1.7 1.8 3.6
8 1.5 1.8 1.9 4.1
16 1.6 1.9 2.1 5.3
32 1.7 2.2 2.7 7.0
64 2.5 3.7 5.1 10.1
128 4.8 10.6 14.2 26.7
256 7.4 27.1 40.6 180.4

PostgreSQL 的延迟,无论是“读已提交 (READ COMMITTED)”还是“可串行化 (SERIALIZABLE)”,都相当合理,99%百分位的延迟远低于50ms。对于256个并发线程,每1000个事务中只有1个可能会经历180ms的延迟。在任何并发级别下,所有事务的一半(中位数)都在10ms内处理完毕。最小延迟低于1ms。

正如预期的那样,“可串行化 (SERIALIZABLE)”通常会导致更高的延迟,但并不非常显著。

现在让我们看看 MongoDB 的延迟结果:

MongoDB 延迟

并发线程数 MongoDB 百分位延迟 (ms) 50.0% 95.0% 99.0% 99.9%
1 4.7 8.3 8.9 9.6
2 5.2 8.9 9.6 12.5
4 5.9 11.4 14.2 15.4
8 6.6 12.0 14.8 16.7
16 9.3 15.9 17.6 23.2
32 17.6 33.8 42.2 53.5
64 34.1 66.1 84.4 109.1
128 68.7 131.6 170.9 228.6
256 143.7 278.9 377.5 505.4

MongoDB 的延迟明显比 PostgreSQL 差。MongoDB 的中位延迟比 PostgreSQL 高 5-20 倍;99%百分位则差 7-17 倍。MongoDB 的最小中位延迟在最佳情况下(无并发)为 5ms,而在 256 个并发线程时飙升至超过 100ms(而 PostgreSQL 的中位延迟保持在 10ms 以下)。在 256 个并发线程时,99%百分位延迟飙升至超过四分之一秒,并且只有在 64 个或更少并发线程时才保持在 100ms 以下。

OLTP 基准测试

基准测试描述

本基准测试旨在比较两种数据库在 OLTP(在线事务处理)场景下的性能。在 OLTP 中,通常由大量并发用户执行大量性质较小(通常是由索引支持的单行查询,或单行更改操作)的操作。这可以说是 MongoDB 最受追捧的用例,也是 PostgreSQL 最常用的用例之一。

为了执行此基准测试,我们使用了著名的 sysbench 工具。Sysbench 是一个基于 LuaJIT 的可编写脚本的多线程基准测试工具,可通过 LUA 脚本进行扩展。它最初是为执行系统基准测试而开发的,并最初支持 MySQL。现在它原生支持 PostgreSQL,并且有一个 sysbench-mongodb-lua 脚本可以封装并使其在 MongoDB 下工作。有人可能会说,两种系统的 Lua 驱动程序质量可能不同。然而,基准测试的运行是可靠和一致的,因此我们使用了这个在数据模型方面具有相关性且支持两种数据库⁷的少数基准测试之一。[^7]

使用此工具进行的基准测试并未利用 MongoDB 中的事务功能。它们也没有执行在关系型模式下可能表现更好的复杂查询。Sysbench 创建一个具有用户定义数量的表/集合(以前缀 sbtest* 创建)的模式。它还在它们的 ID 列上创建相应的索引。文档或行结构具有 id、k(整数)、c(稍大的文本)、pad(文本)。请注意,此测试在 MongoDB 中不使用事务(因此实际上在“读未提交 (READ UNCOMMITTED)”隔离级别下运行),而 PostgreSQL 在其默认的隔离级别“读已提交 (READ COMMITTED)”下运行。

我们进行了几个不同的测试,涵盖了几个不同的分析维度:

  • 数据集大小(小型数据集,2-4GB,可放入内存,称为“FIT”;以及更大的数据集,约 2TB,显然无法放入内存,称为“XL”)。
  • FIT 和 XL 在自动化平台中都使用了 SSD 支持的存储,分别具有 1000 和 5000 的保证 IOPS (io1)。这样做是为了模拟对存储成本敏感的真实世界场景。
  • 文件系统(XFS 或 ZFS⁸)。[^8]
  • 读/写工作负载分配。考虑了两种场景:95/5 和 50/50(读与写)。
  • 几种不同数量的数据库连接,以模拟不同级别的并发。

总共,我们运行了数十到数百个测试,全部都是自动化的,包括结果收集,这要归功于作为本项目一部分开发的自动化基准测试平台。

初步考量

磁盘上的数据集大小

在 sysbench 基准测试的数据加载阶段之后,MongoDB 和 PostgreSQL 两个数据集的大小如下:

引擎 XFS ZFS
MongoDB FIT 2.5 GB 1.9 GB
PostgreSQL FIT 6.1 GB 4.5 GB
MongoDB XL 2.0 TB 1.8 TB
PostgreSQL XL 2.5 TB 2.1 TB

这里有两个快速结论:对于相同的数据集,PostgreSQL 需要比 MongoDB 更多的磁盘空间;ZFS 提供了良好的压缩效果,即使在 MongoDB 上也是如此。

PostgreSQL 连接数与连接池

PostgreSQL 的架构基于进程模型,每个新连接都会运行一个新进程。由于一个进程最多由一个 CPU 核心在同一时间服务,因此 PostgreSQL 数据库的理论最大连接数等于核心数,前提是连接因高活动性而充分利用 CPU 核心。对于本次基准测试,使用具有 16 个 CPU 的 m5.4xlarge 实例,这意味着最大连接数为 16。

实际上,连接数通常是核心数的倍数。尽管所有核心都在使用一个 CPU,但它们经常在等待 I/O(数据库经常这样做)甚至其他中断。这允许操作系统通过分时机制换出和换入其他可以利用 CPU 的进程,而其他进程则在等待。最重要的是,如果一个给定的连接没有充分利用会话(例如,不是连续执行查询,而是在等待应用程序执行其他任务),那么可以向数据库抛出更多的并发连接。

在此演示中,OnGres 提出了一个公式来估算 PostgreSQL 数据库的最佳连接数,基于上述因素:

$$ 连接数 = \frac{核心数}{连接有效利用率%} \times 伸缩因子 $$

其中,如果连接只是打开、非交互式地执行查询然后释放,则利用率百分比接近 100%,最典型的是 50-75%;伸缩因子是一个取决于有效 I/O 和其他等待的因素,通常在 2-8 之间(在 I/O 更好的系统上,如 NVMe,则较小)。

对于本次基准测试,我们假设利用率因子很高(90%;基准测试在持续执行查询),因此根据此公式,最佳性能可能在 27 到 53 个连接之间实现。

那么,这为什么重要呢?事实证明,这决定了最佳性能。对于 sysbench 基准测试,以 50 为增量进行测量,实验确定 50 个连接是性能最佳的连接数(这与先前公式的预期相符)。

因此,以更高的连接数运行可能会降低性能。降低多少?这取决于工作负载,但对于某些模式,可能会很显著。有两个主要因素导致这种效应:

  • 操作系统调度开销和缓存抖动。 前者只有在连接数非常高时才明显,可能在数千的量级。但可以通过上下文切换等方式间接测量。
  • 缓存抖动 是由 PostgreSQL 与共享缓冲区区域的工作方式引起的。这个区域是所有进程读/写缓冲数据的地方,并遵循 LRU 算法。如果这个区域稀缺(与整个数据集相比),许多进程可能会最终争用它,换入换出可能仍被其他进程需要的数据。换句话说:这个缓存区域由较少的进程同时共享,尽可能避免一个进程对另一个进程的不良影响。
  • 每个进程的本地目录缓存。 每个 PostgreSQL 进程都会缓存一些目录表信息。这大约占用了每个进程几 MB 的 RAM(7-10 MB)。如果连接数配置为数千,这很快就会累积到数十甚至数百 GB 的 RAM,再加上数据库操作所需的所有其他内存。

因此,对于 PostgreSQL 来说,正确调整连接数并使其接近理想的操作点非常重要。sysbench 基准测试的初步测量表明,当客户端连接数增长到最佳范围的大倍数时,PostgreSQL 的性能可能会下降多达一个数量级。

这是否意味着 PostgreSQL 不能处理更多的并发用户?绝对不是。关键是使用连接池。实现这一点的标准方法是使用像 PgBouncer 这样的 PostgreSQL 连接池。连接池可以维持到数据库的较低连接数(在接近最佳范围的连接数上运行),并且要么将多余的事务排队(但仍然接受它们;这称为会话模式),要么将它们多路复用(称为事务模式),以创造出更多可用数据库连接的净效应。与传统观念相反,这不会比直接连接到 PostgreSQL 的更高数量连接增加延迟:虽然一些用户连接由于等待而增加了一些延迟,但正在进行的查询处理速度显著加快,弥补了这一点。总的来说,使用连接池相比直接连接到 PostgreSQL 有以下效果:

  • 使 PostgreSQL 在几乎任何数量的传入连接下都保持在最佳范围附近运行,有效地平滑了性能变化。
  • 将查询延迟保持在合理范围内,当连接数非常高时不会呈指数级增长。
  • 减轻由于本地进程缓存引起的内存压力。
  • 显然,允许任意数量的并发连接,使用户几乎感觉不到从连接池到数据库存在一个最佳连接数。

目标之一是模拟接近生产环境的基准测试条件。这里解释了,运行没有连接池的 PostgreSQL 是一个众所周知的反模式。因此,为 OLTP 基准测试呈现的结果要么包括 PostgreSQL 前面的 PgBouncer,要么,如果呈现原始结果,则是在 PostgreSQL 运行最佳的连接数(确定为 50)下,因为这会是生产中使用的设置,前面有一个连接池。显然,PostgreSQL 和 MongoDB 总是以相同数量的并发连接进行比较。

那么 MongoDB 呢?它的性能是否随连接数而变化?由于 MongoDB 的架构使用了更轻量级的线程模型,这并非必要。最重要的是,MongoDB 客户端驱动程序包含一个自动连接池。因此,在 MongoDB 前面使用显式连接池是不必要的,并且结果在不同数量的连接用户中更稳定。

基准测试结果

每个配置(测试类型、文件系统、加载选项和连接数)的基准测试都运行了五次,取中间三个值的平均结果。

可容于内存的数据集 (几 GB)

基准测试在 XFS 和 ZFS 文件系统上都进行了。对于 50 个连接的结果,比较 PostgreSQL 和 MongoDB,观察到的性能如下:

内存中数据集 (FIT) 每秒操作数 (50个用户)

XFS 95/5 XFS 50/50 ZFS 95/5 ZFS 50/50
PostgreSQL 2,603 2,569 2,424 2,301
MongoDB 929 924 909 730
比率 (速度提升) 2.8 2.8 2.7 3.2

这个结果可能令人惊讶。人们可能期望 MongoDB 会胜过 PostgreSQL,因为众所周知,当数据适合内存并且访问有索引时,MongoDB 在 OLTP 操作上表现良好。但 PostgreSQL 的速度快了 2.7-3.2 倍,具体取决于测试和读/写工作负载。有两个可能的原因:

  • sysbench 执行的操作不仅仅是读取或插入单个记录,而是更像一个接近现实的场景,需要在多个表/集合中处理几比特的数据。请注意,尽管 sysbench 组合了多个操作,但它不使用事务(只是分组的操作)。
  • 如前所述,通过使用 PgBouncer,PostgreSQL 已被驱动到其最佳性能点。这对 PostgreSQL 来说并非不公平的优势:MongoDB 驱动程序实际上为 MongoDB 提供了同样的好处;并且大多数生产环境中的 PostgreSQL 部署都使用 PgBouncer。

非常值得注意的是,MongoDB 的 CPU 使用率明显更高。下图显示了 CPU 使用率随时间的变化,在一个包含了不同连接数测试的图表中:

CPU 平均负载 - Mongo vs Postgres

我们已经详细解释了 PostgreSQL 的性能在很大程度上取决于前面是否有一个连接池,或者是否选择了正确的连接数。为了完全透明,下图和数据表显示了在 XFS 上进行 50/50 读/写负载的 FIT 基准测试中,PostgreSQL、MongoDB 和 PostgreSQL + PgBouncer 在不同连接数下的性能:

内存中数据集 (FIT) 每秒操作数 - 不同连接数

并发连接数 50 100 150 200 250 300
PostgreSQL 2,569 332 183 147 127 121
MongoDB 924 889 872 867 856 828
PostgreSQL+PGB 2,779 2,714 2,880 2,881 2,832 2,860

一旦连接数超过参考公式的数值,PostgreSQL 的性能明显大幅下降。然而,一旦 PgBouncer 在 PostgreSQL 前面,它就抵消了这种效应,使 PostgreSQL 在任何连接数下都能在其最佳性能点运行。对于这个测试,如果完全不考虑连接池,MongoDB 在除了最佳性能点之外的所有情况下都会显著胜出,而在最佳性能点,PostgreSQL 会以显著的优势击败 MongoDB。

使用 PgBouncer 是否对 CPU 消耗有影响,或者是否有任何其他不利影响?就 CPU 使用而言,略有增加,但并不显著,特别是与 MongoDB 相比:

CPU 平均负载 - 使用 PgBouncer

ZFS 上的 MongoDB

在观察性能测试随时间演变的过程中,我们注意到 MongoDB 性能的突然下降,尤其是在进行 50/50 测试时,这些下降在最终平均结果中很难察觉。它们看起来像这样:

ZFS 上的 MongoDB 性能 (50/50)

MongoDB 由灰色条表示。其他条与此分析无关。可以看到,性能存在一些周期性的下降,接近于 0(Y 轴是每秒操作数),这可能是由于某些或多或少的周期性停顿,或“停止世界”的磁盘刷新。

MongoDB 文档中没有直接提到这种次优行为,尽管 WiredTiger 强烈推荐 XFS 而不是其他文件系统——提到了 ext4⁹。为了验证这种行为,我们进行了一个类似的测试来记录操作延迟,以毫秒为单位,在 95% 百分位(意味着 5% 的操作比显示的数字慢),它们有许多周期性的尖峰:[^9]

Mongo FIT on ZFS 50/50 - 95 百分位延迟

灰色和绿色的条代表相同的基准测试,只是在两次不同的运行中执行。结果是相同的:或多或少的周期性停顿直接转化为非常尖峰的 95% 百分位操作延迟。

由于这一发现,ZFS 的结果可能存在问题,因此我们决定在大型数据集基准测试中不使用 ZFS。

数据集超过可用内存 (2 TB 数据集)

在经历了 MongoDB 在 ZFS 上的问题后,此测试仅在 XFS 上进行。在 PostgreSQL 方面,它在 50 个连接时表现也更好,就像内存中测试一样。对于这个连接数,比较 PostgreSQL 和 MongoDB,观察到的性能如下:

磁盘上数据集 (XL) 每秒操作数 (50个用户)

95/5 50/50
PostgreSQL 2,433 2,211
MongoDB 96 51
比率 (速度提升) 25.3 43.4

可以看出,PostgreSQL 仍然比 MongoDB 快得多。但差距显著扩大:从 2-3 倍扩大到 25-40 倍。

在这里,连接数对 PostgreSQL 的影响是什么?

数据集 XL (2TB) 每秒操作数 - 不同连接数

并发连接数 50 100 150 200 250 300
PostgreSQL 2,433 334 191 138 116 123
MongoDB 96 81 82 86 96 48
PostgreSQL+PGB 2,709 2,686 2,707 2,670 2,827 2,839

从结果中可以得出以下结论:

  • 无论数据集大小如何,PostgreSQL 的性能都非常相似。这意味着缓冲算法非常好,能够将相关数据集保留在内存中。
  • 当数据不适合内存时,MongoDB 的性能会显著下降。
  • 对于此基准测试,即使 PostgreSQL 在远离其最佳状态(在更高的连接数下)运行时,也比 MongoDB 快。

OLAP 基准测试

基准测试描述

OLAP 代表在线分析处理,通常用于报告、商业智能和数据挖掘。此基准测试旨在提供 MongoDB 和 PostgreSQL 在涉及 OLTP 工作负载产生的数据的简单报告场景中的性能比较。

使用的数据是来自 GitHub Archive 的 JSON 文档。这些 JSON 数据被导入到 MongoDB 和 PostgreSQL(使用 jsonb 数据类型)中。为了导入数据,我们开发了一套脚本到一个自动化平台中,该平台将在一个干净且受控的环境中,从头开始在不同数据大小上重复测试数次。所有的源代码、自动化和测试细节都与 OLTP 基准测试相同(包括实例大小和特性:实例为 m5.4xlarge,具有 16CPU 和 62G RAM)。

GitHub Archive 是一个记录公共 GitHub 时间线、存档并使其易于进一步分析的项目。GitHub Archive 提供 20 多种事件类型,从新的提交和 fork 事件,到开设新工单、评论和向项目添加成员。GitHub Archive 文件通过 HTTP 提供。使用此数据集是因为它提供了显著的优势:

  • 无需生成 JSON 数据。
  • 免费和公开的信息和访问。
  • 真实的用信息来开发、进行聚合分析和运行 OLAP 查询。
  • 足够的信息量来填充数据库并运行有意义的查询。

每个存档都包含由 GitHub API 报告的 JSON 编码事件。可以下载和处理原始数据——例如,编写一个自定义聚合脚本并将其导入数据库,就像我们在这里所做的那样。

事件和 JSON 键的列表非常广泛,请参阅 Schema.js 和 Event Types 以深入了解 JSON 文档和 GitHub API。关于 JSON 数据,相关的是:

  • “payload” 字段的内容因事件类型而异,并可能随时由 GitHub 更新。
  • “other” 字段的内容是一个 JSON 字符串,其中包含 GitHub 提供的所有其他数据。

用于 OLAP 基准测试的数据集是从 2015-01-01 到 2015-12-31(1 年时间范围)的 GitHub Archive 中导入的,代表了 212,222,001(2.12 亿)条记录。

查询范围与投射

有了关于 GitHub Archive 的所有这些背景知识,我们编写了 4 个查询来运行 OLAP 基准测试,如下所示:

  • 查询 A: 返回按最多开放问题排序的仓库。
  • 查询 B: 返回按数量排序的 git 事件类型(最频繁的在前)。
  • 查询 C: 返回前 10 名最活跃的参与者。
  • 查询 D: 返回评论超过两条且具有特定事件类型的仓库,按平均评论数降序排序。

作为最佳实践,我们挑选的查询相当准确地代表了真实用例场景,其中数据库被要求提供相关的 BI(商业智能)信息。以下是在被测系统中执行这些查询的实际代码。

查询 A

返回按最多开放问题排序的仓库。

PostgreSQL:

SELECT data->'repo'->>'name', count(*)
FROM github2015
WHERE (data->>'type') = 'IssuesEvent' AND (data->'payload'->>'action') = 'opened'
GROUP BY 1
ORDER BY count DESC

MongoDB:

db.github2015.aggregate(
[
    { $match: {
        $and: [ { type: "IssuesEvent"}, {"payload.action": "opened" } ]}
    },
    { $group: { _id: "$repo.name", total: { $sum: 1 }}},
    { $sort: { total: -1 } }
],
{ allowDiskUse: true, cursor: { batchSize: 100000000 } }
)

查询 B

返回按数量排序的 git 事件类型(最频繁的在前)

PostgreSQL:

SELECT data->>'type', count(*)
FROM github2015
GROUP BY 1
ORDER BY count DESC

MongoDB:

db.github2015.aggregate(
[
    { $group: { _id: "$type", total: { $sum: 1 } } },
    { $sort: { total: -1 } }
],
{ allowDiskUse: true, cursor: { batchSize: 100000000 } }
)

查询 C

返回前 10 名最活跃的参与者

PostgreSQL:

SELECT data->'actor'->>'login' as actor, count(*)
FROM github2015
GROUP BY actor
ORDER BY count DESC
LIMIT 10

MongoDB:

db.github2015.aggregate(
[
    { $group: { _id: "$actor.login", events: { $sum: 1 } } },
    { $sort: { events: -1 } },
    { $limit: 10 }
],
{ allowDiskUse: true, cursor: { batchSize: 100000000 } }
)

查询 D

返回评论超过两条且具有特定事件类型的仓库,按平均评论数降序排序。

PostgreSQL:

SELECT data->'repo'->>'name',avg((data->'payload'->'issue'->'comments')::int) as comments
FROM github2015
WHERE data->>'type' = 'PushEvent'
AND (data->'payload'->'issue'->'comments')::int IS NOT NULL
GROUP BY 1
ORDER BY 2 DESC

MongoDB:

db.github2015.aggregate(
[
    { $match: { "type": "PushEvent", "payload.issue.comments": { $gt: 2 }}},
    { $group: { _id: "$repo.name", avg: { $avg: "$payload.issue.comments" } } },
    { $sort: { avg: -1 } }
],
{ allowDiskUse: true, cursor: { batchSize: 100000000 } }
)

PostgreSQL 具体配置

Postgres 的 work_mem 调整为 1GB,shared_buffers 调整为 12GB。数据集最终占用了大约 340G 的磁盘空间。

对于本基准测试中的查询集,创建了以下索引:

CREATE INDEX ON github2015 ((data->'repo'->>'name'));
CREATE INDEX ON github2015 ((data->'payload'->>'action'));
CREATE INDEX ON github2015 ((data->>'type'));
CREATE INDEX ON github2015 ((data->'actor'->>'login'));
CREATE INDEX ON github2015 ((data->'payload'->'issue'->'comments'))

当前基准测试中使用的查询位于基准测试平台中相应的脚本中,链接在此。

MongoDB 具体配置

数据集所需的磁盘空间为 206GB。请注意,MongoDB (WiredTiger) 的压缩显著减少了与 PostgreSQL (340GB) 相比的磁盘空间需求。

与 PostgreSQL 等效地,创建了以下索引:

db.github2015.createIndex( {type:1} )
db.github2015.createIndex( {"repo.name":1} )
db.github2015.createIndex( {"payload.action":1} )
db.github2015.createIndex( {"actor.login":1} )
db.github2015.createIndex( {"payload.issue.comments":1} )

基准测试查询可以在平台仓库中找到。

基准测试结果

查询运行数次并取平均值。总查询执行时间以秒为单位测量。越低越好。对于给定的数据集(2.12 亿条记录),结果如下:

查询 PostgreSQL MongoDB 速度提升百分比 (S-F)/S
查询 A 1h 28m 15s 1h 8m 44s -22.11%
查询 B 41m 03s 1h 13m 3s 43.80%
查询 C 48m 37s 1h 14m 25s 34.66%
查询 D 1h 07m 23s 2h 23m 44s 53.12%

根据以上结果,可以看出 PostgreSQL 在三个查询中比 MongoDB 快 35-50%,仅在第一个查询中慢了 22%。

对于此测试,使用了整个 2015 年的数据。在分阶段测试中,使用几个月的数据时,当数据集小到可以放入内存(几 GB)时,MongoDB 在所有查询中都优于 PostgreSQL。这不是一个令人惊讶的结果,但在生产环境中并不现实,因为 OLAP 数据量级通常在几十 TB 甚至更多。然而,如果您的数据集适合内存,请考虑使用 MongoDB 以获得更快的性能。相反,当数据集大于 RAM 时,随着数据集变大,PostgreSQL 的性能优势会增加。

一个典型的 BI(商业智能)应用程序会包含需要与主表连接的查找表(通常很小)(这在事务基准测试中已经解释过,在 MongoDB 上很慢),或者它们需要被非规范化到主表中(这会增加 OLTP 操作的更改性能和磁盘空间)。预计在这种情况下,PostgreSQL 的表现会比 MongoDB 更好。此外,如果使用像 Tableau、MicroStrategy 或 Qlik 这样使用 SQL 的 BI 工具(正如生态系统中的大多数工具一样),则需要使用 MongoDB 的 BI 连接器。但该产品不是开源的,其许可证明确禁止发布基准测试结果。

基于此讨论,提出了一系列可能的未来附加基准测试,以分析此 OLAP 基准测试的其他向量:

  • 考虑为 PostgreSQL 使用启用压缩的 ZFS。这可以减少 PostgreSQL 使用的磁盘空间,并在所需 I/O 方面进行更公平的比较。预计 PostgreSQL 的性能会更好。MongoDB 可能不会(WiredTiger 已经有压缩)。
  • 将 PostgreSQL 中的 jsonb 查询转换为一组规范化的关系表。类似于 (www.torodb.com) 自动执行的操作,基于 jsonb 的查询可以通过一组视图或物化视图转换为关系表,进行良好的规范化。然后,可以通过基于结果模式进行特定的连接,以“纯 SQL”重写查询。预计在这种情况下,PostgreSQL 中的查询将运行得快得多,比 MongoDB 快 1-3 个数量级。有关更详细的解释,请参阅 ToroDB 的这篇帖子,其中包含详细的基准测试数据¹⁰。[^10]
  • 用一个几 TB 大小的数据集重复当前的基准测试。如果它重复观察到的趋势,PostgreSQL 的性能优势应该会扩大。

脚注

  1. MongoDB 企业订阅协议明确禁止“公开传播性能信息或分析,包括但不限于基准测试结果”(见 https://www.mongodb.com/subscription-and-services-agreement-february-2014 § 3.3.f)
  2. 原始基准测试生成了非常简单的数据。特别是,航班号被硬编码为一个常量值,分配的座位是纯随机的。对于开发的基准测试,使用了一个单独的表(在 MongoDB 中是集合)来加载来自 LSV 网站的包含航班数据的真实数据,以及另一个包含飞机数据的表。数据仍然很小(航班时刻表 1.5 万行,飞机数据 200 行)。
  3. 该软件支持一个参数 -booking-sleep,它使事务休眠一定秒数。此处显示的基准测试结果中,该值设置为 0。
  4. MongoDB 宣布在即将推出的 MongoDB 4.2 中支持分片集群上的事务。
  5. 在 OLTP 基准测试部分,有关于 PostgreSQL 需要连接池的更深入讨论。
  6. 一个通常引用的数字是,每 100 毫秒的延迟会使亚马逊损失 1% 的销售额。
  7. 未来的工作可能包括从头编写一个支持两种数据库的 OLTP 基准测试。它可以基于一个真实的用例(如社交数据参考架构,一个用于性能回归测试的 MongoDB 应用程序),但要利用每个数据库的真正能力,就好像程序是为每个数据库专门设计的一样。这将是一个公平的基准测试,因为它使用相同的操作和数据,但可能会改变内部表示和数据建模,以适应每个数据库的最佳模式和能力。
  8. ZFS 文件系统在一个简单的配置中使用,启用了压缩,但没有配置读写缓存,否则可以利用实例上可用的临时 NVMe 磁盘。这留待未来的基准测试。主要原因是,spot 实例上临时磁盘的路径似乎会随时间变化,使得测试自动化变得更加困难。
  9. 详细信息可以在 MongoDB 生产说明中找到。
  10. 将 JSON 数据转换为关系型模式的好处可能相当显著,特别是对于 OLAP 类型的查询。这种规范化过程在几个方面受益:
    • 只需扫描“集合”的一部分即可满足给定的查询,而如果数据是 JSON 格式,则会扫描整个集合。假设一个查询正在对一个深度嵌套的子文档中的某些字段进行聚合。在关系型模式中,这个深度嵌套的子文档将映射到一个单独的表,该表只占整个数据的一小部分。在关系模型中,只有这个较小的表会被扫描以进行查询,从而可能导致查询效率大大提高。
    • 作为前一点的附加好处,如果这些派生表(代表一个嵌套的子文档)并非在原始 JSON 数据的所有行中都存在,那么这些表的基数也将低于原始 JSON 数据中的行数。从而导致更快的查询——扫描更少甚至少得多的数据行。
    • 存储和 I/O 节省:键经常被重复。一旦被分解到模式中,就可以节省一部分存储和 I/O。

PERFORMANCE BENCHMARK POSTGRESQL / MONGODB