JSONBench 榜单排名第一,10 亿条数据秒级响应:Apache Doris vs. ClickHouse,Elasticsearch,PostgreSQL

坦白讲,每次看性能测试排行榜,我都会下意识地先找找 Apache Doris 在哪个位置。

这次打开 JSONBench 的榜单,心情一如既往的期待加紧张。

好在结果让我松了一口气:默认配置下就能排到第三,仅次于维护方 ClickHouse 的两个版本。

不过,Doris 只能止步于此了吗?经过一系列优化后,查询时长能不能再缩短点?和 ClickHouse 的差距在哪里?

调优前后对比图镇楼,至于调优的具体思路,请一起往下看吧。

  • Apache Doris 排名 (Default)

    Apache Doris 排名 (Default)

  • Apache Doris 排名 (Unofficial Tuned)

    Apache Doris 排名 (Unofficial Tuned)

JSONBench 简介

JSONBench 是一个为 JSON 数据而生的数据分析 Benchmark,简单来说,它由 10 亿条来自真实生产环境的 JSON 数据、5 个针对 JSON 构造的特定 SQL 查询组成,旨在对比各个数据库系统对半结构化数据的处理能力。目前榜单包括 ClickHouse、MongoDB、Elasticsearch、DuckDB、PostgreSQL 等知名数据库系统,截至目前,Doris 的性能表现是 Elasticsearch 的 2 倍,是 PostgreSQL 的 80 倍

JSONBench 官网地址:jsonbench.com

JSONBench 简介.png

不仅在性能上 Apache Doris 领先其他同类产品,在数据集相同的情况下,Apache Doris 的存储占用是 Elasticsearch 的 1/2、PostgreSQL 的 1/3

JSONBench 简介-2.png

JSONBench 测试具体流程:首先在数据库中创建一张名为 Bluesky 的表,并导入十亿条真实的用户行为日志数据。测试过程中,每个查询重复执行三次,并且在每次查询前清空操作系统的 Page Cache,以模拟冷热查询的不同场景。最终,通过综合计算各查询的执行耗时得出数据库的性能排名。

在这个测试中,Apache Doris 使用了 Variant 数据类型来存储 JSON 数据,默认的建表 Schema 如下:

CREATE TABLE bluesky (
    `id` BIGINT NOT NULL AUTO_INCREMENT,
    `data` variant NOT NULL
)
DISTRIBUTED BY HASH(id) BUCKETS 32
PROPERTIES ("replication_num"="1");

Variant 是 Apache Doris 2.1 中引入一种新的数据类型 ,它可以存储半结构化 JSON 数据,并且允许存储包含不同数据类型(如整数、字符串、布尔值等)的复杂数据结构,而无需在表结构中提前定义具体的列。Variant 类型特别适用于处理复杂的嵌套结构,而这些结构可能随时会发生变化。在写入过程中,该类型可以自动根据列的结构、类型推断列信息,动态合并写入的 schema,并通过将 JSON 键及其对应的值存储为列和动态子列。

调优思路与原理

JSONBench 榜单排名依据各个数据库系统在默认配置下的性能数据,那么能否通过调优,让 Apache Doris 进一步释放性能潜力,实现更好的性能效果呢?

01 环境说明

  • 测试机器:AWS M6i.8xlarge(32C128G);
  • 操作系统:Ubuntu24.04;
  • Apache Doris: 3.0.5;

02 Schema 结构化处理

由于 JSONBench 特定查询中涉及到的 JSON 数据都是固定的提取路径,换言之,半结构化数据的 Schema 是固定的,因此,我们可以借助生成列,将常用的字段提取出来,实现半结构化数据和结构化数据结合的效果。类似的高频访问的 JSON 路径或者需要计算的表达式,都可以使用该优化思路,添加对应的生成列来实现查询加速。

CREATE TABLE bluesky (
    kind VARCHAR(100) GENERATED ALWAYS AS (get_json_string(data, '$.kind')) NOT NULL,
    operation VARCHAR(100) GENERATED ALWAYS AS (get_json_string(data, '$.commit.operation')) NULL,
    collection VARCHAR(100) GENERATED ALWAYS AS (get_json_string(data, '$.commit.collection')) NULL,
    did VARCHAR(100) GENERATED ALWAYS AS (get_json_string(data,'$.did')) NOT NULL,
    time DATETIME GENERATED ALWAYS AS (from_microsecond(get_json_bigint(data, '$.time_us'))) NOT NULL,
    `data` variant NOT NULL
)
DUPLICATE KEY (kind, operation, collection)
DISTRIBUTED BY HASH(collection, did) BUCKETS 32
PROPERTIES ("replication_num"="1");

