Jsonb:分享几个关于性能的故事

FASTJSON v2深度解析:架构革新、安全加固与性能优化实战 JSON作为现代应用数据交换的核心格式,其处理库的性能与安全性直接影响系统效率与稳定性。传统JSON库在追求极致性能时,常面临安全漏洞与架构僵化的挑战。FASTJSON v2通过引入多格式内核架构,在底层实现了JSONB二进制格式与标准文本格式的分离,从而在原理层面兼顾了高性能与灵活性。其技术价值在于,JSONB格式通过紧凑编码与字段映射,显著减少了序列化体积与耗时,尤其适用于高并发微服务通信与缓存场景。同时,v2版本彻底重构了安全防御体系,默认关闭AutoType并强制使用安全白名单机制,从根源上杜绝了反 阅读详情

原文:Jsonb: few more stories about the performance
作者:Dmitry Dolgov
译者:roy

译者注: 作者用轻松诙谐的语气结合详细的步骤和图表,生动地比较了PostgreSQL,MySQL和MongoDB在处理不同文档时候的性能。

因此, 实际上并没有什么 “标准” 基准能告诉你哪种技术对你的应用程序是最佳的。只有你自己的需求、数据和基础架构才能告诉你哪些是你需要了解的。

有一段时间,我无法停止做一些有趣的,或者有用的,甚至怪异的(每次做一个)基准测试, 用来揭示在关系数据库的世界中如何应用面向文档的一些方法细节。最后我决定用我的 博客文章来分享这些大量的细节。所以欢迎来到基准俱乐部, 在这里我们将讨论公平地比较不同数据库性能需要做些什么。你可能猜到了, 基准俱乐部的第一条准则是永远不要共享一个可重现的基准。但我们认定自己是坏蛋工程师, 所以今天我们要打破这个规则。

目标

比较所有的存储和处理文档形式的数据库解决方案是不可能的(虽然人们通常期望这样),所以我将范围限制在PostgreSQL,MySQL和MongoDB:

  • PostgreSQL - 因为它是一个领先的数据库,而且是我生命中的一部分(译者注:作者 Dmitry Dolgov 是PostgreSQL 开源项目贡献者)。

  • MySQL - 对二进制json也有相当不错的实现,所以将PostgreSQL与其最接近的竞争对手进行比较是很有趣的。

  • MongoDB - 最流行的NoSQL数据库之一。对于我来说,它是“面向文档”的同义词。

环境

不幸的是,我没有任何裸机来测试性能。所以我所有的测试都是使用AWS EC2进行的,这样的测试有其特有的优点和缺点:

  • 你可以非常容易地重现相同的结果。
  • 许多公司使用AWS作为主要平台来运行他们的基础设施,所以我的结果可能更加相关。
  • 但是使用EC2进行性能测试有一些潜在影响。

我的典型基准环境有两个EC2实例,一个用于工作负载生成器,另一个用于数据库。我使用的是 m4.large 实例类型和一个gp2 ELB卷存储,使用默认的Ubuntu 16.04 AMI映像,因此可以使用HVM虚拟化。两个实例都在同一个可用区域,VPC和安置组,所以我们消除了重要的网络问题。

但对结果充满信心是不够的。还有一些有趣的和性能相关的EC2的使用 细节(例如,通常建议禁用超线程以获得更好的延迟),并且通常基准测试是相当危险的区域(只有疯狂的人才加入Benchmark Club,因为每个人都认为他比你聪明,而且没人愿意帮忙)。为了至少能部分地缓解这些可能的问题,并确保我的测试更具可重复性,我尽可能地小心 - 几乎每个用例都至少做了四轮相同的基准测试(通常甚至更多)。

我使用了YCSB作为测试工具,这是一个非常有名的测试NoSQL数据库性能的工具。它提供了许多有趣的工作负载类型,但不幸的是只涉及结构相当简单的文档。更确切地说,YCSB使用简单文档,仅有一定数量的关键字和相应的值,这与我的观点并不相近。这就是为什么我也创建了这个工具的一个分支,在那里我介绍了创建具有一些复杂结构的文档的可能性,以及PostgreSQL(jsonb)和MySQL(二进制json)的驱动程序(警告:我应该指出,它不是让我感到自豪的代码,但你懂的 - “它对我很有用”)。

为了创造一个环境,我有一个 ansible手册(对于那些在各种会议上看到我介绍这个主题的人来说,这本手册总是公开的,但因为没有真正的反馈,过了一段时间之后,我就没把它放到幻灯片里),它接受一些参数,如EC2的键值,可用区等,然后创建所需要的实例并配置它们,最后开始数据加载和实际测试。你需要手动做的唯一事情就是为你的数据库创建包含所有安全组的子集(我只是懒得自动化)。不幸的是,Ansible自身在某个时候成了一个问题,因为它仍然使用Python2,而最新版本的Ubuntu并不提供Python2,所以需要一些技巧来使用它。

除了运行数据库本身之外,每个实例还利用sar工具收集系统度量值(以及来自pgview或mongotop的一些与数据库相关的度量值),以便测试后使用。

几点说明。我不确定现在的情况,但是当我编写所​​有这些脚本的时候,在最新版本的MongoDB的Ubuntu的最新版本中没有提供适当的服务。这意味着我必须再添加一个模板并自己创建这个服务。

另一件让我度过数个不眠之夜的好事就是所谓的“无人值守升级”。这个Ubuntu服务有时可能会唤醒并开始更新系统。我不需要这个额外的开销(虽然很小),但有时软件包会因为安装被锁死从而导致测试失败,所以我禁用了它。一般来说,为了防止碎片化的测试,添加Ansible重试来更新软件包部分是完全有意义的,例如:

until: update_result.stderr == ""
retries: 10
delay: 1
ignore_errors: yes

