summarize CLI 缓存设计全解析:SQLite 存储、键构造、配置与驱逐策略
2026/9/17 18:44:43 网站建设 项目流程

summarize CLI 缓存设计全解析:SQLite 存储、键构造、配置与驱逐策略

【免费下载链接】summarizePoint at any URL/YouTube/Podcast or file. Get the gist. CLI and Chrome Extension.项目地址: https://gitcode.com/GitHub_Trending/summarize/summarize

summarize 项目是一款面向 URL / YouTube / 播客 / 本地文件的"抓重点"工具,同时提供 CLI 与 Chrome 扩展。本文聚焦其 CLI 侧的缓存子系统(design 文档见 docs/cache.md),系统讲解缓存的设计目标、SQLite 存储结构、各类缓存键的 SHA-256 构造方式、配置文件参数、CLI 开关以及 TTL 与容量双重驱逐策略,并结合仓库源码逐一印证实现细节。读完本文,你将能准确判断一次运行是否会命中缓存、如何排查缓存失效、如何调优cache配置,以及如何安全地清理缓存。

一、缓存设计目标与整体架构

summarize 的缓存是仅存在于 CLI 侧的轻量级 SQLite 缓存,整个缓存就是一个单文件数据库(另有 WAL/SHM 侧车文件)。它的设计目标在 docs/cache.md 中明确为四点:

  • 避免重复的转录 / 提取 / 摘要:同一 URL 多次运行不必重复抓取与调用 LLM;
  • 无文件蔓延、磁盘占用有界:通过容量上限(maxMb)与 TTL 双重约束;
  • 安全的默认值、易于关闭:默认开启,但可通过 CLI 标志一键绕过;
  • 仅使用原生 SQLite:Node 24(node:sqlite)与 Bun(bun:sqlite)运行时自带能力,不引入任何第三方 SQLite 依赖。

代码职责划分非常清晰(见 src/cache.ts):

  • src/cache.ts:对外暴露缓存配置类型、路径解析、统计读取与清理工具;
  • src/cache-store.ts:真正持有 SQLite 行数据,负责读写、驱逐(eviction)、幻灯片文件清理以及转录缓存适配器;
  • src/cache-database.ts:把 Node / Bun 两种 SQLite 绑定隔离在同一个接口后面;
  • src/cache-keys.ts:集中实现全部版本化(versioned)缓存键构造逻辑。

此外还有一个独立于 SQLite 的媒体下载缓存(见下文第五节),两者并不共享存储。

二、存储:单文件 SQLite 与 pragma 设置

默认路径与覆盖方式

  • 默认数据库路径:~/.summarize/cache.sqlite
  • 覆盖方式:配置项cache.path

路径解析的完整逻辑位于 src/cache.ts 的resolveCachePath

  • 支持~~/前缀展开(借助环境变量HOMEUSERPROFILE);
  • 绝对路径直接使用;
  • 相对路径则基于 home 目录拼接;
  • 当配置未提供且无法解析 home 时返回null(即不启用数据库缓存)。
// resolveCachePath 关键行为(src/cache.ts) if (raw === "~" || raw.startsWith("~/")) { const expanded = raw === "~" ? home : join(home, raw.slice(2)); return resolvePath(expanded); } return isAbsolute(raw) ? raw : home ? resolvePath(join(home, raw)) : null;

SQLite pragma 与表结构

打开数据库时会执行以下 pragma(见 src/cache-store.ts):

pragma作用
busy_timeout5000写锁竞争时最多等待 5 秒,避免并发 CLI 进程直接报错
journal_modeWAL允许读写并发,配合-wal/-shm侧车文件
synchronousNORMAL平衡持久性与写入性能(WAL 下常规选择)
auto_vacuumINCREMENTAL删除数据后通过PRAGMA incremental_vacuum逐步回收空间

缓存表cache_entries的结构(src/cache-store.ts):

CREATE TABLE IF NOT EXISTS cache_entries ( kind TEXT NOT NULL, -- 缓存类别:extract / summary / transcript / chat / slides key TEXT NOT NULL, -- SHA-256 缓存键 value TEXT NOT NULL, -- 序列化后的缓存负载(文本或 JSON) size_bytes INTEGER NOT NULL, -- 条目大小,用于容量驱逐 created_at INTEGER NOT NULL, last_accessed_at INTEGER NOT NULL, -- LRU 排序依据 expires_at INTEGER, -- TTL 过期时间戳,NULL 表示永不过期 PRIMARY KEY (kind, key) ); CREATE INDEX idx_cache_accessed ON cache_entries(last_accessed_at); CREATE INDEX idx_cache_expires ON cache_entries(expires_at);