除了可以减少查询时提取数据的开销,还可以用展平出来的列作为分区列,使得数据分布更均衡。

需要注意的是,查询的 SQL 语句也要改为使用展平列的版本:

// JSONBench 原始查询:
SELECT cast(data['commit']['collection'] AS TEXT ) AS event, COUNT(*) AS count FROM bluesky GROUP BY event ORDER BY count DESC;
SELECT cast(data['commit']['collection'] AS TEXT ) AS event, COUNT(*) AS count, COUNT(DISTINCT cast(data['did'] AS TEXT )) AS users FROM bluesky WHERE cast(data['kind'] AS TEXT ) = 'commit' AND cast(data['commit']['operation'] AS TEXT ) = 'create' GROUP BY event ORDER BY count DESC;
SELECT cast(data['commit']['collection'] AS TEXT ) AS event, HOUR(from_microsecond(CAST(data['time_us'] AS BIGINT))) AS hour_of_day, COUNT(*) AS count FROM bluesky WHERE cast(data['kind'] AS TEXT ) = 'commit' AND cast(data['commit']['operation'] AS TEXT ) = 'create' AND cast(data['commit']['collection'] AS TEXT ) IN ('app.bsky.feed.post', 'app.bsky.feed.repost', 'app.bsky.feed.like') GROUP BY event, hour_of_day ORDER BY hour_of_day, event;
SELECT cast(data['did'] AS TEXT ) AS user_id, MIN(from_microsecond(CAST(data['time_us'] AS BIGINT))) AS first_post_ts FROM bluesky WHERE cast(data['kind'] AS TEXT ) = 'commit' AND cast(data['commit']['operation'] AS TEXT ) = 'create' AND cast(data['commit']['collection'] AS TEXT ) = 'app.bsky.feed.post' GROUP BY user_id ORDER BY first_post_ts ASC LIMIT 3;
SELECT cast(data['did'] AS TEXT ) AS user_id, MILLISECONDS_DIFF(MAX(from_microsecond(CAST(data['time_us'] AS BIGINT))),MIN(from_microsecond(CAST(data['time_us'] AS BIGINT)))) AS activity_span FROM bluesky WHERE cast(data['kind'] AS TEXT ) = 'commit' AND cast(data['commit']['operation'] AS TEXT ) = 'create' AND cast(data['commit']['collection'] AS TEXT ) = 'app.bsky.feed.post' GROUP BY user_id ORDER BY activity_span DESC LIMIT 3;

// 使用展平列改写的查询:
SELECT collection AS event, COUNT(*) AS count FROM bluesky GROUP BY event ORDER BY count DESC;
SELECT collection AS event, COUNT(*) AS count, COUNT(DISTINCT did) AS users FROM bluesky WHERE kind = 'commit' AND operation = 'create' GROUP BY event ORDER BY count DESC;
SELECT collection AS event, HOUR(time) AS hour_of_day, COUNT(*) AS count FROM bluesky WHERE kind = 'commit' AND operation = 'create' AND collection IN ('app.bsky.feed.post', 'app.bsky.feed.repost', 'app.bsky.feed.like') GROUP BY event, hour_of_day ORDER BY hour_of_day, event;
SELECT did AS user_id, MIN(time) AS first_post_ts FROM bluesky WHERE kind = 'commit' AND operation = 'create' AND collection = 'app.bsky.feed.post' GROUP BY user_id ORDER BY first_post_ts ASC LIMIT 3;
SELECT did AS user_id, MILLISECONDS_DIFF(MAX(time),MIN(time)) AS activity_span FROM bluesky WHERE kind = 'commit' AND operation = 'create' AND collection = 'app.bsky.feed.post' GROUP BY user_id ORDER BY activity_span DESC LIMIT 3;

03 Page Cache 调整

调整查询语句后,开启 profile,执行完整的查询测试:

set enable_profile=true;

进入 FE 8030 端口的 Web 页面,找到相关 profile 进行分析,此时发现 SCAN Operator 中的 Page Cache 命中率较低,导致热读测试过程中存在一部分冷读操作。

-  CachedPagesNum:  1.258K  (1258)
-  TotalPagesNum:  7.422K  (7422)