还有一件让你的生活更轻松的事情就是添加

host_key_checking = False # 我们知道我们的主机
timeout = 60  # 或更多
pipelining = True # 减少SSH操作次数

到你主机上的 ansible.cfg 文件中
(理论上 pipelining 可能破坏 sudoers 配置的兼容性,但是在我的测试中,我从来没有经历过类似的事情)。

为了节省一些金钱和时间,在几乎所有的测试里,我使用已经准备好所需要一切的AMI镜像。但是,如果将测试数据预加载到此镜像中,则必须明白,在测试开始时,缓存中显然没有数据。

配置

在我的测试中,我使用了各种数据库版本:

  • PostgreSQL 9.6.3/10
  • MongoDB 3.2.5/3.4.4
  • MySQL 5.7.9/8.0.3

为了简单起见,你可以假设我们正在使用最新的稳定版本,我只会在一个特定的版本有一些性能差异的时候才会提到它。

在处理这些数据库的配置时,我试图只调整那些与实例参数或工作负载性质直接相关的选项。其它需要更详细的配置选项则留给我的下一个研究。这让我有以下重要的选项:

  • PostgreSQL

    • shared_buffers
    • effective_cache_size
    • max_wal_size
    • checkpoint_completion_target
  • MySQL

    • innodb_buffer_pool_size
    • innodb_log_file_size
  • MongoDB

    • write concern level
    • checkpoints
    • eviction
    • transaction_sync(只是出于好奇,并不真的推荐使用)

几个重要的注意事项:

  • 所有的测试都使用正确的数据一致性(这意味着MongoDB要使用 write concern 级别的journal)
  • SSL被禁用(这对PostgreSQL和MongoDB很容易,而对MySQL驱动程序,我不得不手动禁用它)
  • 在PostgreSQL和MySQL使用了Prepared Statement
  • 所有数据库都以单一实例的形式使用。对于MongoDB来说,这实际上是非常不自然的,通常你想使用复制来获得最终的一致性。但同时这也允许我们在相似的条件下测试所有涉及到的数据库。现在我正在开发测试套件的第二部分来测试数据库集群,所以你可以将本文作为第一步来考虑,尽管如此,它本身仍然非常有趣。

另外对于所涉及的文档类型,使用默认的YCSB,我可以定义:

  • “简单”或“小”文档 - 10个键和值,每个值是100个随机字符
  • “大”文档 - 100个键和值,每个值是200个随机字符

而使用我的分支,我也可以定义:

  • “复杂”文档 - 100个键和值,组成3个嵌套级别的树,每个值为100个随机字符

读负载

YCSB提供了几种有趣的工作负载类型来模拟真实世界的情况。我们从简单的 WorkloadC 开始,它由100%的读取查询组成。每个读取查询通过其ID来获取单个文档,所以我们需要讨论如何索引文档:

  • PostgreSQL - 我们可以在文档中索引单个路径/多个路径(使用常规功能索引)或所有路径(使用GIN索引)。

  • MongoDB - 我们可以在文档中索引路径或多个路径。

  • MySQL - 没有直接支持索引二进制JSON,但我们可以创建一个虚拟列,并像往常一样索引它。所以文档内的单个或多个路径是可以支持的。

让我们尝试使用“默认”索引方法进行简单的性能测试,这意味着我们将为PostgreSQL索引整个文档,在MongoDB中只索引ID,而在MySQL中只有一个独立的虚拟列索引ID。

此图表示所有数据库的 WorkloadC 下的吞吐量。x轴表示正在查询我们的数据库的客户数目, 所以基本上它是一个并发级别。y轴表示吞吐量值。

在此图上,你可以看到相同测试的第99百分位(99%)的延迟。

我敢打赌,你已经对这个数据有很多疑问。既然我们努力想得到一个公平的比较,我们需要讨论和解释它们:

  • 为什么所有数据库在大约有20个客户的时候会出现峰值?
  • 为什么当客户数量增长时,MongoDB性能下降?
  • 为什么MySQL和MongoDB之间存在性能差距?
  • 为什么PostgreSQL和MongoDB之间存在性能差距?

20客户时的峰值

这可能是最简单的一个,它涉及到一个实例配置。下一个图表显示了PostgreSQL在20个客户时候的测试中的CPU消耗。

正如你所看到的,CPU资源几乎被完全消耗掉,而从20个客户之前的相同度量来看,我们拥有同样的容量。所以这就是这个峰值的解释。

MangoDB 的性能下降

要了解这里发生的事情是非常棘手的,因为我从 sar 里获得的所有指标对于20个和100个客户来说都处于同一水平。所以,作为我调查的一部分,我开始用 perf 来跟踪MongoDB。事实证明,只有两个显著增长的指标: CPU迁移事件的数量和 sched_yield 系统调用的数量。

这让我得出的结论是,MongoDB在螺旋锁中出现了一些问题,因为包含 sched_yield 系统调用的唯一模块是 spin_lock.cpp 。有趣的是,这种情况在版本更新后变得越来越好(但它仍然相当显著),例如这里比较3.2和3.4的吞吐量:

MySQL 对比 MongoDB

实际上,MySQL在第一次测试中的表现更糟。事实证明,由于在MySQL中我们使用的是虚拟列,所以我们必须明确地告诉它应该使用 STORED 语句来存储:

CREATE TABLE usertable (
data JSON,
ycsb_key VARCHAR(255)
    GENERATED ALWAYS AS (JSON_EXTRACT(data, '$.YCSB_KEY'))
    STORED PRIMARY KEY
);

但是即使有了这个改进,这个测试的吞吐量也是很低的。来自MySQL方面的同事声称,这主要是因为 information_schema 比其他数据库收集更多的数据并涉及更多类似的操作,这显然导致更大的开销。但是同时在生产环境中没有人真正地禁用它,所以到目前为止,我还是决定为我的性能测试做同样的事情。

