news 2026/9/12 13:23:50

Beads 存储层扩展契约:深入解析 `IssueFilter.Lite` 轻量 SELECT 与部分水合机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Beads 存储层扩展契约:深入解析 `IssueFilter.Lite` 轻量 SELECT 与部分水合机制

Beads 存储层扩展契约:深入解析IssueFilter.Lite轻量 SELECT 与部分水合机制

【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads

本文围绕 Beads 仓库内部存储扩展文档 engdocs/EXTENDING.md 展开,系统讲解嵌入或直连 bd 存储层的调用方必须遵守的IssueFilter.Lite契约:哪些重 TEXT 列会被省略、返回的*types.Issue上哪些字段可信、如何用IsLitePartial感知水合深度,以及这一机制在issueops存储栈中的强制点与后端覆盖边界。读完本文,你将能安全地在自己的调用方代码中启用 lite 扫描,并理解其与完整水合路径、模式 B(id 收缩)与 counts 大查询之间的协作关系。

这份文档的定位:存储 API 调用方契约

engdocs/EXTENDING.md开篇即明确自己的读者群体:它不是面向终端用户的文档,而是面向嵌入 bd 或直接对话存储层的代码。凡是调用store.SearchIssues(ctx, query, filter)的模块,都受这份契约约束。这意味着文中描述的IssueFilter.Lite是一个内部扩展点——它不会改变bdCLI 的默认行为,而是为那些希望跳过大型正文列、只做路由/列表类读取的调用方提供一条显式可选的快路径。

文档中出现的工程标识(如 be-uwvs.2)说明这是有计划的演进:lite 契约本身已经落地在 issueops 栈,而把filter.Lite贯通到 domain/db 代理服务器栈被明确推迟到后续的 CLI 接线工作中。

为什么需要 Lite:heavy TEXT 列的成本

bd 的issues表把一张 issue 的所有内容都放在同一行里,其中包含六个体量可观、以自由文本为主的 TEXT 列:

  • description(描述)
  • design(设计)
  • acceptance_criteria(验收标准)
  • notes(备注)
  • waiters(等待者列表)
  • payload(事件载荷)

这六列就是源码中 internal/storage/issueops/scan.go 定义的HeavyDropList。对一个以“列出所有 issue 供路由决策”为目的的查询来说,这六列中的绝大多数内容根本不会被读取:路由只需要身份、状态、优先级、时间戳、标签、依赖这些“小而常读”的列。把多 KB 的正文列随每一行一起物化,是对 IO 与内存的浪费。

IssueFilter.Lite正是为此设计:当filter.Lite == true时,存储层发出一个更窄的 SELECT,从投影列中剔除上述六个重列,让列表路径只搬运真正需要的数据。

调用方契约:三条 MUST/MAY 规则

engdocs/EXTENDING.md以非常明确的措辞规定了 lite 结果的语义,任何调用方都必须遵守:

MUST NOT:禁止读取被省略的六个字段

当以IssueFilter.Lite == true调用store.SearchIssues时,调用方绝对不得从返回的*types.Issue上读取DescriptionDesignAcceptanceCriteriaNotesPayloadWaiters。lite 扫描之后这些字段全部是零值——它们根本没有从行中取回,读取它们不产生任何信号。把“零值”误当成“内容为空”是这类 API 最常见的错误用法。

MAY:可以读取其余所有字段

除上述六个字段外,其余字段在 lite 扫描中全部保留:身份标识(ID、标题、内容哈希)、状态、优先级、类型、时间戳(创建/更新/开始/关闭/截止/推迟)、标签、依赖、元数据(metadata)、租约覆盖列(lease_expires_atheartbeat_atgranted_node)、行版本令牌(row_lock)与存储类(storage_class)。源码中 internal/storage/issueops/scan.go 的 lite 目标列表证实了这一点——它只跳过了HeavyDropList中的六列。

