拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ParadeDB 基数统计(Cardinality)聚合基准查询解析:从 COUNT(DISTINCT) 到 pdb.agg 的源码级原理

ParadeDB 基数统计(Cardinality)聚合基准查询解析:从 COUNT(DISTINCT) 到 pdb.agg 的源码级原理 数据库搜索引擎全文检索向量数据库后端【免费下载链接】paradedbOne Postgres for your application data, full-text search, vector retrieval, and aggregations. Home of the pg_search extension.项目地址https://gitcode.com/gh_mirrors/pa/paradedb点击查看免费下载在 ParadeDB 的 Stack Overflow 基准测试套件中cardinality查询目录是专门验证统计去重后基数这一最昂贵聚合形态性能的战场。本文以 benchmarks/datasets/stackoverflow/queries/cardinality/README.md 为核心骨架结合目录下 8 个 SQL 基准脚本、pdb.agg()的公开 API 实现与聚合执行器源码说明为什么原生 PostgreSQL 在 GIN 索引上做基数聚合不可行、ParadeDB 如何通过列式扫描与 Tantivy 聚合收集器加速这类查询以及 MVCC 可见性开关true/false在字符串字段基数统计上的惰性可见性检查快速路径。读完本文你将掌握cardinality基准目录中每个查询脚本的测量意图与参数设置、pdb.agg({cardinality: ...}, ...)的完整调用语法与可见性语义、paradedb.enable_aggregate_custom_scan等 GUC 的作用以及 ParadeDB 在什么条件下能把 MVCC 检查内嵌进基数聚合本身。为什么基数聚合值得单独开一个基准目录COUNT(DISTINCT ...)去重计数是所有聚合中最伤的形态之一它不仅要遍历全部命中文档还必须为每个文档的取值建立去重集合才能给出精确结果。在全文检索 聚合的混合负载下这个成本会被放大到不可接受。cardinality目录的 README 开宗明义地记录了基准设计中的两个关键取舍原生 PostgreSQL 聚合基于 GIN 索引被直接排除对大数据集而言在 GIN 全文索引之上跑原生聚合慢到无法工作unworkably slow。原因是标准 PostgreSQL 聚合需要扫描完整 posting list 并回表取堆元组耗时以数十秒到分钟计。冗余的 base scan 变体被裁剪以控制基准运行时长basescan_group_by.sql和basescan_high_cardinality_group_by.sql被移除只保留basescan_distinct.sql作为COUNT(DISTINCT ...)的代表性基线。这样基准能在有限时间内跑完同时仍能对照关闭自定义聚合扫描后的列式 base scan性能。关于第一点benchmarks/datasets/stackoverflow/README.md 给出了更完整的表述标准 PostgreSQL 聚合在 GIN 全文索引上需要扫描整条 posting list 与堆元组而 ParadeDB 即使只用列式 base scanparadedb.enable_aggregate_custom_scan off也比 PostgreSQL 快因此聚合类查询不与 PostgreSQL 对比而是专注于测量 ParadeDB 自身的两条执行路径——列式 base scan 与 DataFusion/自定义聚合下推。基准查询矩阵8 个脚本各测什么cardinality目录下的 8 个 SQL 脚本形成了一组精心设计的对照实验。它们统一基于stackoverflow_posts表结构见 benchmarks/datasets/stackoverflow/create_tables.sql涉及body TEXT、tags VARCHAR、post_type_id SMALLINT、score INTEGER等字段统一使用全文谓词body ||| javascript圈定命中文档集。脚本注释中的测量意图关键参数aggregate_scan_count_col.sqlpdb.agg底层无GROUP BY的计数work_mem4GBenable_aggregate_custom_scanonaggregate_scan_group_by.sqlaggregate custom scan 下的GROUP BY计数同上COUNT(*) FROM (SELECT ... GROUP BY post_type_id)aggregate_scan_high_cardinality_group_by.sql高基数GROUP BY多指标聚合GROUP BY tags LIMIT 65000聚合COUNT/MIN/MAX/SUM(score)basescan_distinct.sql列式 base scan 的COUNT(DISTINCT ...)基线enable_aggregate_custom_scanoff数值 fast fieldpdb_agg_high_cardinality_group_by_no_mvcc.sql高基数GROUP BYpdb.agg关闭 MVCCpdb.agg(..., false)GROUP BY tags LIMIT 65000pdb_agg_value_count_no_mvcc.sql无GROUP BY的pdb.aggvalue_count关闭 MVCCpdb.agg({value_count: ...}, false)tantivy_cardinality_mvcc.sql字符串字段 cardinality开启 MVCC → 惰性可见性检查pdb.agg({cardinality: {field: tags}}, true)tantivy_cardinality_no_mvcc.sql字符串字段 cardinality关闭 MVCC 的基线pdb.agg({cardinality: {field: tags}}, false)可以看到目录内同时覆盖了两个正交维度执行路径维度aggregate_scan_*.sql走自定义聚合扫描enable_aggregate_custom_scan onbasescan_distinct.sql关闭下推走列式 base scanpdb_agg_*.sql与tantivy_cardinality_*.sql也是off但通过显式pdb.agg()调用驱动聚合。MVCC 维度同一查询分别用true/false新版语义对应transaction/raw测量开启与关闭事务可见性检查的开销并借此验证字符串字段 cardinality 的快速路径。三条自定义聚合扫描基准-- aggregate_scan_count_col.sqlpdb.agg底层在无 GROUP BY 下的计数 SET work_mem TO 4GB; SET paradedb.enable_aggregate_custom_scan TO on; SELECT COUNT(post_type_id) FROM stackoverflow_posts WHERE body ||| javascript;-- aggregate_scan_group_by.sqlaggregate custom scan 下按 post_type_id 分组计数 SET work_mem TO 4GB; SET paradedb.enable_aggregate_custom_scan TO on; SELECT COUNT(*) FROM (SELECT post_type_id FROM stackoverflow_posts WHERE body ||| javascript GROUP BY post_type_id);-- aggregate_scan_high_cardinality_group_by.sql高基数标签分组 多指标 SET paradedb.enable_aggregate_custom_scan TO on; SET work_mem 4GB; SELECT tags, COUNT(tags), MIN(score), MAX(score), SUM(score) FROM stackoverflow_posts WHERE body ||| javascript GROUP BY tags LIMIT 65000;这三条脚本共同覆盖了无分组计数 → 低基数分组 → 高基数分组 多指标三种典型负载形态。tags是 Stack Overflow 帖子上的高基数字符串字段每个帖子通常挂多个标签GROUP BY tags LIMIT 65000强制聚合器在高基数桶下工作用于压测分布式聚合收集器的桶管理与内存上限。COUNT(DISTINCT) 的列式基线-- basescan_distinct.sql数值 fast field 上的 COUNT(DISTINCT)关闭自定义聚合扫描 SET work_mem TO 4GB; SET paradedb.enable_aggregate_custom_scan TO off; SELECT COUNT(DISTINCT post_type_id) FROM stackoverflow_posts WHERE body ||| javascript;该脚本是目录中唯一保留的 base scan 变体。post_type_id是 SMALLINT 数值字段走列式 fast fieldenable_aggregate_custom_scan off迫使执行器退回 base scan 路径从而为COUNT(DISTINCT ...)提供一个不与任何自定义路径共享实现细节的对照基线。pdb.agg()聚合下推的公开入口pdb_agg_*.sql与tantivy_cardinality_*.sql展示了另一条 API 形态——显式调用pdb.agg()向 Tantivy 聚合收集器提交 JSON 规格-- pdb_agg_value_count_no_mvcc.sqlvalue_count 指标MVCC 关闭 SET work_mem TO 4GB; SET paradedb.enable_aggregate_custom_scan TO off; SELECT pdb.agg({value_count: {field: post_type_id}}, false) FROM stackoverflow_posts WHERE body ||| javascript;-- pdb_agg_high_cardinality_group_by_no_mvcc.sql高基数分组 多指标MVCC 关闭 SET paradedb.enable_aggregate_custom_scan TO off; SET work_mem 4GB; SELECT tags, pdb.agg({value_count: {field: tags}}, false) as count, pdb.agg({min: {field: score}}, false) as min, pdb.agg({max: {field: score}}, false) as max, pdb.agg({sum: {field: score}}, false) as sum FROM stackoverflow_posts WHERE body ||| javascript GROUP BY tags LIMIT 65000;在 pg_search/src/api/aggregate.rs 的模块注释中可以看到pdb.agg()的完整语义函数形态pdb.agg(jsonb)、pdb.agg(jsonb, text)和pdb.agg(jsonb, bool)三个重载。JSONB 参数是 Tantivy 聚合请求规格例如{cardinality: {field: tags}}。可见性参数text 重载第二个参数是visibility模式——transaction默认检查事务可见性与 PostgreSQL 语义一致raw跳过检查直接聚合原始索引数据更快但近似threshold仅当预估匹配行数低于paradedb.visibility_threshold时才检查。bool 重载已弃用这是旧的solve_mvcc拼写true等价transactionfalse等价raw保留是为了兼容存量查询。基准脚本中的true/false正是这一历史形态。GROUP BY 场景窗口函数形态pdb.agg(...) OVER ()是主推用法会在规划期被替换为内部占位函数window_agg()与GROUP BY组合时非窗口形态可能直接报错或走独立 API 路径执行。基准中的pdb_agg_high_cardinality_group_by_no_mvcc.sql通过多次pdb.agg(...)调用与 SQL 原生GROUP BY tags结合来实现多指标高基数聚合。cardinality 指标HyperLogLog 近似基数目录名cardinality对应的正是pdb.agg的cardinality指标。docs/reference/aggregates/metrics/cardinality.mdx 对该指标的语义做了权威说明它估计estimate字段中去重值的数量返回形如{value: 5.0}的结果与 SQL 的DISTINCT给出精确值但计算代价极高不同cardinality聚合使用HyperLogLog 算法以极低的内存开销给出高精度近似支持对字符串字段与数值字段使用tantivy_cardinality_*.sql针对字符串tags字段。这也是基准选择基数作为压测对象的原因它是近似但极快的聚合语义代表与精确但昂贵的COUNT(DISTINCT ...)basescan_distinct.sql形成互补对照。MVCC 快速路径字符串字段基数聚合的惰性可见性检查tantivy_cardinality_mvcc.sql的注释点出了本目录最值得玩味的实现细节-- tantivy_cardinality_mvcc.sql字符串字段 cardinalityMVCC 开启 → 惰性可见性检查 SET work_mem TO 4GB; SET paradedb.enable_aggregate_custom_scan TO off; SELECT pdb.agg({cardinality: {field: tags}}, true) FROM stackoverflow_posts WHERE body ||| javascript;惰性可见性检查lazy vischeck的实现在 pg_search/src/aggregate/exec.rs 的模块注释中有直接描述When every aggregation in a request is a cardinality over a string field, MVCC is solved inside the aggregation: tantivy consults the visibility filter only for docs whose value is not yet confirmed visible, instead of vischecking every matched doc up front.即当一个聚合请求中全部聚合都是字符串字段上的cardinality时执行器走use_cardinality_fast_pathexec.rs判定条件为solve_mvcc为真、请求非空且每个聚合都是AggregationVariants::Cardinality且目标字段在索引 schema 中是文本字段field.is_text()命中快速路径后不再为每个命中文档预先做可见性检查否则包装器会返回Some(VisibilityChecker)见 exec.rs而是构造一个DocVisibilityFilterFactory——只有在聚合器内部遇到尚未确认可见的取值时才按文档的 CTID通过FFType::new_ctid读取查询堆做一次检查exec.rs。这意味着开启 MVCC 的字符串字段基数查询其开销近似于HLL 计数 按需检查比逐文档回表验证廉价得多。tantivy_cardinality_no_mvcc.sql则提供false基线用于量化该快速路径相比完全跳过可见性检查的额外成本。关键 GUC 与运行环境基准脚本中反复出现的三个设置项设置取值作用SET work_mem TO 4GB会话级为聚合/排序操作分配内存保证大结果集不被过早 spill 到磁盘而污染耗时测量SET paradedb.enable_aggregate_custom_scan TO on/off布尔用户级Userset开关 ParadeDB 的自定义聚合扫描。on时以列式/DataFusion 方式替代行式聚合off时退回 base scan用作基线pdb.agg(..., true/false)的可见性参数transaction/raw/threshold或旧 bool 拼写控制是否执行 MVCC 可见性检查threshold模式配合paradedb.visibility_threshold使用paradedb.enable_aggregate_custom_scan的官方定义见 pg_search/src/gucs.rs一个用户级布尔 GUC用基于列的自定义聚合扫描在有利时替代基于行的聚合。docs/reference/aggregates/limitations.mdx同时提醒聚合下推要求查询中存在 ParadeDB 谓词如body ||| javascript或id pdb.all()否则查询不会走下推路径。关于数据集环境benchmarks/datasets/stackoverflow/config.toml 记录了根表stackoverflow_posts主键id、采样种子sampling_seed 723及badges/comments/users三个关联表数据由s3_base_path s3://paradedb-benchmarks/datasets/stackoverflow提供基准运行流程可参考 benchmarks/README.md 与 scripts/pg_search_run.sh。从基准到生产可迁移的结论综合 README 说明、8 个脚本与源码实现可以沉淀出三条可迁移到生产环境的结论不要在高基数大表上对 GIN 索引做原生精确去重聚合README 明确记载其在大数据集上慢到不可用。基数类聚合应优先交给 ParadeDB 的自定义聚合扫描列式 Tantivy 收集器。COUNT(DISTINCT ...)用 base scan 兜底cardinality指标用pdb.agg加速前者保精确后者用 HyperLogLog 换数量级的速度收益对字符串字段开启 MVCC 时执行器会自动走惰性可见性检查快速路径不必担心可见性语义丢失。enable_aggregate_custom_scan与可见性参数是调优旋钮而非开关关闭自定义扫描可获得纯 base scan 基线对照basescan_distinct.sqlfalse/raw模式适用于允许近似结果的场景threshold模式则适合在一致性要求高但与速度有折中需求的生产负载中精细控制。围绕本目录展开的更多聚合形态terms、sum、min/max、跨 join 下推、回退条件等可继续阅读 docs/reference/aggregates/overview.mdx 与 docs/reference/aggregates/limitations.mdx以及聚合执行器的完整实现 pg_search/src/aggregate/exec.rs。赞分享数据库搜索引擎全文检索向量数据库后端【免费下载链接】paradedbOne Postgres for your application data, full-text search, vector retrieval, and aggregations. Home of the pg_search extension.项目地址https://gitcode.com/gh_mirrors/pa/paradedb点击查看免费下载相关推荐ParadeDB 数值无过滤分桶聚合基准从 GROUP BY COUNT 到 pdb.agg 的列式聚合实测ParadeDB 数值无过滤分桶聚合基准从 GROUP BY COUNT 到 pdb.agg 的列式聚合实测 导读 本文围绕 ParadeDB 仓库中 Sta数据库搜索引擎全文检索向量数据库后端VoiceStudio 的 Supertonic-3 引擎31 语言 CPU 端 ONNX 语音合成接入与 sidecar 隔离架构实战VoiceStudio 的 Supertonic 3 引擎31 语言 CPU 端 ONNX 语音合成接入与 sidecar 隔离架构实战 Supertonic数据库搜索引擎全文检索向量数据库后端ParadeDB count_nofilter 基准查询解析无过滤 COUNT(*) 聚合在 BM25 索引上的三种执行路径ParadeDB count_nofilter 基准查询解析无过滤 COUNT 聚合在 BM25 索引上的三种执行路径 本文围绕 ParadeDB 仓库中 S数据库搜索引擎全文检索向量数据库后端上一篇从源码编译 Bazel基于 Bazel 的自举构建与零依赖 Bootstrap 完整实战指南下一篇Gas Town 工作分派指南用 Kilo 创建任务与车队Convoy实现多智能体协作创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表