PostgreSQL 对比 MangoDB

我认为这是现在对你最重要的问题。为什么PostgreSQL表现如此不佳呢?答案很简单,我们正在做一个不公平的基准。你可能会注意到,我们仅在MySQL和MongoDB的文档中索引了一个ID,而对PostgreSQL索引了一个完整的文档。所以,我们试着解决这个问题。

在这里,可以看到与我们正在讨论的变化完全相同的测试,即为PostgreSQL仅索引一个ID。现在很明显,PostgreSQL和MongoDB的吞吐量在MongoDB遇到螺旋锁导致性能下降之前是几乎相同的。 PostgreSQL和MySQL之间还有差距,这主要是有两个因素:

  • 如前所述的 information_schema
  • prepared statements,因为在MySQL中它们比在PostgreSQL中缓存的信息少,因此它们不是那么有效

现在我们来讨论一下结果。到目前为止,PostgreSQL和MongoDB的吞吐量与读取工作负载相同。这令人惊讶吗?一点都不 - 我要告诉你,所有的数据库都或多或少地使用相同的数据结构和相同的方法来存储文档。考虑到这一点,以及相同类型的索引和相同的环境,很自然地我们得到的数字是相同的。

数据结构

当我们谈论如何在数据库中存储文档时,我们通常有两个关注点 - 我们的文档应占用尽可能小的空间,但同时我们应该能够有效地使用它。要做到这些我们唯一能做的事情就是把一些额外的信息放到文档中,例如对于其中的每个键,我们可以存储相应值的类型和值。我们所有的目标数据库都采用这种或那种类似的形式。

下面是一个表示PostgreSQL中 jsonb 内部结构的图表:

在这里我们可以看到的是:

  • 整体文件大小
  • 一棵树,每个节点都有一个标题头和实际的内容

让我们放大一下节点结构:

基本上,节点头包含 值类型,值大小 或 偏移量 和一个神秘的标识来决定我们是否存储 大小 或 偏移量 。这样做是为了找到平衡点 - 使用 偏移量,我们可以通过压缩成本获得更多的访问速度,使用 尺寸 可以获得更好的可压缩性。

下面是MongoDB中的 bson 类似图:

毫不奇怪,我们看到几乎相同的图片,包含文档大小,树形结构的节点,每个节点都有自己的头部和实际的内容。再来看一下节点头结构:

和以前一样(没有那么多的选择),我们在这里有 值类型,值的大小 和 键名 ,这是一个小而有趣的区别。问题是,对于 jsonb 我们将所有的键和值存储在一个可变大小的内容部分,并隐式地为它们应用一些顺序。在 bson 里,我们直接在节点头中存储一个键。

现在让我们来探索MySQL中二进制json的内部结构:

正如你猜测的那样,树形结构的节点,但是这次没有明确的头部 - 所有的信息都存储在一个可变大小的内容块中。让我们深入一下:

在这里,与大小,键和值一起,我们发现一个有趣的区别 - 指针,这是从文档的开始处的偏移量,你可以找到这个特定的键或值。据我了解的MySQL源代码,这样做是为了能够以一种懒惰的方式处理文档。

有关文档如何存储的另一个有趣的事情是键/值顺序。

对于 jsonb 和二进制json,我们实际上将同一级别的键和值组合在一起。原因很简单,通过这种方式我们可以更快地访问键,很容易缓存它们等等。

我们总结一下。所有的数据库都或多或少地使用相同的方法来存储文件,只有很小的差异。为了好玩,这里有一些简单文档的二进制级别差异的例子:

{"a": 3, "b": "xyz"}

以下是PostgreSQL中 jsonb 文档的样子:

我们可以清楚地看到其结构的一些部分,并将键和值组合在一起。

bson 也一样:

正如你所看到的,这里的情况是相反的,所有的键和值都是按照自然的顺序排列的,包括 值类型,值的大小 等等。

最后是二进制json:

又一次看到相当长的标题头中有一些指针,然后是分组的键和值。

另一个有趣的事情是,在 jsonb 中,可变大小的内容和头按4个字节对齐。很简单,但会导致有趣的后果,例如这里有两个文档:

{"a": "aa", "b": 1}
{"a": 1, "b": "aa"}

这两个文档的大小相同, 因为我们只是交换了值,仅此而已。但是,如果你尝试将它们存储在 PostgreSQL 中, 你将看到第二个将增加2个字节的磁盘空间:

INSERT INTO test VALUES('{"a": "aa", "b": 1}')

INSERT INTO test VALUES('{"a": 1, "b": "aa"}')

在第二个文档中,我们可以清楚地看到在键 a 和键 b 前后有4个额外的字节。因为第一个有2字节长的头,最终我们看到这个2字节的差异。当然, 这不是那么多,但在某些情况下,也许值得一提。

复杂文档

我们之前所做的测试涉及相当简单的文档,没有任何内部结构。正如我之前说过的,在现实生活中我们要使用更复杂的东西,所以让我们试着用相同的环境来测试相同的读工作负载, 但是现在让我们使用带有一些嵌套信息的文档 (“复杂” 文档):

我们在这里看到,数字是不同的,但这只是因为我们在处理更复杂和更大的文档, 但是所有的模式都是一样的。

文档大小

另一个有趣的问题是处理较大的和较小的 jsonb 文档有什么不同。让我们复制与以前相同的测试,但现在我们将固定客户的数量为 40,而在x轴上变化文档的大小。

我们可以看到在大约2kB的地方,性能有趣的下降,在图中其他地方则几乎线性退化。你能猜到原因吗?

当我们将文档存储在 PostgreSQL 中时,首先将其压缩。即使在压缩后文件依然太大(是的, 你猜对了,超过2kB),PostgreSQL 将其分裂成多个区块,并储存在toast表中。