kind的合法取值由 core 包定义(packages/core/src/runtime/cache-store.ts):"extract" | "summary" | "transcript" | "chat" | "slides"。同一个 key 可以分属不同 kind,互不冲突(联合主键)。

写入采用 UPSERT(ON CONFLICT(kind, key) DO UPDATE),重复生成同键内容会覆盖旧条目并刷新last_accessed_at。值得注意的细节:close()时会先执行PRAGMA wal_checkpoint(TRUNCATE),把 WAL 合并回主库并截断侧车文件(src/cache-store.ts),这解释了为何--clear-cache需要同时删除-wal-shm

Node / Bun 双运行时绑定

src/cache-database.ts 通过检测全局Bun对象来决定加载哪个绑定:

if (isBun) { const mod = await import("bun:sqlite"); return new mod.Database(path); } const mod = await import("node:sqlite"); // Node 24 内置 DatabaseSync return new mod.DatabaseSync(path);

另外,在 Node 上运行时它会过滤掉ExperimentalWarning中与 sqlite 相关的警告(installSqliteWarningFilter),避免噪声刷屏。这是"原生 SQLite only"目标的直接实现证据。

三、缓存键:版本化 SHA-256 构造

所有缓存键都是 SHA-256 十六进制摘要,由 Nodecrypto/ Buncrypto提供(src/cache-keys.ts)。键的构造逻辑集中在src/cache-keys.ts,核心思想是把决定缓存有效性的所有因素打包进 JSON 后整体哈希

各类缓存键定义(docs/cache.md 原文)

缓存类别说明
Transcriptssha256({url, namespace, fileMtime?, formatVersion})本地文件路径会带上fileMtime以便在文件变更后自动失效
Extracted content(URL → text/markdown)sha256({url, extractSettings, formatVersion})提取设置变化(如抓取选项)会使键改变
Summariessha256({contentHash, promptHash, model, length, language, formatVersion})即使 URL 不同,只要内容哈希相同也能命中(内容哈希优先)
Slides(清单 + 输出目录中的磁盘图片)sha256({url, slideSettings, formatVersion})幻灯片设置变化会使键改变

源码级键构造

对照 src/cache-keys.ts 可以还原每个键的具体字段:

Transcript 键buildTranscriptCacheKeyValue,L155-L172):

hashJson({ url, namespace, fileMtime: fileMtime ?? null, formatVersion });

namespace是转录来源命名空间,目前包含 YouTube 模式(例如yt:autoyt:web),并由createCacheStoretranscriptNamespace参数注入(见 src/cache-store.ts)。本地媒体文件的fileMtime参与键计算,因此文件被重新生成后旧缓存自动失效。

Summary 键buildSummaryCacheKeyValue,L100-L123):

hashJson({ contentHash, promptHash, model, lengthKey, languageKey, formatVersion });

其中的contentHashpromptHash有专门构造规则:

  • promptHashbuildPromptHash,L35-L48):从 prompt 中抽取<instructions><context>两个标签块,拼合后哈希;若 prompt 中不存在任何标签则回退为对整个 prompt 做哈希;
  • contentHashbuildPromptContentHash,L50-L60):取自实际发送给模型的<content>块,因此幻灯片时间轴、转录补充信息等会影响该键;若 prompt 内容为空,则回退为附件的二进制字节哈希(buildAttachmentContentHash,L62-L76,其中记录附件的 kind、mediaType、byteLength 与字节哈希)。

长度(buildLengthKey,L78-L82)区分预设模式与字符数模式:preset:shortchars:1200这类形式;语言(buildLanguageKey,L84-L86)在自动模式下记为auto,否则用语言 tag。

Slides 键buildSlidesCacheKeyValue,L125-L153)包含url与一组幻灯片设置:ocroutputDirsceneThresholdautoTuneThresholdmaxSlidesminDurationSeconds

formatVersion:格式级失效开关

formatVersion是 core 包中的硬编码常量(packages/core/src/runtime/cache-store.ts 中CACHE_FORMAT_VERSION = 2)。当提示词格式或缓存负载结构发生不兼容变更时,维护者会提升该常量,使所有旧缓存一次性全部失效——这是比逐个改键更省事的全局失效手段。

摘要缓存"内容哈希优先"语义

摘要键不包含 URL,只包含contentHash。这意味着:同一篇文章以不同 URL 形式出现(例如带?utm_source参数、或镜像站),只要提取出的<content>块一致,摘要缓存就能命中,避免了重复调用 LLM。这是原文档强调的"cache hit even if URL differs (content hash wins)"。

