导读:找出哪种数据模型(RDBMS 或文档)更适合大型、复杂与生成式 AI 的工作负载。
各位朋友,生成式人工智能 (GenAI) 应用程序使用数据的方式与传统的在线事务处理 (OLTP) 工作负载是不一样的。
后者以小块的形式处理数据,通常以行和表的形式,并且数据结构相对简单。大语言模型 (LLM)训练需要更丰富的文档结构或二进制对象,其大小可能为几千字节甚至几兆字节。
检索增强生成 (RAG)是一种广泛应用于 GenAI 应用程序的数据检索技术,它利用复杂的数据结构来构建提示语并实时生成响应。
此外,与任何其他类型的现代工作负载相比,人工智能驱动的工作负载更需要底层数据库系统能够快速高效地处理针对此类数据的查询。
传统关系数据库管理系统 (RDBMS)数据库平台未针对此类工作负载进行过设计。它们所基于的存储引擎在行大小方面假设存在一致性,并且它们在使用具有类型属性的记录行时运行效率最高。
使用 RDBMS 的最佳实践包括最小化表中的属性数量、使用垂直分区以及保持行数较小以及避免行外存储。这也适用于在 RDBMS 中使用JSON属性类型,这允许开发者使用可在复杂查询中引用的灵活架构对象来扩展其关系表的行。
为了让它们保持相关性,一种新的基于文档和对象的 NoSQL 替代方案已经发展起来,更能够满足这一需求。
在开源 RDBMS PostgreSQL中,标准实践指导是尽量减少值中包含 JSON 或 JSONB 属性的数量,也可以使用稀疏填充对象。
除了数据模型的差异之外,对于 GenAI 的工作负载而言, MongoDB等文档数据库相对于 PostgreSQL 等 RDBMS 平台的最大优势之一在于底层设计。
MongoDB 建立在存储引擎之上,该引擎旨在处理大小各异(从几个字节到几兆字节)丰富且复杂的文档。
当今 OLTP 工作负载越来越频繁地让小型、一致性设计的 RDBMS 平台对大型数据对象采用行外存储。但是行外存储技术(如 PostgreSQL 的TOAST)可能会带来性能瓶颈,因为查询无法再从表的行中访问所需的数据,从而导致从大型对象存储层进行二次检索。
另外,MongoDB 利用一个称为 BSON 的二进制文档结构来存储和传输强类型属性数据,这些数据由服务器处理,并以相同的格式存储在磁盘上,从而无需到服务器端解析。
RDBMS 数据库(例如 PostgreSQL)将复杂的 JSON 传输为文本格式。这要求服务器端解析写入内容,至少验证 JSON 格式或(在 JSONB 反序列化的情况下)属性值,以便它们可以在需要计算值的服务器端查询中有效使用。
在输出过程中,数据必须先序列化为 JSON 文本,然后才能传输到客户端。
我们将在下面的基准测试中,展示这一对大型文档的开销,虽然巨大但是可衡量的过程。
MongoDB 与 PostgreSQL 的比较
随着数据库需要存储和处理的对象的平均大小急剧增加, Gen AI 给系统带来了新的挑战。为了评估 PostgreSQL JSON/JSONB 与 MongoDB BSON 的性能,我们在同一硬件上运行了两台服务器,分别为:
Windows 10 Pro 32GB RAM Intel Core i7-8700K CPU@3.70GHz
PostgreSQL v16.2 MongoDB v7.0.8
我们开始测量单线程性能,以便更好地了解客户端和服务器之间的协议开销。
如前所述,MongoDB 专门使用 BSON 来减少这种开销,即无需序列化和反序列化数据。线程化或运行多个客户端会掩盖这种开销,而无需测量整个系统的整体资源利用率。
对于写入工作负载,我们将 10,000 个文档插入到单个表或集合中,具有以下配置和有效负载大小:
其中,PostgreSQL 服务器使用以下参数进行配置:
MongoDB 服务器是默认安装,未应用任何调整。
我们进行的第一个测试是插入 10,000 个单属性文档,同时测量有效负载从 10B 增加到 ~4KB 时完成所需的总时间(以毫秒为单位)。
下图显示了原始数据与结果图表。
10K 记录插入(单一属性有效载荷) | |||
MongoDB | Postgres(JSONB) | Postgres(JSON) | |
n=1,s=10 | 773 | 399 | 331 |
n=1,s=200 | 789 | 2184 | 969 |
n=1,s=1000 | 750 | 8393 | 4071 |
n=1,s=2000 | 850 | 16387 | 7944 |
n=1,s=4000 | 829 | 31705 | 15767 |
当涉及大型文档时,数据非常清楚:PostgreSQL 的性能与 MongoDB 相当,直到文档大小开始增长到几百字节以上,一旦 TOAST 在 2KB 左右启动,情况就会变得“糟糕”。
另一方面,毫不奇怪,PostgreSQL 在处理其设计用于处理的工作负载类型(处理小块数据)方面相当有竞争力。然而,随着有效负载的大小增加,处理开销很快就会变得明显。PostgreSQL 仅验证 JSON 属性的格式,而 JSONB 还会解析属性值。
在本测试中可以看到 JSONB 产生的额外开销。MongoDB BSON 在所有有效负载上的表现都远远优于 JSON 和 JSONB,并且曲线非常平坦。
测试更多属性和更大的有效载荷
下一个测试是插入 10,000 个多属性文档,同时测量完成测试所需的总时间(以毫秒为单位),因为有效负载分布在多个属性中,大小从 10B 增加到 ~4KB。
下图显示了原始数据和结果图表。
10K 物品插入(多属性有效载荷) | |||
MongoDB | Postgres(JSONB) | Postgres(JSON) | |
n=10,s=10 | 531 | 415 | 322 |
n=10,s=200 | 569 | 2040 | 793 |
n=50,s=1000 | 641 | 9504 | 4419 |
n=100,s=2000 | 812 | 19213 | 9181 |
n=200,s=4000 | 1085 | 37278 | 17460 |
主要之结论为,随着属性数量的增加,JSONB 的解析开销会更高。
MongoDB BSON 和 PostgreSQL JSON 在单属性和多属性测试中都保持了相对平稳的性能。
BSON 没有任何解析开销,而 JSON 只是验证格式,因此在插入复杂文档时,两者都不需要做太多工作。MongoDB BSON 仍然是明显的赢家,而 JSONB 则要落后很多。
MongoDB 与 PostgreSQL 读取测试
最后一个测试是针对多属性文档集的读取。
索引是在包含 10 个整数值的数组属性上创建的,这些整数值是从上一次基准测试期间插入的 10,000 个文档 ID 中随机选择的。
在 PostgreSQL 中,属性是在行本身上创建的,而不是在JSON/JSONB 文档中创建。这样做是为了消除与索引文档属性相关的任何可能的开销,然而 PostgreSQL 最初并不是为此设计的。没有理由不在实际工作负载中将索引属性投射到行中,因此以这种方式配置测试代表了更平等的比较。
查询测试包括遍历 10,000 个唯一整数 ID 值,并针对每个值选择表或集合中索引数组中包含该 ID 的所有文档。结果查询测试中共检索了大约 100,000 个文档。
在这两种情况下,都会迭代整个结果集,但不会处理实际数据。这导致基准测试结果偏向了 PostgreSQL,因为 JSON 文本实际上不会被客户端解析为可用对象,而 BSON 文档则不需要解析。
尽管PostgreSQL有这样的特色,但结果还是有力地证明了 MongoDB 的优势。
数组索引查询测试 | |||
MongoDB | Postgres(JSONB) | Postgres(JSON) | |
n=10,s=10 | 3587 | 19933 | 18749 |
n=10,s=200 | 3810 | 23619 | 23946 |
n=50,s=1000 | 4741 | 27760 | 21311 |
n=100,s=2000 | 6023 | 36701 | 23264 |
n=200,s=4000 | 8352 | 53808 | 27789 |
我们看到,JSON 和 JSONB 的序列化相关开销都很高。MongoDB 显然受益于 BSON 不需要序列化的事实。对于 PostgreSQL,没有测量与 JSON 文本反序列化相关的开销,因为文本在代码中会被丢弃。
即使文档较小,MongoDB 也以较大地优势胜出。一个有趣的结论是 JSONB 也会在读取时产生影响,因为类型化属性值的序列化似乎会产生更多成本。
相比之下,尽管 JSONB 的读取速度更快,但如果文档中存储的数据值实际上并未在查询或处理的服务器端引用,那么最好使用 JSON。
结语
考虑到 RDBMS 在宽行和大数据属性方面的性能限制,这些测试表明,像 PostgreSQL 这样的系统将难以处理生成式 AI 工作负载所需的多样、复杂的文档数据。
而使用文档型数据库来处理这些文档数据,可以获得更好的性能。
参考:
https://db-engines.com/en/system/Cassandra%3BMongoDB%3BPostgreSQL
https://airbyte.com/data-engineering-resources/mongodb-vs-postgresql