这种情况通常是由于 Page Cache 容量不足,无法完整缓存 Bluesky 表中的数据。建议在 be.conf 中添加配置项 storage_page_cache_limit=60%,将 Page Cache 的大小从默认的内存总量的 20% 提升至 60%。重新运行测试后,可以观察到冷读问题已得到解决。

-  CachedPagesNum:  7.316K  (7316)
-  TotalPagesNum:  7.316K  (7316)

04 最大化并行度

为了进一步挖掘 Doris 的性能潜力,可以将 Session 变量中的parallel_pipeline_task_num设为 32,因为本次 Benchmark 测试机器m6i.8xlarge为 32 核,所以我们将并行度设置为 32 以最大程度发挥 CPU 的计算能力。

// 单个 Fragment 的并行度
set global parallel_pipeline_task_num=32;

调优结果

经过上述对 Schema、Query、内存限制、CPU 等参数的调整,我们对比了调优前后 Doris 的性能表现以及一些其他数据库系统的成绩,有如下结果:

调优结果.png

可以看到,对比调优前的 Doris,调优后 Doris 查询整体耗时降低了 74%,对比原榜单第一的 ClickHouse 产品实现了 39% 的领先优势

总结与展望

通过对 Schema 的结构化处理、查询语句的优化、缓存配置的调整以及并行参数的设置,Apache Doris 整体查询耗时显著下降,并超越 ClickHouse。

在默认设置下,Doris 在 10 亿条 JSON 的查询耗时与 ClickHouse 仍有数秒的差异。然而,依托于 Doris 在 JSON 处理、Variant 类型支持及生成列等能力的加持,经调优后,其半结构化数据处理性能获得了进一步显著提升,并在同类数据库中表现出明显的领先优势。

未来,Apache Doris 将继续打磨在半结构化领域的数据处理能力,为用户带来更加优质、高效的分析体验,包括:

  • 优化 Variant 类型稀疏列的存储空间,支持万列以上的子列;
  • 优化万列大宽表的内存占用;
  • 支持 Variant 子列根据列名的 Pattern 自定义类型、索引等。

推荐阅读