四、配置:cache 与 cache.media

完整配置示例(docs/cache.md 原文):

{ "cache": { "enabled": true, "maxMb": 512, "ttlDays": 30, "path": "~/.summarize/cache.sqlite", "media": { "enabled": true, "maxMb": 2048, "ttlDays": 7, "path": "~/.summarize/cache/media", "verify": "size" } } }

默认值(与源码 packages/core/src/runtime/cache-store.ts 一致):

  • cache.enabled = true
  • cache.maxMb = 512
  • cache.ttlDays = 30
  • cache.path未设置(回退~/.summarize/cache.sqlite
  • cache.media.maxMb = 2048cache.media.ttlDays = 7cache.media.verify = "size"(src/media-cache.ts)

配置解析位于 src/config/sections.ts:

  • parseCacheConfig(L150-L175):读取cache对象,enabled必须是布尔值,maxMb/ttlDays必须是大于 0 的数字,path必须是非空字符串;任一字段非法都会抛出带路径与字段名的明确错误;
  • parseMediaCacheConfig(L110-L148):cache.media必须是对象,verify只接受"none""size""hash"三者之一,否则报错。

类型定义见 src/config/types.ts。所有字段均可省略,省略即采用默认值。

五、媒体缓存(下载文件):独立于 SQLite 的文件缓存

需要特别区分:媒体缓存是用于已下载媒体文件(yt-dlp 或直接媒体 URL)的独立文件缓存,不是 SQLite 数据库(docs/cache.md 原文明确标注 "This isnotthe SQLite DB")。

  • 默认路径:~/.summarize/cache/media
  • TTL:7 天;
  • 容量上限:2048 MB;
  • CLI:--no-media-cache仅禁用媒体缓存;
  • 注意:--no-cache不会禁用媒体缓存——二者互相独立。

实现位于 src/media-cache.ts,其机制与 SQLite 缓存有显著差异:

  • 目录内一个index.json充当索引(version: 1,按 URL 的 SHA-256 映射到文件名、大小、可选 sha256、媒体类型、时间戳与过期时间);
  • 通过.lock文件实现跨进程索引锁:带 PID 心跳(每 30 秒刷新 mtime),锁文件超过 5 分钟判定为陈旧并接管(L134-L173);
  • 文件命名{sha256(url)}{ext},扩展名根据原始文件名或 mediaType 推断(如.mp3.m4a.mp4.m3u8,推断规则见 L81-L100);
  • verify完整性校验三档:size(默认,比对记录大小与磁盘大小)、hash(重算文件 SHA-256)、none(不校验);
  • 写入先落到.incoming-*暂存文件,再原子 rename 进缓存目录,避免半成品污染缓存;
  • put时若单文件大小超过maxBytes则直接不缓存。

媒体缓存与 SQLite 缓存的对照:

维度SQLite 缓存媒体缓存
存储形态单文件cache.sqlite+ WAL/SHM目录 +index.json
默认上限512 MB2048 MB
默认 TTL30 天7 天
索引cache_entriesindex.json
并发控制SQLitebusy_timeout+ WAL.lock文件 + 心跳

六、CLI 标志与运维命令

三个缓存相关 CLI 标志(定义见 src/run/help.ts):

标志作用
--no-cache绕过摘要缓存的读写(LLM 输出)。提取/转录缓存仍然生效
--cache-stats打印缓存统计信息后退出
--clear-cache删除缓存数据库(连同 WAL/SHM),必须单独使用

校验逻辑位于 src/run/runner-setup.ts:--clear-cache--cache-stats都要求"must be used alone",一旦与其他参数混用会直接抛错(错误文案见 src/locale.ts,支持多语言本地化)。--no-cache只影响摘要缓存,而幻灯片模式还有独立的--no-cache语义("Bypass slide cache (force re-extract)",见 src/run/help.ts),说明缓存开关是按缓存类别精确作用的。

统计与清理的实现

readCacheStats(src/cache.ts)以只读模式打开数据库(PRAGMA query_only = ON),按kind分组统计条目数并汇总size_bytes;磁盘占用统计(getSqliteFileSizeBytes,L121-L130)会把主库、-wal-shm三个文件大小相加,且容忍侧车文件在 checkpoint 期间短暂消失。

clearCacheFiles(src/cache.ts)使用rmSync(force: true)依次删除主库、-wal-shm,即使文件不存在也不会报错。

七、驱逐策略:TTL + LRU 双保险

SQLite 缓存的驱逐(src/cache-store.ts)

  1. TTL 清理(读/写时触发)sweepExpiredexpires_at <= now一次性查出全部过期条目并删除;读取时也会惰性检查单条是否过期(readEntry),过期即删并返回 miss;
  2. 容量清理(写时触发)enforceSize先求SUM(size_bytes),若超过maxBytes,则按last_accessed_at升序每次批量取 50 条最久未访问的条目删除(LRU 语义),直到总大小回落到上限以内,最后执行PRAGMA incremental_vacuum回收空间。

此外幻灯片缓存比较特殊:deleteEntrykind === "slides"的条目会调用cleanupSlidesPayload,同时通过buildReferencedSlideArtifacts收集仍被其他 slides 条目引用的磁盘图片路径并予以保留(src/cache-store.ts),避免误删共享资源;clear()全量清理时也会先对 slides 条目做负载清理再清表。相关实现见 src/cache-slides-cleanup.ts。

媒体缓存的驱逐(src/media-cache.ts)

  • TTLpruneExpired删除expiresAtMs已到期的条目(含磁盘文件与索引项);
  • 容量enforceMaxBytes先补齐缺失的sizeBytes,若总量超限则按lastAccessAtMs升序(LRU)逐条删除直到达标。

两套缓存都是"读时惰性校验 + 写时主动清扫"的组合,整体设计保证磁盘占用始终有界。

八、与其他组件的边界:扩展面板缓存、转录来源与共享格式

浏览器扩展使用独立面板缓存

CLI 的 SQLite 缓存与 Chrome 扩展的面板缓存完全隔离:扩展使用chrome.storage.local,URL 维度键控条目采用30 天 TTL 与 8 MB 容量上限;它与 core 共享可移植的行格式化(buildPortableCacheRow/parseCacheJson,见 packages/core/src/runtime/cache-store.ts),而 daemon 的提取与摘要缓存仍然使用 SQLite。

转录来源库存与命中诊断

core 持有转录来源库存(TRANSCRIPT_SOURCES),同时服务提取与 SQLite 读取两条路径,涵盖内嵌字幕与 YouTube 原生媒体;缓存命中时会保留条目的原始来源诊断信息sourceserviceresourceKeynamespaceformatVersion一并写入负载,见 src/cache-store.ts),方便排查某条转录究竟来自哪个通道。