解析时间

当你使用 jsonb时候,可以尝试一个有趣的实验。使用这样的查询是完全可以的:

SELECT * FROM test_table WHERE data @> '{"key": "123"}'::jsonb;

但正如你可能看到的那样,为这个条件构建一个 jsonb 对象 PostgreSQL 需要解析一个字符串,其中包含 jsonb 定义。这是绝对没有必要的, 因为你有很多其他的选择来创建一个 jsonb 对象, 例如函数 jsonb_build_object:

SELECT * FROM test_table WHERE data @> jsonb_build_object('key', '123');

这不是一个顶级优化,我没有期望它做任何事情,但事实证明,对很多查询, 你可以使用它来提升吞吐率。例如,让我们看一下对带有 GIN 索引的只读工作负载的第一个测试, 但现在我们要使用 jsonb_build_object:

你可以看到,与原始图相比,PostgreSQL每秒多了数千个额外的操作。一点说明-这是一个有点老的测试,所以它对比的是MongoDB 3.2.5。

可扩展性

到目前为止,我们正在使用相当普通的硬件进行测试,这当然是有价值的。但我不得不提一下,当使用性能更强大的硬件时测试情况可能会有某种程度上不同。例如,让我们在不同的机器( m4.large,m4.xlarge,m4.2xlarge)上比较对整个文档使用 GIN 索引的PostgreSQL只读测试:

第一个图形与我们之前看到的是一模一样的, 但是在下面的两个图中有一个有趣的模式。你可以看到,在几乎线性地获得更多吞吐量的同时, PostgreSQL 和 MongoDB 之间的索引相关的差别在减少。我们将在以后讨论其他类型的工作负载会有怎样的不同。

插入负载

既然 YCSB 提供了这么多有趣的工作负载,让我们来探索一下。下一个是只插入的工作负载。最开始我在这里犯了一个错误(我必须说我犯错是很专业的,其中一些错误几乎就像是一门艺术), 我试图使用和之前测试几乎相同的设置,即对所有数据库的文档建一个 ID 索引。问题是, 我还必须正确配置检查点,因为所有数据库的默认配置都不适合这类工作负载。让我们来看看:

你可以看到数据具有振荡模式。例如,如果我们希望看到一些度量值,我们可以看到这个测试是受 IO 限制的:

在最后一个图中,我们可以看到 IO 操作队列大小的时序变化。这些峰值与检查点相关, 数据库将数据刷新到磁盘的执行频率太高。因此,考虑到这一点,让我们调整检查点配置 (我在这里谈论的是 max_wal_size, checkpoint_completion_target,innodb_log_file_size,checkpoint + eviction。另外,请注意,MySQL 在默认情况下有两个 journal 文件, 而 innodb_log_file_size 每一个都要配置的):

这一次的数据更具代表性,我们可以看到有趣的东西,PostgreSQL 在吞吐量方面更具竞争力。MySQL 和 MongoDB 是几乎相同的水平,但有一点 - 实际上, 为了得到更好的 MySQL 性能,当一个文档 id 对应的虚拟列没有 * PRIMARY KEY* 的时候, 我使用了”错误”的设置。在这种情况下,我们可以得到更好的插入工作负载的数字 (但其他一切都是很可怕的),没有它的话MySQL将会有更少的吞吐量。据我所知,MySQL 处理插入工作负载不是那么好,这是一个众所周知的情况,当然有很多MySQL分支用来解决这个问题。

更新负载

在谈论更新工作负载之前,让我们讨论另一个相关的主题 - 在更新 jsonb 时, 我们会遇到什么样的问题? 尽管我们认为它是一个具有某种结构的文档,但对于 PostgreSQL 来说,它仍然是一个常规的数据类型。这意味着有时我们只触及文档的很小一部分, 但数据库需要遍历整个文档。以下是我所说的几个例子:

  • 更新文档的一个字段
  • DETOAST 文档
  • 重新索引文档

重新索引

这里的故事很简单。到目前为止,在 PostgreSQL 里,如果你为 jsonb 列创建了一个函数索引, 然后更新了文档的某些部分(根本不涉及索引),而数据库仍将重新索引此文档。为了检测这是否重要,让我们在两个设置里做一个测试 - 一个是我们有一个文档的id 当索引,另一个是我们对一个单独的列做索引,该列包含相同类型的 id,但与文档分开。我使用 WorkloadA 做测试, 它包含50% 的更新和50% 的读取, 其他一切都类似于以前的测试。

从这个图中我们可以看出,吞吐量有差异。它可能没有那么显著,但请记住, 这严重依赖于文档类型,我预计对于大型和更复杂的文档(索引可能带来更多的开销),这种差异会更大。我很高兴地指出,这方面的工作正在进行中。

DETOAST

让我们比较以下两个查询:

SELECT data->'key1'->'key2' FROM table;
SELECT data->'key1', data->'key2' FROM table;

乍一看,他们之间没有明显的差别,但这种印象是错误的。在第一种情况下,data 只 detoasted 一次,一切都很好。不幸的是,在第二种情况下,data 的两个键都 detoasted 两次。这不仅对 jsonb,而且对 PostgreSQL 中的所有复合数据类型都是如此。总的来说,这不是一个大问题,但是如果我们的文档很大,而且我们要提取太多的键,那么就会有巨大的开销。

为了证明这个说法,让我们做一个实验,重复之前的只读测试(PostgreSQL 使用 ID 索引)。但这次我们只获取一个大文档的 10%:

我们可以清楚地看到,在这种情况下,PostgreSQL 和 MySQL 的开销是巨大的。 对此问题有两种可能的解决方案:

  • 当使用 ALTER TABLE table_name ALTER COLUMN column SET STORAGE EXTERNAL 时候,禁用 jsonb 列压缩。在这种情况下,你可以或多或少地避免 detoasting 开销的磁盘空间成本。
  • 更改查询

