Mastra 的 DuckDB 一体化存储实战:HNSW 向量检索与全量可观测性追踪
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
@mastra/duckdb是 Mastra 框架中一个"单进程内嵌数据库"式的双域存储包:一方面通过 DuckDB VSS 扩展提供 HNSW 索引的向量相似度检索(DuckDBVector),另一方面基于 DuckDB 的 OLAP 能力为 traces、metrics、logs、scores、feedback 五类可观测性信号提供持久化与高级查询(DuckDBStore)。读完本文,你将掌握如何在 Mastra 应用中以组合存储的方式接入 DuckDB,配置内存与线程参数规避大库查询导致的 CPU/内存尖峰,并熟练使用从基础列表到 1.8.0 新增的 span 属性、feedback、score 与顶层 metadata 谓词在内的完整 trace 查询语法。
包定位:一个进程、两份能力
从仓库入口文件 stores/duckdb/src/index.ts 可以看到,@mastra/duckdb同时导出了两条产品线:
export { DuckDBVector } from './vector/index'; export type { DuckDBVectorConfig, DuckDBVectorFilter } from './vector/types'; export { DuckDBConnection, DuckDBStore, ObservabilityStorageDuckDB } from './storage/index'; export type { DuckDBStorageConfig, DuckDBStoreConfig, ObservabilityDuckDBConfig } from './storage/index';- 向量存储:
DuckDBVector实现MastraVector接口,使用 DuckDB VSS 扩展创建 HNSW 索引,不需要额外部署向量数据库服务。 - 可观测性存储:
DuckDBStore(组合存储)只暴露observability域,内部由惰性加载的ObservabilityStorageDuckDB委托实现;DuckDBConnection则封装了底层连接、参数绑定、事务与关闭逻辑。
根据 stores/duckdb/package.json,该包唯一运行时依赖是@duckdb/node-api@^1.5.2-r.2,对@mastra/core的 peer 依赖区间为>=1.58.0-0 <2.0.0-0,要求 Node.js>=22.13.0。也就是说,只要你的应用已有 Mastra 核心运行时,安装这一个包即可同时获得检索与追踪两大存储能力。
安装与向量存储:HNSW 相似度检索开箱即用
安装命令(对应 stores/duckdb/README.md):
npm install @mastra/duckdb初始化一个持久化向量库并注册到 Mastra:
import { Mastra } from '@mastra/core'; import { DuckDBVector } from '@mastra/duckdb'; const vectorStore = new DuckDBVector({ id: 'rag-store', path: './rag-vectors.duckdb', }); // 接入 Mastra 的 RAG 系统 const mastra = new Mastra({ vectors: { ragStore: vectorStore, }, });包在 1.0.0 引入向量存储时给出的完整示例(见 stores/duckdb/CHANGELOG.md 1.0.0 条目),包含了建索引、写入与查询三步:
import { DuckDBVector } from '@mastra/duckdb'; const vectorStore = new DuckDBVector({ id: 'my-store', path: ':memory:', // 或 './vectors.duckdb' 用于持久化 }); await vectorStore.createIndex({ indexName: 'docs', dimension: 1536, metric: 'cosine', }); await vectorStore.upsert({ indexName: 'docs', vectors: [[0.1, 0.2, ...]], metadata: [{ text: 'hello world' }], }); const results = await vectorStore.query({ indexName: 'docs', queryVector: [0.1, 0.2, ...], topK: 10, filter: { text: 'hello world' }, });从源码 stores/duckdb/src/vector/index.ts 可以看到底层机制:
- 初始化时执行
INSTALL vss; LOAD vss;加载向量扩展;若 VSS 不可用则降级为基本数组运算并打印VSS extension not available, using basic array operations警告(第 75-86 行)。 createIndex阶段创建USING HNSW (vector)的 HNSW 索引(第 337-348 行),若索引创建失败同样降级为线性扫描。- 构造器默认值为
path: ':memory:'、dimensions: 1536、metric: 'cosine'(第 42-47 行),这些默认值都可被传入的DuckDBVectorConfig覆盖。
可观测性存储:以组合存储接入 Mastra
1.1.0 起包新增了 DuckDB 可观测性存储,支持 traces、metrics、logs、scores、feedback 五类信号。官方推荐的接入方式是使用MastraCompositeStore,把 DuckDB 专门用作 observability 域,其余域交给其他存储(如 LibSQL):
import { Mastra } from '@mastra/core/mastra'; import { DefaultExporter, Observability } from '@mastra/observability'; import { MastraCompositeStore } from '@mastra/core/storage'; import { LibSQLStore } from '@mastra/libsql'; import { DuckDBStore } from '@mastra/duckdb'; const duckDBStore = new DuckDBStore(); const libSqlStore = new LibSQLStore(); const storage = new MastraCompositeStore({ id: 'composite', domains: { ...libSqlStore.stores, observability: duckDBStore.observability, }, }); export const mastra = new Mastra({ agents: {/* your agents here */}, observability: new Observability({ configs: { default: { serviceName: 'obs-test', exporters: [new DefaultExporter()], }, }, }), storage, });DuckDBStore的构造与close()语义(stores/duckdb/src/storage/index.ts):
- 构造器默认
id: 'duckdb',内部创建DuckDBConnection并装配observability域;stores只包含 observability 一项,因此文档明确建议通过组合存储补齐其他域。 close()会释放 DuckDB 的原生文件锁。1.4.1 的修复说明这一点很关键:开发模式下mastra dev热重载若不释放文件锁,重启进程会遇到Conflicting lock is held错误;Mastra.shutdown()会自动调用它,重复调用是安全的 no-op。
底层连接管理(stores/duckdb/src/storage/db/index.ts)提供query、execute、executeTransaction(BEGIN/COMMIT/ROLLBACK)、executeBatch(单连接批量执行无参 DDL,用于加速 schema 初始化)等方法。参数绑定使用bindParam显式类型化方法(bindNull/bindVarchar/bindInteger/bindDouble/bindBoolean/bindBigInt/bindTimestamp),这修复了依赖 DuckDB 类型推断时在json_extract_string等 SQL 上下文报Cannot create values of type ANY的问题(1.1.0 Patch)。
高级 Trace 查询:1.8.0 的四种谓词形态
1.7.0 为 DuckDB 引入了与 ClickHouse 对齐的 advanced trace query 能力(过滤、分组、排序、游标分页与共享跨适配器语义);1.8.0 在此基础上补齐了四类更丰富的谓词,全部通过mastraClient.queryTraces使用。
1. 同 span 属性谓词(span properties)—— 可基于status、model、duration、outcome、identity、lineage 等字段过滤:
await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { spans: { some: { op: 'eq', left: { path: 'status' }, right: { literal: 'error' } } } }, });2. 关联 feedback 过滤—— 按反馈类型等字段筛选 trace;同一feedbackId的重复写入会保留最新一条,保证谓词求值结果一致:
await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { feedback: { some: { op: 'eq', left: { path: 'feedbackType' }, right: { literal: 'rating' } } } }, });3. 更丰富的 score 谓词—— 例如按scoreSource过滤:
await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { scores: { some: { op: 'eq', left: { path: 'scoreSource' }, right: { literal: 'automated' } } } }, });4. 顶层 metadata 谓词—— 直接针对 trace 的顶层元数据做存在性判断:
await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z', }, where: { op: 'notExists', path: 'metadata.parentMessageId' }, });这些谓词最终编译为 SQL。查询编译器位于 stores/duckdb/src/storage/domains/observability/trace-query.ts,通过字段注册表将逻辑字段映射到具体 SQL 列与参数类型:
TRACE_FIELDS:traceId、threadId、resourceId、startedAt、endedAt、entityName、entityType、environment、status(status 由CASE WHEN r.error IS NOT NULL THEN 'error' ELSE 'success' END计算得出);SPAN_FIELDS:name、spanType、model、provider、durationMs、error以及entityVersionId系列版本字段;SCORE_FIELDS:scorerId、scorerVersion、scoreSource、score、spanId等;FEEDBACK_FIELDS:feedbackType、feedbackSource、feedbackUserId、sourceId、comment等。
指标查询:count_distinct 聚合与服务端 TopK
1.3.0 为指标存储 API 引入了两个面向高基数场景的能力(stores/duckdb/CHANGELOG.md 1.3.0 条目)。
count_distinct 聚合:getMetricAggregate、getMetricBreakdown、getMetricTimeSeries接受aggregation: 'count_distinct'并配合distinctColumn。DuckDB 后端映射为approx_count_distinct(ClickHouse 则用uniq),从而让基于threadId、resourceId等高基数维度构建的仪表盘保持快速且结果有界。distinctColumn被限制在低/中基数的分类允许列表内(entityType、entityName、parentEntityType、parentEntityName、rootEntityType、rootEntityName、name、provider、model、environment、executionSource、serviceName),ID 列被禁止——因为对近似唯一的值做去重计数会退化为行数,几乎没有分析价值。
await store.getMetricAggregate({ name: ['mastra_llm_tokens_total'], aggregation: 'count_distinct', distinctColumn: 'model', filters: { timestamp: { start, end } }, });服务端 TopK:getMetricBreakdown支持limit与orderDirection,让 breakdown 永远不从数据库拉回列的全量基数。排序始终按聚合后的value进行;orderDirection在 top-N(DESC,默认)与 bottom-N(ASC)之间切换:
await store.getMetricBreakdown({ name: ['mastra_agent_duration_ms'], aggregation: 'sum', groupBy: ['threadId'], limit: 20, orderDirection: 'DESC', });1.6.0 还加入了批量 trace ID 过滤,可一次查询多个指定 trace 的指标明细:
const result = await observability.getMetricBreakdown({ name: ['mastra_model_total_input_tokens'], aggregation: 'sum', groupBy: ['traceId'], filters: { traceIds: ['trace-1', 'trace-2'] }, });评分与反馈分析:聚合、分桶、时间序列与分位数
1.1.0 起 DuckDB 支持基于 score 与 feedback 的分析查询,包括计数、平均等聚合、按 model/environment 等维度的分桶、固定间隔的时间序列,以及 p50/p95 等分位数计算。官方示例:
const result = await store.observability.getScorePercentiles({ scorerId: 'relevance', percentiles: [0.5, 0.95], interval: '1h', }); // { series: [{ percentile: 0.5, points: [{ timestamp, value }] }, ...] }1.2.0 为所有可观测性信号(logId、metricId、scoreId、feedbackId)统一引入了唯一 ID,用于框架管线内的去重与跨系统关联;用户侧 API(logger.info()、metrics.emit()、addScore()、addFeedback())保持不变。1.3.0 增加了按scoreId直接取回评分记录的能力(getScoreById),无需扫描分页的评分列表。1.6.4 为 feedback 增加reviewStatus列(默认needs-review),支持读写映射、按状态列表过滤以及updateFeedbackReviewStatus更新方法。
1.8.0 补充了按 ID 删除 feedback 与 score 的能力,并支持可选的 organization 与 resource 过滤:
await observability.deleteFeedback({ feedbackIds: ['feedback-1'] }); await observability.deleteScores({ scoreIds: ['score-1'], resourceId: 'resource-1' });性能调优:memoryLimit 与 threads
1.5.2 修复了一个针对大型 DuckDB 库的严重问题:在 Studio 打开 traces 页或调用列表 API 时,每次翻页/轮询都会解压整个span_events表,导致 CPU 全核打满、内存膨胀数 GB。修复手段有三:分页查询只扫描请求 span 所在的时间范围;带过滤与自定义排序的查询先在窄列集上分页再重建完整 span 负载;无新数据时 delta 轮询直接短路。
同时该版本为DuckDBStore增加了两个资源控制参数:
const store = new DuckDBStore({ path: 'mastra.duckdb', memoryLimit: '4GB', // 默认 '2GB' threads: 2, // 默认:每个 CPU 核心一个线程 });这两个参数在 stores/duckdb/src/storage/db/index.ts 中直接映射为 DuckDB 实例选项:
memoryLimit对应max_memory,默认'2GB'。DuckDB 自身的默认值是系统内存的 80%,对一个内嵌在应用服务器里的存储来说过于激进——单条大查询就可能把进程撑到 swap;文件型数据库可以把超内存操作溢写磁盘,而:memory:数据库无法溢写,所以对超大内存库查询需要调高此值。threads对应 DuckDB 的threads选项,默认每 CPU 核一个线程;在多租户共享服务器上调低可以避免查询独占所有核心。
1.5.2 的描述给出了量级参考:在 multi-GB 数据库上,trace 列表查询的 CPU 开销大约下降为原来的五分之一,并保持在有界内存预算内。1.6.2 还修复了 Studio 对 DuckDB 可观测性存储的 metrics/logs 探测问题,确保资源页能被正确识别。
轻量列表与增量轮询(delta polling)
围绕大库列表性能,包提供了"轻量 + 增量"的组合能力:
- 1.3.2 暴露
GET /observability/traces/light及对应的存储支持,用于拉取不含 span 负载的分页 trace 列表行。 - 1.5.1 修复了
listTracesLight因惰性存储门面缺少转发方法而抛This storage provider does not support listing lightweight traces的问题——DuckDB 本身完全支持该操作。 - 1.6.1 修复了轻量列表忽略 delta 轮询参数的问题:
listTracesLight之前忽略mode、after、limit,导致客户端每次轮询都重新拉取首页且永远拿不到delta/deltaCursor。现在 delta 请求只返回游标之后新记录的轻量行;行数据携带短inputPreview(替代完整 input)、计算出的status与 spanmetadata,页面响应包含deltaCursor,轮询可以随时切换到 delta 模式。该实现依赖@mastra/core >= 1.57.0提供的buildInputPreview与computeTraceStatus共享助手。 - 1.4.0 在 core、DuckDB、ClickHouse 三端统一加入了可观测性列表 API 的 delta polling 支持;1.7.0 的高级 trace 查询则自带游标分页。
ObservabilityStorageDuckDB门面会依据@mastra/core是否声明observability-delta-polling特性来切换静态特性列表(['metrics', 'logs', 'trace-query']与['metrics', 'logs', 'delta-polling', 'trace-query'],见 stores/duckdb/src/storage/index.ts 第 14-16 行),保证与旧版核心运行时组合时能够优雅降级。
迁移与版本兼容要点
- 信号 ID 迁移:从旧版升级到 1.2.0+ 时,对既有的 DuckDB 可观测性信号表需要先执行
npx mastra migrate再初始化 store,以便应用新的信号 ID schema(同样适用于 ClickHouse)。 - 版本字段迁移:1.1.2 为 spans、metrics、scores、feedback、logs 表新增了
entityVersionId、parentEntityVersionId、rootEntityVersionId列用于按实体版本过滤/分组 trace,并附带了针对现有库的 ALTER TABLE 迁移。 - reviewStatus 迁移:1.6.4 新增 feedback 的
reviewStatus列,默认值needs-review。 - core 版本约束:1.6.1 起该包的 peer 依赖下限被提高到与所使用 API 匹配(
>=1.58.0-0);1.1.0 曾明确"较老的@mastra/core在使用 DuckDB 可观测性存储时会显示升级错误"。若ObservabilityStorageDuckDB加载具体实现时发现 core 缺少对应导出(如does not provide an export named等),会抛出结构化的MastraError(ErrorCategory.SYSTEM,错误 IDOBSERVABILITY_STORAGE_DUCKDB_CORE_UPGRADE_NOT_IMPLEMENTED)提示升级 core。 - dev 热重载:升级到 1.4.1+,
DuckDBStore.close()会在关闭时释放原生文件锁,避免mastra dev热重载时出现Conflicting lock is held。 - 供应链修复:1.4.3 为 2026-06-17 "easy-day-js" 供应链事件做了版本清理(patch bump 发布干净版本并前移
latestdist-tag)。 - 安装兼容:1.2.0 起使用可解析的
@duckdb/node-api版本区间,解决安装失败问题。
版本能力速览
| 版本 | 核心能力 |
|---|---|
| 1.0.0 | 引入DuckDBVector,HNSW 索引向量检索,支持:memory:与文件持久化 |
| 1.1.0 | 引入 DuckDB 可观测性存储(traces/metrics/logs/scores/feedback),score/feedback 聚合、分桶、时间序列与分位数分析 |
| 1.2.0 | 全信号唯一 ID(需npx mastra migrate),getTraceLight |
| 1.3.0 | listBranches/getSpans,count_distinct聚合与服务端 TopK,getScoreById |
| 1.4.0 | 可观测性列表 API 的 delta polling;1.4.1 修复热重载文件锁;1.4.3 供应链清理 |
| 1.5.1 | 修复listTracesLight门面转发 |
| 1.5.2 | 大库列表性能修复,新增memoryLimit(默认 2GB)与threads配置 |
| 1.6.x | 指标批量 trace ID 过滤;Studio metrics/logs 探测修复;feedbackreviewStatus |
| 1.7.0 | 高级 trace 查询(过滤/分组/排序/游标分页、跨适配器语义一致、查询形态感知的关系读取) |
| 1.8.0 | span 属性、feedback、score、顶层 metadata 四类谓词;feedback/score 按 ID 删除 |
综上所述,@mastra/duckdb的价值在于"零外部服务":向量检索与可观测性都跑在进程内,天然适合本地开发、单机部署与边缘场景。生产化时请重点关注三点——用MastraCompositeStore把 observability 域交给 DuckDB、按机器资源显式设置memoryLimit/threads、在升级涉及 schema 的版本后及时执行npx mastra migrate。相关实现与测试可继续在仓库 stores/duckdb/src(向量、连接、各可观测性域)与 stores/duckdb/src/storage/domains/observability/index.test.ts、stores/duckdb/src/storage/domains/observability/trace-query.test.ts 中深入研读。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考