无第三方依赖

整个缓存体系只依赖 Node 24 / Bun 原生 SQLite 与 Node 内置crypto/fs模块,没有引入任何第三方 SQLite 或哈希库——这在 src/cache.ts 与 src/cache-database.ts 的 import 清单中可以完整验证。

九、实践建议与排查速查

基于上述设计与实现,给出以下可直接落地的操作建议:

  1. 判断是否命中摘要缓存:摘要键 =sha256({contentHash, promptHash, model, lengthKey, languageKey, formatVersion})。若你修改了自定义 prompt、切换了模型、改动了输出长度或语言,键必然变化,属正常失效;若只是 URL 不同而正文相同,仍可命中。
  2. 本地文件转录不失效:请确认文件mtime是否变化——转录键包含fileMtime,编辑文件后会自然产生新键。
  3. 限制磁盘占用:调小cache.maxMb/cache.ttlDays,或把cache.path指向专用磁盘。
  4. 精确绕过缓存:只想强制重新总结用--no-cache(提取/转录缓存仍生效);想完全关掉媒体下载缓存用--no-media-cache;两者互不影响。
  5. 查看与清理--cache-stats查看各 kind 条目数与总大小;--clear-cache一次性删除整个数据库(需单独使用,并会自动连带 WAL/SHM)。
  6. 怀疑缓存负载不兼容:关注CACHE_FORMAT_VERSION(当前为 2),升级后旧缓存整体作废属预期行为。
  7. 媒体文件损坏:把cache.media.verify从默认的size提升为hash,可在每次读取时重算 SHA-256 校验;追求极致性能可设为none

延伸阅读:CLI 配置整体说明见 docs/config.md,命令行用法见 docs/commands/index.md 与 docs/cli.md;缓存相关测试覆盖可参考 tests/cache.store.test.ts、tests/cache.keys.test.ts、tests/cache-stats.test.ts、tests/cache.path.test.ts、tests/cache-state.refresh.test.ts 与 tests/media-cache.test.ts。

【免费下载链接】summarizePoint at any URL/YouTube/Podcast or file. Get the gist. CLI and Chrome Extension.项目地址: https://gitcode.com/GitHub_Trending/summarize/summarize

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询