我想详细阐述一下第二个方案。由于我们在一个关系数据库中工作,我们可以将我们的文档转换成一个像记录一样的 SQL 结构,然后做任何我们想做的事情:

CREATE TYPE test AS ("a" TEXT, "b" TEXT);
INSERT INTO test_jsonb VALUES('{"a": 1, "b": 2, "c": 3}');
SELECT q.* FROM test_jsonb, jsonb_populate_record(NULL::test, data) AS q;

实际上这是在 PostgreSQL 中使用文档的很常见的模式,因为没有对 Json 路径的全面支持。当然还有一些 扩展包,提供了类似的 Json 的路径,甚至还有 SQL/JSON 这样的巨大扩展包。但是,如果Json 路径是真正必要的,至少在目前我们可以使用这种 “打开” 模式。

让我们设想一下,我们有一个文档的集合,我们想从每一个文档中提取所有 value = “aaa” 的项目:

[{
    "items": [
        {"id": 1, "value": "aaa"},
        {"id": 2, "value": "bbb"}
    ]
}, {
    "items": [
        {"id": 3, "value": "aaa"},
        {"id": 4, "value": "bbb"}
    ]
}]

为此,我们可以”打开”文档使用函数 jsonb_array_elements 做这样的事情:

WITH items AS (
    SELECT jsonb_array_elements(data->'items')
    AS item FROM test
)
SELECT * FROM items
WHERE item->>'value' = 'aaa';

类似的,如果我们希望得到所有项目并包含 status = true:

{
    "items": {
        "item1": {"status": true},
        "item2": {"status": true},
        "item3": {"status": false}
    }
}

我们可以使用 jsonb_each 函数打开我们的文档:

WITH items AS (
    SELECT jsonb_each(data->'items')
    AS item FROM test
)
SELECT (item).key FROM items
WHERE (item).value->>'status' = 'true';

当然,这种方法可能会导致相当冗长的查询(试想一下,如果你想以这种方式更新文档中的内容, 你必须”拆开”并再次”包装”回来),这就是为什么我们有 SQL/JSON 这样的补丁。但是不要把这看成是丑陋的技巧,因为实际上这是非常重要的事情 - 当面向文档方式遇到关系型数据库的时候,你可以使用 SQL 的全部功能并应用到你的文档。

更新

现在,让我们最后来谈谈更新工作负载。这里的问题是,当我们更新 jsonb 文档的一个字段时, PostgreSQL 将在journal 中写入整个文档,你可以想象它的开销。问题是这一开销会有多大?让我们使用 WorkloadA 并使用默认检查点配置和小尺寸的文档来做个测试 :

有趣的是,我们有几乎稳定的数据,前面提到的开销是微不足道的。现在让我们在正确调整后的检查点上再测一次:

这里我们可以看到另一个有趣的事情 - 在检查点调整后,PostgreSQL 和 MySQL 在吞吐量方面处于相同的水平,然而不幸的是,MongoDB 的配置并没有多大帮助。起初, 我认为我配置错了检查点,并且 MongoDB 仍然在频繁地执行它们(这是对的,因为我使用 log_size 选项来调整 journal 大小,但事实证明,WiredTiger 有最大值2GB的限制)。但是, 即使我打开 wait 选项,db. serverStatus() 显示 transaction checkpoints 是 1,吞吐量仍然处于同一级别。

在一天结束的时候,我还在调查这个性能差距,但是预期的结果是 MongoDB 的吞吐量低于 PostgreSQL 和 MySQL,只是没有那么多。这一次我们可以说,我前面提到的开销是微不足道的。现在,让我们使用相同的配置进行相同的测试,但是针对大型文档:

最后,开销很大。为了证明这一点,让我们看看如果我们想更新这个小文档会发生什么:

{
    "aaa": "aaa",
    "bbb": "bbb",
    "ccc": "ccc"
}

我们要更改第一个字段:

{
    "aaa": "ddd",
    "bbb": "bbb",
    "ccc": "ccc"
}

下面是我们将在不同的数据库journal中看到的:

PostgreSQL

MySQL

MongoDB

我们可以清楚地看到,MongoDB 只写入了新旧文件的区别。

但有一件有趣的事情 - 实际上,当我们更新一个文档时,如果它的新旧版本都在同一页上, PostgreSQL 将只写我们想要的差异。问题是,文件往往是相当大,因此, 在同一页上的可能性会很低(即使在我笔记本电脑上面的一个小文件,它也不是所有时间都在同一页上)。明显的改善方法是 增大页的容量。快速提醒一下,你可以使用此指令或此指令 来预估分配多少页是有意义的:

sysctl -w vm.nr_hugepages=1000

然后在 PostgreSQL 中启用大容量的页:

huge_pages = on # 默认是 try

不幸的是,对此我还没有任何基准测试,但从手工实验我可以看到,针对大容量页(2048kB) 和简单文档类型,大多数时候更新保存是有差异的。

另外值得一提的是,这个问题在最近的 MySQL 开发版本(即8.0.3)中也得到了解决,此版本有一个称为”JSON 值的部分更新”的 功能,但我还没有测试它。

在我们完成这一节之前,我不得不提一下,所有的测试都用的是不算强大的 EC2 实例, 实际结果会依不同设置(如更多的CPU)而不同。我正在研究这个问题, 同时你可以查看我同事使用更强大硬件的结果(一,二,三)。它们实际上涵盖了 YCSB 可以提供的所有工作负载,但使用了一些不同的设置,主要是 synchronous_commit = off。

Jsonb 对比 Json 对比 Table 表