值得注意的是:metadata被刻意保留。scan.go 的注释明确说明它“体积小且路由会读取”;row_lock与租约三列也保留,因为它们是乐观并发令牌、活跃租约状态与授权副本标识,属于路由/认领代码的必读项,而非本次拆分要跳过的多 KB 正文。

MUST:用IsLitePartial检测部分水合

如果调用方需要针对水合深度做分支处理,必须通过issue.IsLitePartial来检测 lite 抓取的记录。该字段在 internal/types/types.go 中定义,是仅内部可见的标志(json:"-"),永远不会随序列化跨过线路(wire)——也就是说,任何外部消费者都无法通过 JSON 观察到它,这使它成为区分“真·无文本 issue”和“未水合的文本”的唯一内部手段。

恢复完整正文的唯一途径:GetIssue

lite 列表之后,如果针对某个特定 issue 需要完整正文,唯一正确的恢复方式是调用store.GetIssue(ctx, id)——它始终返回完整行。internal/storage/issueops/get_issue.go 的实现印证了这一点:它固定使用完整的IssueSelectColumns投影,配合LeaseJoin与单行扫描。

默认行为:零成本迁移

IssueFilter.Lite默认值为false(见 internal/types/types.go 的字段注释)。因此,所有未显式选择加入的现有调用点保持今天的行为:六个重列完整水合,Issue.IsLitePartialfalse。这是一个向后完全兼容的开关——新增该字段不会让任何存量代码悄悄改变语义,只有显式设置Lite: true的调用方才会进入窄投影路径。

这一设计在 internal/storage/issueops/search.go 中体现得极为直白:

func SearchIssuesInTx(ctx context.Context, tx DBTX, query string, filter types.IssueFilter) ([]*types.Issue, error) { proj := issueProjection if filter.Lite { proj = issueLiteProjection } return searchInTx(ctx, tx, query, filter, proj) }

filter.Lite只在两个投影字面量之间做一次选择,其余全部共享。

契约在源码中的强制位置

engdocs/EXTENDING.md精确列出了四处强制点,逐一对应到源码:

列清单:scan.go的三个常量

internal/storage/issueops/scan.go 定义:

  • IssueSelectColumns:完整水合的规范列清单,直接复用sqlbuild.IssueSelectColumns
  • IssueSelectColumnsLite:lite 列清单,与完整清单保持列顺序一致,仅剔除HeavyDropList
  • HeavyDropList:被省略的六个重列,注释明确要求其满足集合恒等式cols(IssueSelectColumnsLite) ∪ HeavyDropList == cols(IssueSelectColumns)

扫描助手:ScanIssueFromScanIssueLiteFrom

同一文件中的两个扫描函数按位置(positionally)绑定扫描目标,二者都必须与各自的列清单逐列对应:

  • ScanIssueFrom(scan.go)水合全部字段,IsLitePartial保持false
  • ScanIssueLiteFrom(scan.go)不读取六个重字段,并在返回前设置issue.IsLitePartial = true(第 414 行)。

注意 scan.go 第 68-69 行的警告:调用方必须保证查询精确选择了IssueSelectColumns(或 Lite 版)且顺序一致,位置扫描对列顺序极度敏感——这正是下面 schema-parity 守卫要锁死的东西。

SELECT 分发:search.go的投影选择

internal/storage/issueops/search.go 定义了两个searchProjection[*types.Issue]字面量:

  • issueProjection:完整列 +ScanIssueFrom
  • issueLiteProjection:lite 列 +ScanIssueLiteFrom,注释明确指向 engdocs/EXTENDING.md 作为调用方契约。

