ARTICLE DETAIL

资讯详情

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

Backstage Catalog 数据库查询性能测试:Query Performance Battery 实战指南

Backstage Catalog 数据库查询性能测试:Query Performance Battery 实战指南 Backstage Catalog 数据库查询性能测试Query Performance Battery 实战指南【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本篇指南围绕 Backstage Catalog 的数据库查询性能测试方法展开在改动 catalog 数据库查询、索引或 schema 前后如何运行仓库中内置的Query Performance Battery查询性能测试集将结果与既有基线对比并据此判断是否存在性能回归。读完本文你将掌握 11 个覆盖 Catalog 核心读路径的测试场景、每种场景的健康查询计划healthy plan与反模式识别方法以及如何在生产规模副本上安全地执行EXPLAIN (ANALYZE, BUFFERS)并维护基线文档。测试的完整定义位于 queries.md基线记录位于 baseline.md二者配合使用也可通过/catalog-db-performance技能自动执行。为什么 Catalog 需要专门的数据库性能测试Backstage Catalog 的元数据存储规模非常可观。以仓库基线中记录的生产规模副本为例search表约13.2M 行、堆体积 11 GB—— 对它做一次全表顺序扫描Seq Scan代价是灾难性的relations表约3.5M 行、714 MB 堆final_entities约474K 行refresh_state约 476K 行refresh_state_references约 478K 行。在这样的规模下索引缺失、计划形状劣化如出现 Materialized CTE、临时文件落盘都会直接放大为秒级甚至分钟级的查询延迟。性能测试集的价值正在于此用一组固定的、贴近真实用户行为的场景快速暴露改动 schema 或索引后查询计划是否退化的问题。注意queries.md中明确标注了这些数据规模数字是编写测试集时的真实观测不同环境的绝对耗时不可直接横向比较关注的是计划形状变化与比例性回归。测试集组成场景、基线与运行方式整个性能测试体系由三个文件组成文件作用queries.md定义 11 个测试场景用户动作、对应的方法调用、参考 SQL、健康计划、反模式清单、全局反模式baseline.md记录最近一次运行的执行时间、规划时间、计划形状、Buffer 统计与反模式检测结果README.md简要说明场景与基线用于检测 catalog 数据库性能回归可经/catalog-db-performance技能或手动psql运行从仓库结构看该测试集挂在plugins/catalog-backend/src/tests/performance/目录下与集成测试、迁移测试并列说明它被定位为 catalog-backend 的常规质量保障手段之一。运行前的准备工作1. 获取数据库连接信息数据库连接参数host、port、user、database name是环境相关的不会存储在仓库中。运行前需要向维护者或 DBA 索取针对目标副本的连接细节。2. 了解两条执行路径queries.md提供了两种执行方式首选方法Preferred method实例化DefaultEntitiesCatalog或直接调用 REST 端点按每个场景给出的参数发起调用并在数据库侧用EXPLAIN (ANALYZE, BUFFERS)捕获计划。可以通过 knex 的 debug 日志或数据库代理来截获计划。这种方式测试的是代码实际生成的查询结论最可靠。备选方法Alternative直接用psql对生产规模副本执行参考 SQL。需要留意参考 SQL 是编写时对代码所生成查询的快照下结论前必须先核对它是否仍与当前代码一致。3. 关键约束不要把数据库连接信息写进仓库每个查询使用30 秒超时部分查询使用占位 entity ref如component:default/my-service目标数据库中可能不存在该实体——返回 0 行完全正常计划形状才是判断依据这一点在基线的场景 6/8/9 中均有体现见后文。4. 每个场景需要记录的四类指标执行时间Execution time规划时间Planning time计划形状Plan shape——顶层节点与所用索引名Buffer 统计——EXPLAIN (ANALYZE, BUFFERS)输出的shared hit、temp read/written等。同时要对照该场景的反模式清单以及queries.md底部的全局反模式清单进行检查。11 个测试场景详解每个场景对应一个真实的用户操作或后台处理动作queries.md为每个场景给出了方法调用、参考 SQL、健康计划与反模式。以下是完整清单与要点场景 1分页实体列表kindcomponent按名称排序用户动作打开默认的 catalog 表格视图。方法调用catalog.queryEntities({ filter: { kind: component }, orderFields: [{ field: metadata.name, order: asc }], limit: 20, credentials })。参考 SQL以search_key_value_entity_idx上的 Index Scan 驱动按search.value排序LIMIT 2120 条 1 条溢出判断。健康计划search_key_value_entity_idx提供排序序的 Index ScanLIMIT 在取到 21 行后短路。执行时间应5ms。反模式Materialized CTE意味着查询形状被迫全量求值、Sort 节点压在 Seq Scan 之上意味着索引没有提供顺序、执行时间 50ms。场景 2计数查询kindcomponent用户动作catalog 表格页脚的totalItems计数。方法调用与场景 1 相同计数值来自响应的totalItems字段计数与列表查询并行执行。参考 SQLCOUNT(*)加 EXISTS 过滤与列表查询共享同类型的索引访问路径。健康计划在search_key_value_entity_idx上做索引扫描并对 EXISTS 过滤使用嵌套循环。这是大结果集下天然的昂贵操作——执行时间就是任何需要计数的查询的下限。反模式search表出现 Seq Scan索引缺失执行时间随实体数超线性增长。场景 3分页实体列表无过滤LIMIT 21用户动作不加任何过滤条件的显示全部视图。这是分页的最坏情况——LIMIT 短路至关重要。方法调用catalog.queryEntities({ limit: 20, credentials })。参考 SQL直接扫final_entities按entity_ref排序后LIMIT 21。健康计划final_entities_entity_ref_uniq上的 Index Scan执行时间1ms。反模式Sort 节点索引未提供顺序、final_entities上 Seq Scan。场景 4Facets 查询kindtemplatefacetspec.type用户动作侧边栏中小结果集的 facet 计数。方法调用catalog.facets({ filter: { kind: template }, facets: [spec.type], credentials })。参考 SQL外层在search上按 facet 键聚合内层通过 EXISTS 过滤出kindtemplate的实体。健康计划facet 聚合使用search_facets_covering_idx或search_key_value_entity_idx过滤子查询使用索引支撑的 EXISTS。反模式外层对search做 Seq Scan小结果集却用 Hash Join 而非 Nested Loop。场景 5Facets 查询kindcomponent——大结果集用户动作与场景 4 相同但过滤集很大数万个组件检验计划在规模下是否仍然高效。方法调用同场景 4仅把kind换成component。参考 SQL同场景 4仅过滤值不同。健康计划与场景 4 类似但大过滤集下可以使用 Hash Join执行时间与匹配实体数成正比。反模式search表外层或内层Seq Scan临时文件落盘检查Buffers: temp。场景 6按 ref 查询单个实体用户动作按名称打开单个实体页面。方法调用catalog.entitiesBatch({ entityRefs: [component:default/my-service], credentials })。参考 SQLWHERE final_entities.entity_ref component:default/my-service。健康计划final_entities_entity_ref_uniq上的 Index Scan执行时间1ms。反模式Seq Scan——灾难性的说明唯一索引缺失。场景 7全文过滤LIKE %player%kindcomponent用户动作在 catalog 表格的搜索框中输入关键词。前导通配符使得索引无法提供排序序的短路。方法调用catalog.queryEntities({ filter: { kind: component }, orderFields: [{ field: metadata.name, order: asc }], fullTextFilter: { term: player }, limit: 20, credentials })。参考 SQL在场景 1 的基础上追加AND search.value LIKE %player%。健康计划search_key_value_entity_idx上按key metadata.name做 Index ScanLIKE 作为 Filter。LIKE 本身前导通配符无法走索引但查询其余部分必须由索引驱动。反模式search表 Seq Scan——LIKE 应当是索引扫描上的过滤条件而不是触发顺序扫描的元凶。场景 8关系遍历实体谱系 ancestry用户动作/entities/by-name/.../ancestry端点。方法调用catalog.entityAncestry(component:default/my-service, { credentials })。参考 SQL迭代遍历的一步refresh_state_references按target_entity_ref过滤后与final_entities连接LIMIT 10。健康计划refresh_state_references_target_entity_ref_idx上的 Index Scan 对final_entities_entity_ref_uniq的 Nested Loop Index Scan。反模式refresh_state_references上 Seq Scantarget 索引缺失relations上 Seq Scantarget_entity_ref索引缺失。场景 9Stitching入向引用计数上下文每次 stitch 时都会执行用于判定实体是否为孤儿orphan。不是用户可见动作但对处理吞吐量至关重要。参考 SQLSELECT count(*) FROM refresh_state_references WHERE target_entity_ref ...。健康计划refresh_state_references_target_entity_ref_idx上的Index Only Scan执行时间1ms。反模式Seq Scan索引缺失。场景 10对抗性用例无过滤全量计数用户动作统计整个 catalog 的实体数无任何过滤确立计数性能的上限。方法调用catalog.queryEntities({ limit: 0, credentials })响应中的totalItems即全量计数。参考 SQLsearch与final_entities连接后COUNT(*)。健康计划search_key_value_entity_idx上的索引扫描执行时间与 catalog 总规模成正比。反模式任一表 Seq Scan50 万实体 catalog 上执行时间 30s。场景 11孤儿检测反连接anti-join上下文周期性的孤儿清理deleteOrphanedEntities默认每30 秒运行一次。不是用户可见动作但是持续性的后台负载。参考 SQLrefresh_stateLEFT OUTER JOINrefresh_state_references后取target_entity_ref IS NULL的行LIMIT 100。健康计划利用refresh_state_references.target_entity_ref上的索引做反连接执行时间500ms。反模式refresh_state_references上 Seq Scan这是最需要避免被扫描的主表Hash Join 把整张 references 表拉进内存。全局反模式任何场景都不允许出现queries.md底部定义了五条全局反模式它们在任何查询中出现都意味着严重问题search表 Seq Scan—— search 表体积 11 GB任何顺序扫描都是灾难性的relations表 Seq Scan—— 714 MB 堆、3.5M 行必须走索引Materialized CTE—— 会阻止 LIMIT 短路曾是分页查询缓慢的原始根因临时文件落盘EXPLAIN输出中的Buffers: temp—— 说明查询正在物化一个很大的中间结果内层 Seq Scan 的 Nested Loop—— 通常意味着内表连接列上缺索引。基线解读以 baseline.md 为例理解对比方法基线文档 baseline.md 记录了 2026-05-18 在生产规模副本上的完整运行结果。当时的 catalog 规模为约 474Kfinal_entities、约 13.2Msearch行、约 3.5Mrelations、约 478Krefresh_state_references、约 476Krefresh_state。本次运行摘要部分场景场景执行时间结论1. 分页列表kindcomponent12.5 msOK —— 较上次改善3. 分页列表无过滤0.1 msExcellent6. 按 ref 查询0.1 msExcellent7. 全文过滤LIKE903.5 ms可接受 —— 但相对上次有回归见下10. 全量计数1317.4 msOK —— 改善11. 孤儿检测255.7 ms已修复—— Hash Anti Join 取代 Nested Loop原为 30s 超时与上一次基线2026-05-16的对比要点Catalog 规模变化实体从约 545K 缩至 474Krefresh_state从 984K 缩至 476K几乎减半refresh_state_references从 547K 缩至 478K。规模变化会显著影响触及这些表的场景。主要改善场景 1 分页列表 39% 提速计划从串行改为 2 worker 的 Gather Merge场景 2 计数 45% 提速场景 10 全量计数 41% 提速主要受益于 catalog 缩小场景 11 孤儿检测从30s 超时降到 255ms —— 计划器改为 Parallel Hash Anti Join这很可能得益于refresh_state表缩小后跨过了计划器成本模型的阈值。需要关注的回归场景 7LIKE 全文过滤903ms 对 566ms慢了 60%。两次运行都不得不扫描全部组件前导通配符不可避免但上次计划通过 Memoize 更早地应用 LIKE 过滤本次改为 HashAggregate 方式先求值全部约 55K 个组件再过滤。此外组件数本身也从约 46K 涨到了约 55K。值得持续监控。非查询原因的变化场景 4facets kindtemplate3.7ms 对 1.1ms 慢 3.3 倍但模板数从 9 个涨到 196 个计划形状健康并非查询回归。零影响场景场景 6/8/9 计划形状与耗时完全一致。这个对比过程展示了正确的基线比较方法先排除数据规模变化的影响再聚焦计划形状改变与比例性回归——这正是 skill 中关注 plan shape 变化和比例性回归这一原则的具体体现。何时运行性能测试catalog-db-performanceskill 明确列出了四类触发时机修改 catalog 数据库查询的前后添加/删除/修改索引的前后schema 迁移的前后周期性运行以建立新鲜基线。底层实现佐证索引与代码如何支撑健康计划测试集中反复出现的健康计划如search_key_value_entity_idx、final_entities_entity_ref_uniq、refresh_state_references_target_entity_ref_idx、search_facets_covering_idx并非假设而是由实际迁移脚本创建的真实索引。迁移 20260510000000_search_indices_and_dedup.js 的头部注释详细说明了 search 表的去重与覆盖索引策略覆盖索引说明search_entity_key_value_idx—— 在(entity_id, key, value)上建立UNIQUE约束保证去重后的唯一性search_key_value_entity_idx—— 在(key, value, entity_id)上建立的覆盖索引是场景 1/2/4/7/10 计划的核心驱动索引定义search_facets_covering_idx—— 在(key, original_value, entity_id)上的部分覆盖索引WHERE original_value IS NOT NULL服务于 facets 聚合同时删除被取代的旧索引search_key_value_idx与search_key_original_value_idx。该迁移在 PostgreSQL 上使用CREATE INDEX CONCURRENTLY避免阻塞读写但对 13M 行的表可能需要数分钟迁移本身是幂等的且每个步骤都会检查当前状态、清理中断留下的 INVALID 索引。注释还特别提醒如果 Kubernetes liveness 探针在索引构建完成前杀掉 Pod构建会反复从头开始大型安装建议在部署前手动执行其中的 SQL。场景 11 涉及的孤儿检测其实现位于 deleteOrphanedEntities.ts。该函数在事务中循环执行反连接查询先用 CTEorphans找出没有任何入向引用的refresh_state行leftOuterJoinwhereNull再连接relations把孤儿实体的子实体一并纳入候选随后批量删除并调用markForStitching标记受影响的实体重新 stitch。基线中Hash Anti Join 取代 Nested Loop、从超时降到 255ms的修复正是作用于这段查询路径。此外早期迁移 20210302150147_refresh_state.js 中已定义了refresh_state_entity_ref_uniq与refresh_state_references_target_entity_ref_idx为场景 8/9/11 提供了索引基础。回归判定标准与报告要点每次运行结束后需要与基线对比并标记以下三类问题执行时间回归 50%计划形状变化换了不同的索引、出现了新的 Sort/Seq Scan 节点出现了上次运行没有的新反模式。同时务必记住catalog 规模差异会影响绝对耗时因此重点应放在计划形状变化与比例性回归上。最终向用户汇报时需要给出哪些场景改善、哪些场景回归、是否检测到全局反模式并把新结果以相同格式写回 baseline.md在底部追加对比小节记录显著变化。总结Query Performance Battery 为 Backstage Catalog 的数据库层提供了一套低成本、高覆盖的回归检测手段11 个场景覆盖列表分页、计数、facet、单实体查询、全文过滤、关系遍历、stitching 与后台清理等全部核心读路径每一条都配对了健康计划与反模式判据配合生产规模副本上的基线记录开发者可以在改动查询、索引或 schema 的每个环节快速确认没有把数据库搞坏。对于 plan shape 异常或反模式的出现还可顺着迁移脚本与catalog-backend的数据库操作源码进一步定位根因。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表