到目前为止,我们在一直比较三种不同的数据库,但问题是,即使在PostgreSQL里也有 jsonb 的替代品。如果你必须存储一些文档,也许把它们作为一个纯 json 来存储是有道理的,即便只是从性能的角度?或者,基于相同的原因, 甚至可以将文档转化到适当的 sql 关系中。如果你不太在意 jsonb 提供的灵活性, 你可以考虑这些选项。让我们看看从 jsonb、json 和普通表之间的相同测试中得到什么结果:

  • 关系 对比 Jsonb,只读工作负载

嗯,没那么大的差别。

  • 关系 对比 Jsonb,只插入工作负载

有一些性能差距,但没有那么大。基本上,我们可以看到,因为关系是PostgreSQL里的一等 公民,它们的内部结构相对于jsonb更容易保持。同时,在性能方面,提取一个关系型记录和 jsonb文档中的一行是几乎是相同的。

  • 纯 Json 对比 Jsonb,只读工作负载

我们又一次看到一些性能差距,纯 json 快一点。解释很简单 - 我们在 jsonb 中有一些内部结构在维护结构和磁盘空间方面引入了一些开销。但是正如我们从上面的图中看到的那样,开销并不大。

  • 纯 Json 对比 Jsonb,只插入工作负载

由于上面提到的相同原因,json 快一点,但也不是很显著。

你可能会猜到,我没有谈论更新工作负载,因为它没有意义- jsonb 将完胜 json,但是与 sql 关系相比,它会比较慢。在这一点上,你可以认为 jsonb 介于纯 json 和 sql 关系之间, 它具有一些内部结构,你可以高效地使用它,同时它还提供了一定程度的灵活性。

结论

主要的结论是,这是一个非常有趣的研究课题,我准备继续这样做。我真的希望能得到一些积极的反馈并提升我的测试。

另一个有趣的结论是,我上面展示的所有性能差异基本上都是由数据库本身造成的,而不是它们处理文档的方式。这意味着这完全不是一个灰色的区域,而是我们熟知的东西。在大多数情况下,你可以在关系数据库中使用文档,如果有必要的话,你甚至可以从 NoSQL 的解决方案中迁移过来 ,而且无需担心性能会显著下降。

致谢

非常感谢 Alexey Kopytov,他帮了我很多 MySQL 的设置,感谢Wei Shan Ang,他审查了这篇文章并分享了他配置 MongoDB 的经验,感谢 Oleg Bartunov 提出了最初的设想。

1月13日,SDCC 2017之数据库线上峰会即将强势来袭,秉承干货实料(案例)的内容原则,邀请了来自阿里巴巴、腾讯、微博、网易等多家企业的数据库专家及高校研究学者,围绕Oracle、MySQL、PostgreSQL、Redis等热点数据库技术展开,从核心技术的深挖到高可用实践的剖析,打造精华压缩式分享,举一反三,思辨互搏,报名及更多详情可点击此处查看。
这里写图片描述

基于多智能体协作的AI视频创作平台:从架构到部署实战 多智能体系统(Multi-Agent System)是分布式人工智能的重要分支,其核心原理在于通过多个具备自主决策能力的智能体(Agent)进行协作与协调,共同完成复杂任务。这一技术架构的价值在于能够将庞大问题分解为专业化子任务,并通过智能体间的通信与调度实现高效、稳定的系统化输出,从而在自动化内容生成、复杂流程编排等应用场景中展现出巨大潜力。本文聚焦于一个具体的工程实践——利用FastAPI、React与现代AI服务构建的“虚拟制片厂”系统。该系统模拟影视创作流程,通过导演、编剧、角色设计师等八大智能体角 阅读详情

相关推荐

构建Hacker News摘要工具:从数据流水线到AI集成实战

在信息爆炸的时代,数据流水线(Data Pipeline)成为高效处理海量信息的关键技术架构。其核心原理是通过自动化的工作流,将数据采集、清洗、转换、分析和交付等环节串联起来,形成一个端到端的处理系统。这种架构的技术价值在于能够将原始、杂乱的数据转化为结构化的、可操作的洞察,极大地提升了信息处理的效率和可靠性。在工程实践中,数据流水线常与自然语言处理(NLP)和大语言模型(LLM)结合,应用于智能内容聚合、个性化推荐等场景。例如,针对Hacker News这类高质量但信息密度不均的技术社区,可以构建一个自动

weixin_26750831的博客 516

PostgreSQL 保存json,jsonb类型

PostgresQL 字符串隐式转换JSON脚本: -- 隐式将varchar转换为json CREATEORREPLACEFUNCTIONjson_in_varchar(varchar)RETURNSjsonAS$$ SELECTjson_in($1::cstring); $$ LANGUAGE SQL IMMUTABLE; DROPCASTIF EX...

Joe192的博客 3537

light-bson:轻量级、性能良好的 java bson 编写器,具有用于 fastxml jackson JsonGenerator 的外观

光布森 轻量级、性能良好的 java bson 生成器,带有 jackson JsonGenerator 的外观。 BsonGeneratorFactory lightBson = new BsonGeneratorFactory(); ByteArrayOutputStream out = new ByteArrayOutputStream(); com.fasterxml.jackson.core.JsonGenerator generator = lightBson.createGenerator(out); generator.writeStartObject(); generator.writeFieldName("BSON"); generator.writeStartArray(); generator.writeString("awesome"); generator.

bson和json性能对比

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33736649的博客 1009

MOS管关断时尖峰电压的5种实用抑制方案(附实测数据对比)

本文针对MOS管关断时产生的尖峰电压问题,提供了五种经过实验室实测验证的实用抑制方案。文章从尖峰电压的成因分析入手,详细阐述了优化PCB布局、设计RC缓冲电路、选用TVS二极管钳位、调节驱动电路以及使用磁珠吸收高频振荡等方法的原理、实施步骤与实测数据对比,为硬件工程师解决开关电源和电机驱动中的过压应力与EMI问题提供了系统性的实战指南。