<think>我们正在处理一个关于SeaTunnel和Doris的错误,具体是ErrorCode Doris-01(流加载错误)。需要分析原因并提供解决方案。 根据经验,Doris-01错误通常与Doris的Stream Load功能相关。Stream Load是Doris提供的一种通过HTTP协议导入数据的方式,SeaTunnel在写入Doris时可能会使用这种方式。 可能的原因: 1. 网络问题:SeaTunnel服务器与Doris服务器之间的网络连接不稳定或中断。 2. Doris服务问题:Doris的BE(Backend)或FE(Frontend)服务异常。 3. 数据格式问题:写入的数据格式(如JSON、CSV)与Doris表结构不匹配。 4. 数据质量问题:数据中存在空值、类型不匹配、超出范围等。 5. 权限问题:使用的Doris账户没有写入权限。 6. 资源问题:Doris集群资源不足(如内存不足)导致流加载失败。 7. 配置问题:SeaTunnel中Doris Sink的配置错误,如错误的Doris地址、端口、数据库名、表名等。 8. 版本兼容性问题:SeaTunnel和Doris版本不兼容。 解决方案: 1. 检查网络:确保SeaTunnel服务器可以访问Doris集群的FE和BE的端口(通常FE的HTTP端口为8030,BE的HTTP端口为8040)。 2. 检查Doris服务状态:登录Doris集群,检查FE和BE的运行状态(可以通过`SHOW PROC '/frontends';`和`SHOW PROC '/backends';`命令)。 3. 验证数据格式:确保SeaTunnel生成的数据格式与Doris表定义一致。特别注意日期格式、字符串长度、数值范围等。 4. 检查数据质量:在SeaTunnel中增加数据验证步骤,或者尝试小批量数据导入,定位问题数据。 5. 检查权限:确认SeaTunnel配置的Doris用户有对应数据库和表的INSERT权限。 6. 资源监控:检查Doris集群的内存和磁盘使用情况,适当调整Stream Load的等待时间或分批写入。 7. 检查配置:仔细核对SeaTunnel Doris Sink配置项,包括: - doris.fenodes: FE的HTTP地址,格式为`fe_ip:http_port`(多个用逗号分隔) - 用户名和密码 - 数据库名和表名 - 其他配置如`batch.size`(批量写入大小)等 8. 版本兼容性:查阅SeaTunnel官方文档,确认使用的Doris连接器版本与Doris集群版本兼容。 调试步骤: - 查看SeaTunnel日志,获取更详细的错误信息。通常Stream Load的返回信息中会有具体错误原因。 - 在Doris中查看Stream Load的记录:`SHOW STREAM LOAD;`,然后根据返回的Label查看具体错误信息。 如果以上步骤无法解决,可以尝试在Doris端手动模拟Stream Load请求,以确定问题是出在SeaTunnel还是Doris。 例如,使用curl模拟Stream Load: ```bash curl --location-trusted -u user:password -H "label:your_label" -H "column_separator:," -T test.csv http://fe_host:8030/api/db_name/tbl_name/_stream_load ``` 注意:将上述命令中的参数替换为实际值。 通过模拟请求,可以更直接地看到错误响应,从而定位问题。 最后,如果问题依然存在,建议查看Doris的日志(fe.log和be.log)以获取更深入的错误信息。 由于这是一个常见的错误,SeaTunnel社区和Doris社区可能有相关的讨论。可以搜索相关错误信息。 下面将生成相关问题。</think>### SeaTunnel Doris ErrorCode Doris-01(流加载错误)分析与解决方案 #### 一、错误原因分析 ErrorCode Doris-01 表示 SeaTunnel 向 Doris 执行 Stream Load 数据写入时失败。常见原因包括: 1. **网络连接问题** - SeaTunnel 无法访问 Doris FE 节点的 HTTP 端口(默认 8030)[^1] - 防火墙或安全组阻断通信 - 示例错误日志:`Failed to connect to doris-fe:8030` 2. **数据格式不匹配** - 写入数据Doris 表结构(列类型、长度、约束)冲突 - 日期/时间格式错误(如 `2023-13-32`) - 特殊字符未转义(如未处理 `\n` 导致 JSON 解析失败) 3. **权限配置错误** - Doris 账户缺少目标数据库的 `LOAD_PRIV` 权限[^2] - 错误配置认证信息(如密码错误) 4. **资源超限** - 单次 Stream Load 数据量超过 Doris 内存限制(默认 2GB) - BE 节点磁盘空间不足 5. **配置参数错误** - `fenodes` 地址格式错误(需为 `fe_ip:http_port`) - 表名/数据库名大小写不匹配(Doris 默认区分大小写) #### 二、解决方案步骤 ##### 1. 检查网络连通性 ```bash # 从 SeaTunnel 节点测试连接 Doris FE telnet doris-fe 8030 curl http://doris-fe:8030 ``` ##### 2. 验证 Doris 账户权限 在 Doris 中执行: ```sql GRANT LOAD_PRIV ON db_name.* TO 'user'@'%'; SHOW GRANTS FOR 'user'; -- 确认权限 ``` ##### 3. 调整数据格式 在 SeaTunnel 配置中添加格式处理器(以 JSON 为例): ```yml sink: doris: format: "json" json_format: "canal" # 处理特殊字符 column_separator: "\x01" # 非可见分隔符 ``` ##### 4. 优化资源配置 - **增加内存限制**:在 Doris BE 的 `be.conf` 添加 `streaming_load_max_mb=4096` # 提升至 4GB - **分批写入**:在 SeaTunnel 配置 `batch_size: 50000` # 控制单批次数据量 ##### 5. 完整配置示例 ```yml sink: plugin: "Doris" host: "doris-fe:8030" database: "production_db" table: "user_behavior" user: "loader" password: "encrypted_pass" batch_size: 100000 doris.column_separator: "\\x01" doris.format: "json" ``` #### 三、诊断工具 1. **查看 Stream Load 记录** ```sql SHOW STREAM LOAD WHERE label LIKE 'seatunnel%'; -- 获取失败详情 ``` 2. **检查 Doris 日志** - FE 日志:`fe/log/fe.warn.log` - BE 日志:`be/log/be.WARNING` 搜索关键词:`Rejected from` 或 `load failed` 3. **手动测试 Stream Load** ```bash curl -u user:pass -H "format:json" -T data.json \ http://doris-fe:8030/api/db/tbl/_stream_load ``` > **关键提示**:95% 的 Doris-01 错误可通过以下组合解决: > (1) 确认网络可达性 > (2) 使用 `\\x01` 作为列分隔符 > (3) 将 `batch_size` 降至 10 万条以下[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SelectDB技术团队

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值