两者共享同一套 wisp-merge 与水合机制(searchTableInTxT):标签/依赖水合(hydrateIssueLabelsAndDeps)、issues+wisps 双平面合并、去重(GH#3567)、LeaseJoin租约连接、Pattern B id 收缩,全部由searchProjection[T]泛型抽象承载,lite 投影不另起炉灶。

Schema 一致性守卫:scan_test.go的两个测试

这是把契约“焊死”在 CI 上的关键:

  • TestIssueSelectColumns_LitePlusHeavyEqualsFull:集合守卫。未来任何列被加入IssueSelectColumns而未归类到IssueSelectColumnsLiteHeavyDropList二者之一,测试即失败,并给出可操作的错误信息;
  • TestIssueSelectColumnsLite_IsFullMinusHeavyInOrder:顺序守卫。集合比较无法发现“两个同类型列被对调”的问题——因为扫描是位置绑定,列对调后每行的值会静默错位而没有任何成员关系变化。该测试以“从完整清单原位删除重列必须精确复现 lite 清单”为 oracle,把 lite 清单变成完整清单的派生结果而非第二份手工维护的副本,防止“新列加到完整清单中间却追加到 lite 清单末尾”的漂移。

配套行为测试同样完备:TestScanIssueLiteFrom_LeavesHeavyFieldsBlank 验证六字段零值、身份字段仍水合、IsLitePartial=true;TestScanIssueFrom_PopulatesHeavyFields 验证其反面;search_lite_merge_test.go 则从端到端确认Lite: true确实触发了 lite 扫描路径。

源码级原理:投影抽象、租约覆盖与共享的列构建器

searchProjection[T]:一次抽象,三种投影

internal/storage/issueops/search.go 的searchProjection[T]结构体把“投影列 → 扫描函数 → ID 提取 → 后扫描水合 → Go 侧排序 → 租约连接”全部参数化。三种投影实例各司其职:

投影扫描用途
issueProjectionIssueSelectColumnsScanIssueFrom完整水合
issueLiteProjectionIssueSelectColumnsLiteScanIssueLiteFromlite 窄投影
idProjectionid裸 ID 扫描模式 B 收缩 / 部分 ID 解析

模式 B(idShrink)值得一提:对带Limit的宽投影查询,先跑廉价的SELECT id扫描,再对幸存行批量抓取并水合,避免为被 LIMIT 丢弃的行流式搬运整个投影。lite 投影与完整投影一样启用idShrink,说明 lite 与 Pattern B 是正交的两层优化:前者削减每行的宽度,后者削减行数

列清单的真正宿主:sqlbuild纯 SQL 构建器

列清单的实际定义不在 issueops,而在 internal/storage/sqlbuild/sqlbuild.go:

  • IssueBaseColumns(第 46-55 行)与IssueBaseColumnsLite(第 64-73 行):行本身的列,不含租约覆盖;
  • LeaseSelectColumns(第 79 行)与LeaseJoin(第 99-101 行):租约覆盖列与LEFT JOIN leases ON leases.issue_id = <table>.id片段;
  • IssueSelectColumns/IssueSelectColumnsLite(第 86-93 行):基础列 + 租约覆盖列的组合。

sqlbuild包被经典 issueops 栈(生产,*sql.Tx)与 domain/db 仓库栈(代理服务器)共享,其设计目标是保证两个实现针对相同过滤器产生相同的行集合(由 Seam A 奇偶校验套件固定)。任何选用IssueSelectColumnsLite的查询都必须同时在 FROM 子句中包含LeaseJoin(table)——漏掉连接会在leases.*引用上响亮失败,而不会静默出错。

counts 大查询中的 Lite 变体

lite 契约不止作用于无计数搜索。按 internal/types/types.go 的注释,filter.Lite两个栈上都被计入返回IssueWithCounts的计数页读取(bd list --json的两条路由与GET /v0/beads/issues),它作为sqlbuild.CountsHydration.Lite搭乘 counts 大查询,而 internal/storage/sqlbuild/counts.go 中渲染的就是基础列的带限定符变体(如ReadyWorkIssueColumns)。

后端覆盖与已知边界

engdocs/EXTENDING.md的最后一部分交代了诚实的能力边界,这对调用方至关重要:

  • 已支持filter.Lite目前仅由 issueops 支撑的存储后端(Dolt、嵌入式 Dolt)通过上述分发机制执行;
  • 尚未支持:代理服务器路径 internal/storage/domain/db(issueSQLRepositoryImpl.searchTable/fetchIssuesByIDs尚不检查filter.Lite,总是发出完整的issueSelectColumns查询并返回完全水合的 issue(IsLitePartial == false);
  • 定性:文档将其明确评价为“正确但未优化”(correct-but-unoptimized)——由于目前尚不存在 lite 调用方,这一差异在今天就不可见;把filter.Lite贯通 domain/db 栈的工作被推迟到 CLI 接线后续(be-uwvs.2+),不属于本次基础工作的一部分。

这意味着:如果你的调用方运行在 proxied-server/domain/db 路径上,设置Lite: true目前不会报错,也不会加速——你得到的是行为正确但完全水合的结果。这是可观测、可预期的降级,而不是契约违反。这一事实边界也提醒嵌入式集成者:判断某个查询是否真正走了 lite 路径,唯一可靠的方法是检查返回行的IsLitePartial是否为true,而不是假设设置即生效。

实践要点速查

为嵌入式调用方(库用户、扩展、自定义路由)总结安全用法:

  1. 列表/路由类读取:设置filter.Lite = true,只消费身份、状态、优先级、时间戳、标签、依赖与元数据字段;
  2. 绝不读取Description/Design/AcceptanceCriteria/Notes/Payload/Waiters——它们是零值,不构成“内容为空”的证据;
  3. 需要分支:用issue.IsLitePartial判断水合深度(该标志不会出现在任何序列化输出上);
  4. 需要正文:对单个 issue 调用store.GetIssue(ctx, id)恢复完整行;
  5. 验证生效:检查IsLitePartial == true,若在 domain/db 代理路径上观察到false,属于文档明确记载的“正确但未优化”状态;
  6. 做后端选型:确认目标存储是 issueops 支撑的 Dolt/嵌入式 Dolt 后端,否则 lite 暂不生效。

延伸阅读

  • 契约正文:engdocs/EXTENDING.md
  • 列清单与扫描实现:internal/storage/issueops/scan.go
  • 投影分发与共享机制:internal/storage/issueops/search.go
  • 纯 SQL 列构建器:internal/storage/sqlbuild/sqlbuild.go
  • IsLitePartialIssueFilter.Lite定义:internal/types/types.go
  • Schema 一致性守卫:internal/storage/issueops/scan_test.go
  • 读取器契约(含 lite 断言):backend/conformance/reader_contract.go

【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 13:22:26

OpenClaw框架实战:构建专属AI编程助手全流程指南

1. 项目概述 OpenClaw是一款开源的AI编程助手框架&#xff0c;它允许开发者构建专属的AI编程助手。这个项目标题"基于OpenClaw搭建专属编码龙虾从安装到生产级的AI编程助手实战指南"清晰地指出了几个关键点&#xff1a;使用OpenClaw框架、构建专属AI编程助手、涵盖从…

作者头像 李华
网站建设 2026/9/12 13:20:02

基于SwinTransformer与小波分析的轴承故障诊断实践

1. 项目概述轴承故障诊断一直是工业设备健康监测领域的重要课题。传统方法通常依赖专家经验或简单的频谱分析&#xff0c;而基于深度学习的智能诊断方法正在逐步改变这一局面。这个项目提出了一种结合小波时频分析和SwinTransformer的创新方法&#xff0c;通过Python和PyTorch实…

作者头像 李华
网站建设 2026/9/12 13:19:30

STM32定时器时钟源、PSC与ARR三大陷阱深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 13:19:08

远程智慧停车管理实战:从车牌识别到无人值守的完整指南

停车管理这个行业&#xff0c;过去十年其实是“三件套”打天下&#xff1a;保安亭、道闸杆、对讲机。一辆车进出&#xff0c;要摇窗、取卡、扫码、等抬杆&#xff0c;碰上缴费二维码模糊、手机没信号、前车磨蹭&#xff0c;后面喇叭能按成一片。尤其是夜间和恶劣天气&#xff0…

作者头像 李华