1240

手把手教你用STM32F103的普通IO口读取SSI编码器(附完整代码与示波器调试心得)

本文详细介绍了如何利用STM32F103的普通IO口读取SSI绝对式编码器数据,提供完整的硬件连接方案、精确时序控制的软件实现及示波器调试技巧。通过低成本方案实现高精度位置反馈,适用于工业自动化和机器人控制领域。

weixin_29172963的博客 452

使用postgreSQL的json/jsonb特有操作符查询与传统字符串比较查询的对比

在日常数据库存储中有时会用到json格式,如果现在有在json数据中查询某个json中一个键值对的需求,就需要将整个json全部查询,然后在后台解析才能得到相应值,这在数据量特别庞大的情况下使用常规方式就会造成不小的资源浪费。 在PostgreSQL9.5中引入了新的JSONB功能,极大的增强了PG的NOSQL能力。使用其提供特别的特殊字符,能极大简化开发,以下是常规方式和jsonb方式的...

qq_41514643的博客 1494

R语言:基于Spatstat包的单变量和双变量平面Ripley‘s K函数分析

因为网上查找得到的R语言K函数分析的资料很少,Spatstat包的介绍也很少,所以抛砖引玉。本文简单介绍了使用R语言spatstat包的Kest,Kcross(Lcross)和Kinhom方法进行了单变量和双变量平面Ripley's K函数分析的方法及代码,希望有所帮助。

hsdcmax的博客 1万+

mysql:列类型之json

环境: window10 vs2022 .net 6 mysql 8.0.25 DBeaver 1. 认识json格式 json全称是: JavaScript Object Notation。它的格式有以下几点: 两个对象类型([] or {}); 四种标量 (string、number、true/false、null); 根节点是对象或者是数组; 标量:true/false和null均全为小写形式; 标量:string可以有转移字符("、\、/、\b、\f、\n、\r、\t、\u0000); js

火焰 4843

Win10网络图标消失,网络图标变成灰色,打开网络设置闪退等问题解决

介绍 主要解决如下问题 网络图标消失 常用方法配置中出现的网络图标变成灰色没有办法打开 打开网络和Internet闪退 错误提示`网络发现已关闭。网络计算机和设备不可见。请启动网络和共享中心的网络发现。 连不上网,诊断结果为 依赖服务或组无法启动 这个问题大概率通过重装系统不能解决,请大家谨慎测试之后最后选择重装这个路线。 方法1:这一步一定要做(卸载三方软件) 把360安全卫士,软件管家这些东西都卸载掉。 我的问题一开始就是这个软件引起的,可能他检测到我还给台式机配了无线网卡,然后就把有线网络的一些

呆呆象呆呆的博客 3万+

多传感器融合SLAM指南:如何结合视觉与激光雷达提升建图精度

本文深入探讨了多传感器融合SLAM技术,详细解析了如何将视觉传感器与激光雷达(特别是3D激光雷达)进行深度融合以提升建图精度与鲁棒性。文章从传感器特性分析、数据同步标定、前端融合策略(松耦合与紧耦合)到后端优化与地图构建,提供了完整的实战指南,并重点介绍了V-LOAM等经典融合框架的核心思想,旨在帮助开发者构建适应复杂动态环境的高精度SLAM系统。

weixin_29056145的博客 535

数据库知识

数据库 PostgreSQL相对于MySQL的优势 不仅仅是关系型数据库 除了存储正常的数据类型外,还支持存储: array,不管是一位数组还是多为数组均支持 json(hStore)和jsonb,相比使用text存储接送要高效很多,json存储的是文本类型,类似于mysql中的text类型 json与jsonb之间的区别:jsonb是json的二进制形式 json格式写入快,但读取慢; jsonb格式写入慢,但读取快。 json和jsonb之间的区别 jsonb和json在更高的层面上看起来几乎是

asetqc的博客 1206

拉曼光谱入门:7.拉曼光谱仪光学系统结构详解

本文系统拆解了拉曼光谱仪从激光器输出经聚焦照明、二向色镜/陷波滤光片组滤除瑞丽散射、衍射光栅色散至CCD探测的完整光路,阐述了各光学模块在微弱非弹性散射信号提取、杂散光抑制及高分辨光谱成像中的工程原理与技术参数要求。

qq_53529450的博客 223

记录一次关于spring映射postgresql的jsonb类型的转化器事故,并使用hutool的JSONArray完成映射

自定义jsonb类型的数据映射心得

weixin_64357630的博客 1152

【DRAM存储器六十九】DDR5介绍--2N mode 和 CS Geardown mode

摘要: DDR5内存速率提升至8400MT/s以上时,信号完整性成为关键挑战。JEDEC在JESD79-5B标准中引入2N Mode和CS Geardown Mode两项技术,通过“时间换稳定性”优化时序裕量。2N Mode默认启用,将命令周期延长至两倍时钟周期,增强全局命令稳定性;CS Geardown Mode则针对片选信号(CS_n)降低采样频率,提升局部信号容错能力。两者均需通过特定寄存器配置,适用于高频场景下的性能与稳定性平衡。这些技术为DDR5在高频运行中提供了可靠的时序保障。

highman110的博客 223

优快云专栏限时活动!福利公告必读!

本人优快云付费专栏周排名27名专业博主,每周文章平均阅读量约7w以上!

FJN110的博客 3154

一次PostgreSQL复杂jsonb数据矫正过程分享

这次我学到了什么呢:->加数字代表取数组的第n个元素;->加文本代表取json的指定对象(结果也是json格式);->>与->的用法一致,只是前者结果是文本,后者依然是json对象jsonb_pretty函数可以美化json结果(注意结果不要直接放到记事本里面,放写字板或其他文本编辑工具,记事本展示结果jsonb_set函数可以置换json中的指定元素@> 操作符是判断左侧元素数组中是否包含右侧元素, >@ 操作符是判断右侧元素数组中是否包含左侧元素。

leandzgc的专栏 1087

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Serverless架构实战:构建匿名实时投票工具Votebar的技术解析

在Web应用开发领域,实时数据同步与匿名化处理是提升团队协作效率的关键技术。其原理通常基于WebSocket或Server-Sent Events实现数据推送,结合哈希算法保护用户隐私。这项技术的核心价值在于能够打破沟通壁垒,在会议决策、产品调研等场景中安全、高效地收集真实反馈。以Votebar项目为例,通过Serverless架构与前端轻量化设计,开发者可以快速构建出支持实时图表更新、平衡匿名性与防刷机制的工具。该项目运用Vue 3和Supabase等技术栈,有效解决了小型团队在即时决策、会议反馈等场景中

a2850363633的博客 365

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

构建日志追踪系统:数据库触发器项目实战完整示例

用数据库触发器自动捕获数据变更,构建轻量级日志追踪系统;全程覆盖触发器创建、事件监听、审计日志存储与回溯分析,兼顾实时性与可维护性,是数据库审计与操作溯源的典型落地场景。

weixin_33660045的博客 247

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

MusePublic艺术创作引擎实战分享:如何搭建支持多用户的艺术社区

本文介绍了如何利用星图GPU平台,自动化部署🏛️ MusePublic艺术创作引擎,以构建支持多用户的在线艺术社区。该方案将强大的AI艺术生成能力转化为社区服务,用户可通过直观的Web界面输入提示词并选择风格,快速生成高质量的艺术人像等图片,实现从个人创作到社区分享与互动的转变。

weixin_34456923的博客 267

第三十六篇:QuantumFlow项目总结:从0到1的完整复盘

QuantumFlow是一个开源工作流自动化平台,从2025年9月构思到12月上线仅用25周。该项目采用FastAPI+React技术栈,实现了可视化流程编辑、高性能执行引擎和插件化架构。上线后3周内获得3,847注册用户和124付费用户,MRR达7,440美元。核心技术亮点包括:DAG循环检测、Redis分布式锁、SSE实时日志等。项目通过开源+SaaS混合模式运营,GitHub获2,347星。未来规划包括AI节点集成、移动端App和企业级功能扩展。

梦帮科技开源中心 1147

【回眸】搞钱灵感——自动化敏捷开发项目管理系统落地设计

文章摘要:本文介绍了一套轻量级敏捷协作系统的实战开发经验,从架构选型到大规模并发优化的全流程解决方案。系统采用模块化单体架构,整合需求管理、迭代规划、任务分配、代码关联等功能,实现研发流程自动化闭环。关键技术点包括:基于Docker的本地环境搭建、PostgreSQL的JSONB灵活存储、智能任务分配算法、Git Hook深度集成、实时燃尽图计算以及多级缓存优化策略。该系统通过消除信息孤岛和自动化事务性工作,显著提升了团队协作效率,并支持千级并发场景下的稳定运行。

啤酒鸭的物联网开发记录博客 378

开源数据库管理软件_跟上开源数据库管理的发展

开源数据库管理软件 开源数据库的世界正在Swift发展。 似乎每天都有开源技术的新版本发布,这可能会使数据库管理员的生活变得更轻松,即使只有他/她知道这一点。 幸运的是,有许多方法可以掌握开源数据库技术的最新发展。 一种方法是下周在爱尔兰都柏林举行的Percona实时开源数据库会议。 我们之前已经介绍了Percona Live,并邀请您回顾一下我们以前的故事 。 从物联网到大数据再到与云的协...

cumj63710的博客 368

汉字可视化探索平台:基于Flask+Vue的汉字浏览系统架构与实现

在数据可视化与Web应用开发领域,将复杂信息通过直观界面呈现是提升用户体验的关键技术。其原理在于通过前后端分离架构,将多源异构数据进行聚合、清洗与结构化存储,再通过RESTful API提供标准数据接口,最终由前端框架实现动态交互展示。这一技术价值在于能够将原本分散、专业的数据转化为易于探索和理解的视觉形态,极大降低了信息获取门槛。在文化传承、教育科技、数字人文等领域有着广泛应用,例如构建历史文献数据库、语言学习工具或数字博物馆。本文聚焦于汉字这一独特数据对象,探讨如何利用Flask后端框架与Vue.js前

weixin_30675247的博客 675

SQL与NoSQL选型实战:从数据契约出发的五维决策法

数据库选型本质是数据管理策略的设计,核心在于理解数据的读写特征、一致性要求与演化规律。SQL代表强一致性、事务保障与结构化查询能力,适用于金融交易、电商库存等ACID刚性场景;NoSQL则以BASE理论为基,通过基本可用、软状态和最终一致性换取高吞吐、弹性扩展与Schema灵活演进,适配IoT日志、用户行为分析等海量写入场景。二者并非对立技术栈,而是面向不同数据契约的服务承诺。本文基于真实生产案例,提炼出写入模式、查询复杂度、Schema变更频率、扩展路径与运维成熟度五大可量化维度,帮助工程师摆脱‘MySQ

weixin_33910137的博客 473

CMS系统中智能插件的更新维护需要注意什么?

《CMS智能插件更新避坑指南》摘要:本文针对CMS插件更新中的常见风险,提出10个关键解决方案。

615
上一篇: 2018 年 1 月全球数据库排名:PostgreSQL 稳步上升
下一篇: 京东京麦交易平台设计与实现
csdn研发技术
博客等级 码龄9年 1381粉丝 90原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值