docs/design/metric-design.md
本方案讨论的是 OpenViking 的“指标体系(metrics)”,目标是把 /metrics 做成一个可持续抓取的 Prometheus 导出端点,并与 /api/v1/observer/*(瞬时状态)和 /api/v1/stats/*(分析统计)形成清晰边界。
OpenViking 当前已经存在三类与“观测”相关的入口:
| 入口 | 当前定位 | 当前实现特征 |
|---|---|---|
/api/v1/observer/* | 组件瞬时状态查询 | ObserverService 组装 queue/vikingdb/models/lock/retrieval 状态 |
/api/v1/stats/* | 业务统计与内容质量分析 | StatsAggregator 动态查询 memory 分类、热度、陈旧度、session extraction 等 |
/metrics | Prometheus 指标导出 | 当前依赖 PrometheusObserver.render_metrics() 输出文本 |
结合代码现状可以归纳出关键事实:
| 模块 | 位置 | 当前职责 | 问题摘要 |
|---|---|---|---|
BaseObserver | openviking/storage/observers/base_observer.py | get_status_table/is_healthy/has_errors | 瞬时状态语义清晰 |
PrometheusObserver | openviking/storage/observers/prometheus_observer.py | 进程内存储 Counter/Histogram + 渲染 /metrics | 与 Observer 体系职责不一致,属于“异类” |
| 业务埋点(retrieval/embedding/vlm 等) | 多处 | 直接回调 observer 写指标 | 采集层与导出层强耦合,难扩展/难测试 |
/metrics 路由 | openviking/server/routers/metrics.py | 直接取 app.state.prometheus_observer | 路由层绑定具体实现对象,而非 exporter 抽象 |
当前 /metrics 已打通的指标主要集中在 retrieval/embedding/vlm/cache 的少量 counter/histogram 指标族,覆盖面偏窄。
PrometheusObserver 破坏 Observer 一致性:它本质是“注册中心 + 导出器”混合体,而非瞬时状态读取器。account_id 切片能力,同时必须防止演化为高基数(user/session/resource 等维度严禁进入 /metrics)。/metrics 与 /api/v1/stats 易混淆:分析型统计不应迁入高频抓取的 Prometheus 模型。设计目标:
Observer 回归“瞬时状态观测”本位。/metrics 提供系统化的 Gauge / Counter / Histogram,并明确失败语义(best-effort、valid/stale)。/metrics 与 /api/v1/stats 边界,避免抓取放大成本。非目标:
本节明确指标体系设计中需要持续成立的几项基础原则。这些原则用于约束后续的抽象分层、模块边界、Telemetry 关系以及对外观测接口的职责划分。
概览层保留以下四个核心抽象:
MetricDataSourceBaseMetricCollectorMetricRegistryBaseMetricExporter现有 Observer、Telemetry、TaskTracker、业务事件埋点,本质上都属于“数据来源”,但不等同于指标系统本身。
因此:
这种拆分可以避免 retrieval、embedding、vlm 等业务代码直接耦合某一个 exporter。
MetricRegistry 作为进程内指标的唯一真实来源,负责承接统一的读写与约束。
职责范围如下:
注意:这里的“当前视图”仅指读取时获取 registry 内部状态,不引入独立的 Snapshot 架构层。
Exporter 只负责协议导出,不负责指标语义生成。
Exporter 不负责:
Exporter 只负责:
/metrics 或其他监控后端。这意味着 Prometheus 只是首个落地实现,而不是整个 metrics 架构的中心。
/metrics、/api/v1/observer/*、/api/v1/stats/* 三类入口继续并存,但必须保持清晰分工。
/metrics 面向机器抓取,强调低基数、低成本、可持续聚合;/api/v1/observer/* 面向人工诊断,强调瞬时状态可读性;/api/v1/stats/* 面向业务分析,允许更重的查询与统计逻辑。这三个入口共享部分数据来源,但不共享同一种输出模型。
本节给出指标体系的抽象主链路,只保留最小且稳定的四类角色,不提前展开任何具体实现类。
抽象主链路如下:
graph LR
A["MetricDataSource"]
B["BaseMetricCollector"]
C["MetricRegistry"]
D["BaseMetricExporter"]
A --> B
B --> C
C --> D
四类抽象角色的职责如下:
| 抽象角色 | 作用 | 只负责什么 | 不负责什么 |
|---|---|---|---|
MetricDataSource | 提供原始状态或原始事件 | 产生可观测输入 | 不直接生成 Prometheus 文本 |
BaseMetricCollector | 将输入转为统一指标语义 | 采集、归一化、写 registry | 不直接暴露 HTTP |
MetricRegistry | 统一存储进程内指标 | 注册、校验、读写、并发控制 | 不关心数据源来自哪里 |
BaseMetricExporter | 按协议导出指标 | 格式化与导出 | 不直接理解业务链路语义 |
抽象主链路对应的数据流顺序如下:
MetricDataSource 提供瞬时状态、请求结束摘要或运行时事件;BaseMetricCollector 将这些输入映射为 Counter / Gauge / Histogram 等统一指标;MetricRegistry 保存当前进程内全部指标状态;BaseMetricExporter 在需要时读取 registry 并输出给外部系统。抽象分层确定之后,还需要从设计边界上进一步保证:
因此,需要对相应角色施加明确约束:
MetricDataSource 接触和读取。Collector、MetricRegistry、Exporter 不再直接访问业务服务、业务存储或业务上下文。MetricRegistry,不拥有业务真实状态。
/metrics 抓取前刷新,对应读取只能是轻量快照、已有聚合结果或轻量探针结果。在边界约束成立之后,模块划分也需要与之保持一致,避免目录结构重新把已经划清的职责边界混回同一层。为避免新指标体系继续与 storage/observers/ 的职责混杂,集中放入 openviking/metrics/,并按“注册中心 / 采集器 / 导出器 / 规则 / 启动装配”进行分组。
逻辑分组如下:
| 模块分组 | 职责 |
|---|---|
registry | 指标存储、指标定义、标签校验 |
collectors | retrieval / embedding / vlm / queue / task / observer / telemetry 等采集器 |
exporters | Prometheus / 未来 OTel / InfluxDB |
naming | 指标命名规则、label 规则、bucket 约定 |
bootstrap | app 启动时初始化 registry / exporters / collector manager |
operation telemetry 与 metrics 并不是两套彼此替代的系统。前者继续作为请求级结构化摘要存在,服务于单次调用的解释与排障;后者则只对白名单字段做低基数抽取,用于持续抓取、聚合与告警。
operation telemetry 已经拥有很多有价值的数据字段,如:
duration_mstokens.*vector.*queue.*semantic_nodes.*memory.extract.*errors.*但并非所有字段都适合直接进入 /metrics,因此这里只对白名单字段进行指标化抽取:
| telemetry 分组 | 是否指标化 | 原因 |
|---|---|---|
duration_ms | 是 | 低基数、高通用性 |
tokens.total / llm / embedding | 是 | 有明确容量与成本价值 |
vector.searches / scored / returned | 是 | 检索链路核心运行指标 |
queue.* | 是 | 适合形成任务吞吐与错误指标 |
semantic_nodes.* | 有条件 | 更适合资源导入链路,不应无限扩散标签 |
memory.extract.* | 部分保留在 stats / telemetry | 更偏业务分析,首版不全面指标化 |
errors.message | 否 | 高基数、可能泄漏上下文 |
/metrics、/api/v1/observer、/api/v1/stats 的职责边界三类对外观测接口共享部分数据来源,但并不共享同一种输出模型,也不应追求由同一套接口承担全部观测需求。明确这一边界,是为了避免后续设计在机器抓取、人工诊断与业务分析之间发生职责漂移。
| 接口 | 定位 | 数据特征 | 输出风格 |
|---|---|---|---|
/metrics | 机器消费型监控接口 | 低基数、可持续抓取、可聚合 | Prometheus exposition |
/api/v1/observer/* | 人工诊断型瞬时状态接口 | 组件当前状态、表格或结构化描述 | JSON + 可读状态文本 |
/api/v1/stats/* | 业务分析型接口 | 可能昂贵、可扫描、可聚合但非高频 | JSON 统计结果 |
MetricDataSource 设计在实现层,抽象角色 MetricDataSource 统一落为基类 BaseMetricDataSource,并在其下进一步划分 EventMetricDataSource、StateMetricDataSource、DomainStatsMetricDataSource、ProbeMetricDataSource 四类中间抽象。这四类中间抽象并非单纯的逻辑标签,而是分别对应不同的数据访问契约与刷新方式,因此在架构层被明确区分。
这些输入可能是:
在 OpenViking 中,MetricDataSource 采用“统一基类 + 中间契约层 + 具体实现类”的三层结构。具体继承关系如下:
graph LR
A["BaseMetricDataSource"]
B["EventMetricDataSource"]
C["StateMetricDataSource"]
D["DomainStatsMetricDataSource"]
E["ProbeMetricDataSource"]
F["HttpRequestLifecycleDataSource"]
G["ResourceIngestionEventDataSource"]
H["SessionLifecycleDataSource"]
O["EncryptionEventDataSource"]
I["QueuePipelineStateDataSource"]
J["TaskStateDataSource"]
K["RetrievalStatsDataSource"]
L["ModelUsageDataSource"]
M["ObserverStateDataSource"]
N["ServiceProbeDataSource"]
P["StorageProbeDataSource"]
Q["RetrievalBackendProbeDataSource"]
R["ModelProviderProbeDataSource"]
S["AsyncSystemProbeDataSource"]
T["EncryptionProbeDataSource"]
A --> B
A --> C
A --> D
A --> E
B --> F
B --> G
B --> H
B --> O
C --> I
C --> J
D --> K
D --> L
D --> M
E --> N
E --> P
E --> Q
E --> R
E --> S
E --> T
这四类中间抽象对应的数据访问契约如下:
| 中间抽象 | 契约语义 | 典型读取方式 | 典型指标类型 |
|---|---|---|---|
EventMetricDataSource | 提供增量事件流或生命周期事件 | 读取事件批次、消费新事件、按游标增量拉取 | Counter、Histogram |
StateMetricDataSource | 提供当前状态快照 | 按需读取当前状态、重复读取返回最新值 | Gauge |
DomainStatsMetricDataSource | 提供领域内部已经维护好的累计统计 | 读取累计计数、汇总结果、统计快照 | Counter、Gauge、部分 Histogram 输入 |
ProbeMetricDataSource | 提供探针执行结果或健康检查结果 | 执行探测、读取最近一次探测结果 | Gauge、Health |
各类具体数据源的职责如下:
| 实现 | 分类 | 主要对接对象 | 输出形态 | 适用场景 |
|---|---|---|---|---|
HttpRequestLifecycleDataSource | EventMetricDataSource | FastAPI middleware、路由响应 | 请求生命周期事件 | request total、duration、status code、in-flight |
ResourceIngestionEventDataSource | EventMetricDataSource | ResourceProcessor、ResourceService、watch / wait 流程 | 资源处理事件与阶段摘要 | parse / finalize / summarize / wait / watch duration |
SessionLifecycleDataSource | EventMetricDataSource | session create / used / commit / archive | 会话生命周期状态与事件 | commit 生命周期、contexts_used、archive 状态 |
EncryptionEventDataSource | EventMetricDataSource | Encryptor、API Key 验证路径、KDF / Key Loader | 加密操作事件与密钥处理事件 | encrypt / decrypt / verify count、duration、bytes、kdf / key_load 耗时、auth_failed |
QueuePipelineStateDataSource | StateMetricDataSource | QueueManager、Semantic DAG、request queue stats | 队列与流水线状态 | pending、in_progress、processed、error_count、semantic_nodes |
TaskStateDataSource | StateMetricDataSource | TaskTracker | 当前任务状态 | pending、running、completed、failed 数量 |
RetrievalStatsDataSource | DomainStatsMetricDataSource | RetrievalStatsCollector | 检索累计统计 | query count、zero result、latency、rerank 情况 |
ModelUsageDataSource | DomainStatsMetricDataSource | VLM / Embedding / Rerank token tracker | 模型使用统计 | 调用次数、耗时、token 消耗 |
ObserverStateDataSource | DomainStatsMetricDataSource | ObserverService、LockManager、VikingDBManager | 诊断聚合视图 | component health、lock、vikingdb、models、retrieval |
ServiceProbeDataSource | ProbeMetricDataSource | 服务初始化、组件装配状态 | 服务探针结果 | startup completion、service readiness |
StorageProbeDataSource | ProbeMetricDataSource | AGFS / VikingFS、系统表访问检查 | 存储探针结果 | storage readability、storage writability、system table readiness |
RetrievalBackendProbeDataSource | ProbeMetricDataSource | VikingDB、检索后端最小能力检查 | 检索后端探针结果 | backend readiness、collection availability |
ModelProviderProbeDataSource | ProbeMetricDataSource | VLM / Embedding / Rerank provider 可用性检查 | 模型依赖探针结果 | provider readiness、credential availability |
AsyncSystemProbeDataSource | ProbeMetricDataSource | Queue、TaskTracker、后台消费线程检查 | 异步系统探针结果 | queue readiness、worker liveness |
EncryptionProbeDataSource | ProbeMetricDataSource | Root Key、KMS / Vault Provider、加密组件检查 | 加密探针结果 | root key readiness、kms availability、encryption component health |
设计边界如下:
SessionLifecycleDataSource,也可能间接贡献 TaskStateDataSource。EncryptionEventDataSource;涉及 Root Key readiness、KMS / Vault Provider 可用性、加密组件健康度时,则由 EncryptionProbeDataSource 承接。SessionLifecycleDataSource 只支持聚合级监控,不支持 session_id 级细粒度监控,也不允许把 session_id 作为指标标签。ObserverStateDataSource 属于诊断视图适配结果,适合承接当前 Observer 体系的聚合输出,但不应继续向更高层堆叠新的 summary。operation telemetry 属于请求级链路汇总能力,不作为一级 MetricDataSource 建模;如需复用其结果,应通过 collector 或兼容适配层接入。/api/v1/stats 暴露,不在本节中抽象为独立 DataSource。ProbeMetricDataSource 采用方案 B:不再设置总的 SystemProbeDataSource 聚合父类,而是直接按依赖类型细分为 Service / Storage / Retrieval Backend / Model Provider / Async System / Encryption 六类 probe 子类。Observer、Telemetry、TaskTracker、HTTP Router 与各类业务服务继续保持原有职责;指标系统只把它们视为数据源,而不将其改造成 exporter 或 collector。
BaseMetricCollector 设计Collector 位于指标体系中的语义转换层,负责接收不同类型的可观测输入,并将其稳定映射为对 MetricRegistry 的统一写入操作。随着 MetricDataSource 被进一步划分为 Event、State、DomainStats 与 Probe 四类,Collector 侧采用对应的分层组织,而不再停留在仅区分 Event / State 的简化模型。
在这一设计下,Collector 不再只是“埋点写入器”,而是承担统一收口职责:一方面屏蔽上游数据源在访问方式与更新节奏上的差异,另一方面对下游 registry 暴露一致的写入语义。这样可以保证新增指标链路时,扩展点仍然集中在 Collector 层,而不会把 source-specific 逻辑扩散到 registry 或 exporter。
Collector 采用“基类 + 四类子抽象 + 多个具体实现”的组织方式:
graph LR
A["BaseMetricCollector"]
B["EventMetricCollector"]
C["StateMetricCollector"]
D["DomainStatsMetricCollector"]
E["ProbeMetricCollector"]
F["RetrievalCollector(event)"]
G["EmbeddingCollector"]
H["VLMCollector"]
I["CacheCollector"]
J["EncryptionCollector"]
K["QueueCollector"]
L["LockCollector"]
M["VikingDBCollector"]
N["ObserverHealthCollector"]
O["TaskTrackerCollector"]
P["RetrievalCollector(stats)"]
Q["ModelUsageCollector"]
R["ObserverStateCollector"]
S["ServiceProbeCollector"]
T["StorageProbeCollector"]
U["RetrievalBackendProbeCollector"]
V["ModelProviderProbeCollector"]
W["AsyncSystemProbeCollector"]
X["EncryptionProbeCollector"]
A --> B
A --> C
A --> D
A --> E
B --> F
B --> G
B --> H
B --> I
B --> J
C --> K
C --> L
C --> M
C --> N
C --> O
D --> P
D --> Q
D --> R
E --> S
E --> T
E --> U
E --> V
E --> W
E --> X
抽象层次的分工如下:
| 抽象层次 | 职责 | 典型触发方式 |
|---|---|---|
BaseMetricCollector | 定义 collector 统一行为与 registry 写入约束 | 所有 collector 共用 |
EventMetricCollector | 处理“事件发生一次,就写一次”的场景 | HTTP 请求完成、资源处理完成、VLM 调用完成、加密操作完成 |
StateMetricCollector | 处理“读取当前状态并刷新 gauge”的场景 | /metrics 抓取前刷新 |
DomainStatsMetricCollector | 处理“读取已有累计统计并写入 registry”的场景 | 检索统计、模型使用统计、Observer 聚合视图刷新 |
ProbeMetricCollector | 处理“执行探针并映射为健康类指标”的场景 | readiness / dependency probe 刷新 |
Collector 与 DataSource 的主映射关系如下:
| DataSource 分类 | 优先对应的 Collector 分类 | 说明 |
|---|---|---|
EventMetricDataSource | EventMetricCollector | 事件型输入天然适合 Counter / Histogram |
StateMetricDataSource | StateMetricCollector | 当前状态快照天然适合 Gauge |
DomainStatsMetricDataSource | DomainStatsMetricCollector | 已聚合统计应直接桥接到 registry |
ProbeMetricDataSource | ProbeMetricCollector | 健康检查结果应统一映射为 readiness / health 指标 |
具体 collector 分工如下:
| Collector | 分类 | 数据输入 | 写入指标 |
|---|---|---|---|
RetrievalCollector | EventMetricCollector | retrieval 完成事件 | 请求数、零结果数、结果数、耗时、rerank 使用情况 |
EmbeddingCollector | EventMetricCollector | embedding 完成事件 | 请求数、耗时、错误数 |
VLMCollector | EventMetricCollector | token / duration 事件 | 调用数、耗时、token 统计 |
CacheCollector | EventMetricCollector | cache 命中或未命中事件 | hit / miss |
EncryptionCollector | EventMetricCollector | encrypt / decrypt / verify / kdf / key_load 事件 | 加密次数、认证失败、耗时、字节量、密钥处理统计 |
QueueCollector | StateMetricCollector | QueueManager / DAG 当前状态 | pending、in_progress、processed、errors |
LockCollector | StateMetricCollector | LockManager 当前状态 | active、waiting、stale |
VikingDBCollector | StateMetricCollector | VikingDB collection 当前状态 | health、vectors、collections |
ObserverHealthCollector | StateMetricCollector | ObserverService 结果 | component health、component errors |
TaskTrackerCollector | StateMetricCollector | TaskTracker 当前状态 | pending、running、completed、failed |
RetrievalCollector | DomainStatsMetricCollector | RetrievalStatsDataSource | 检索累计统计桥接 |
ModelUsageCollector | DomainStatsMetricCollector | ModelUsageDataSource | 模型使用累计统计桥接 |
ObserverStateCollector | DomainStatsMetricCollector | ObserverStateDataSource | 诊断聚合视图桥接 |
ServiceProbeCollector | ProbeMetricCollector | ServiceProbeDataSource | service readiness、startup completion |
StorageProbeCollector | ProbeMetricCollector | StorageProbeDataSource | storage readiness、system table availability |
RetrievalBackendProbeCollector | ProbeMetricCollector | RetrievalBackendProbeDataSource | backend readiness、collection availability |
ModelProviderProbeCollector | ProbeMetricCollector | ModelProviderProbeDataSource | provider readiness、credential availability |
AsyncSystemProbeCollector | ProbeMetricCollector | AsyncSystemProbeDataSource | queue readiness、worker liveness |
EncryptionProbeCollector | ProbeMetricCollector | EncryptionProbeDataSource | root key readiness、kms availability、encryption component health |
设计理由:在引入四类 Collector 之后,整个体系的语义边界更清晰:
对于 retrieval 链路,统一保留 RetrievalCollector 命名:它既可以接收 retrieval 完成事件,也可以读取 RetrievalStatsDataSource 的累计快照。两类输入在实现上可以走不同分支,但不再拆出单独的 RetrievalStatsCollectorAdapter 名称,以避免把“同一指标域的两条输入路径”误写成两套 collector。
MetricRegistry 设计MetricRegistry 是整个体系的稳定中心,负责统一注册、校验、存储和读取指标,但不负责采集触发,也不承担协议导出职责。它对外维持统一接口,不针对 Event / State / DomainStats / Probe 四类 Collector 再拆分多套写入 API,语义分流应由 Collector 自身完成。
MetricRegistry 必须满足以下能力:
| 能力 | 要求 |
|---|---|
| 统一注册 | 每个指标在进程内只注册一次,防止重复定义 |
| 类型约束 | 同名指标不能一会儿是 Counter、一会儿是 Gauge |
| 标签约束 | 每个指标的 label key 集合固定,运行时只允许填充固定维度 |
| 并发安全 | 允许 FastAPI 协程、队列线程、后台清理线程同时读写 |
| 低开销读取 | /metrics 抓取不应持有长时间大锁 |
| 可扩展性 | 不耦合 Prometheus 专属概念,避免 registry 层直接出现 exposition 文本逻辑 |
| 统一写入接口 | 对外维持统一的 counter / gauge / histogram 写入接口,不按 source 类型暴露分裂 API |
Registry 只解决“如何统一保存指标”,不解决:
统一接口策略如下:
inc_counter 或 observe_histogram;set_gauge;也就是说,Registry 不感知“当前写入的是事件、状态、统计还是探针”,它只感知“要写入哪种指标类型、哪个指标名、哪些标签和值”。
由此,registry 可以作为整个指标系统的稳定核心,而不随 exporter 或 collector 的演化频繁变动。
BaseMetricExporter 设计Exporter 位于指标体系最下游,只负责把 registry 中的当前指标状态转换成外部协议。与 Registry 相同,Exporter 也遵循统一接口原则:它只依赖统一的读接口,不感知指标来自 Event / State / DomainStats / Probe 中的哪一路。
Exporter 采用如下继承关系:
graph TB
A["BaseMetricExporter"]
B["PrometheusExporter"]
C["OtelExporter"]
D["InfluxDBExporter"]
A --> B
A --> C
A --> D
各 exporter 的定位如下:
| Exporter | 定位 | 首版状态 |
|---|---|---|
PrometheusExporter | 负责 /metrics exposition 输出 | 首版必须落地 |
OtelExporter | 对接未来 OTel 指标导出 | 预留 |
InfluxDBExporter | 对接未来 InfluxDB | 预留 |
统一读取策略如下:
本节讨论 /metrics 请求从进入服务到返回结果之间的完整运行时序,并明确导出前刷新策略的边界。Prometheus 抓取 /metrics 时,先执行必要的 collector 刷新,再统一读取 registry 快照,最后由 exporter 完成协议序列化。
Prometheus 抓取 /metrics 时,执行以下流程:
sequenceDiagram
participant P as Prometheus
participant R as /metrics Router
participant E as PrometheusExporter
participant C as StateCollectors
participant G as MetricRegistry
P->>R: GET /metrics
R->>E: export()
E->>C: refresh()
C->>G: set Gauge values
E->>G: read unified metric snapshot
G-->>E: metric samples
E-->>R: text exposition
R-->>P: 200 text/plain
设计理由:采用“导出前刷新 StateCollector”的原因是:
对于 Queue、Lock、VikingDB、Task、Observer health 以及各类 probe 而言,设计目标不应是“每次 /metrics 抓取都严格同步拿到瞬时最新值”,而应是“在保证抓取路径稳定、延迟可控的前提下,提供足够新的状态视图”。换句话说,/metrics 首先是一个可持续抓取的监控接口,其次才是状态刷新触发点。
基于这一点,State / Probe 刷新统一采用“默认 scrape-triggered refresh + 基于 TTL 的刷新控制 + stale-while-revalidate” 组合策略:
/metrics 时触发刷新;MetricRegistry 的职责。这里需要进一步明确两类信息的边界:
MetricRegistry 保存的是对外可见的当前指标值,是 Exporter 读取并序列化的唯一真实来源;因此,TTL、超时、是否允许返回旧值、是否触发后台刷新等策略不下沉到 MetricRegistry。Registry 只负责统一存储指标,不负责刷新调度,也不在其读取接口上附带“读取即触发刷新”的副作用。刷新时机由 /metrics 导出路径中的执行层显式控制,例如由 Exporter 或独立的 CollectorManager 在导出前调用 refresh_if_needed(),随后再统一读取 Registry。
对于返回旧值(stale value),本方案默认允许该行为,而不是把它当作异常分支。原因在于大多数状态类与探针类指标本身就不是强瞬时一致性的观测对象;相比“为了获取最新值而阻塞整个 /metrics 抓取路径”,继续导出 MetricRegistry 中最近一次成功写入的指标值更符合监控系统的使用语义。
为了避免“旧值长期存在但不可见”,本方案采用更明确的可观测语义:对于无法定义失败默认值的状态类指标,指标设置之初即包含一个最小的有效性标签(如 valid=1/0),以便让告警与看板显式识别“当前值是否可用”。当刷新失败时,不强行覆盖写入默认值,而是将该 valid 标签置为 0,并继续导出 MetricRegistry 中最近一次成功写入值。
valid 标签;Collector 在刷新成功时写入 valid=1,刷新失败时写入 valid=0;在 Collector 分类上,以下对象优先视为“启用基于 TTL 的刷新控制”的候选:
相对地,Queue、Task 等纯内存态或低成本状态读取,仍应优先保持同步 scrape-triggered refresh,不必默认引入基于 TTL 的刷新控制。
当前实现中,HTTP 请求指标并不只是“某个 EventMetricCollector 的普通上游事件源”,而是由一个专门的 middleware 负责把请求生命周期翻译为低基数、best-effort 的 HTTP metrics 事件。其运行时语义如下:
/metrics/health/ready/api/v1/sessions/{session_id})作为 route 标签;"/__unmatched__"),以避免把动态 ID、UUID、资源路径直接带入标签,造成基数爆炸。(route, account_id) 维持;route 与 account_id 等上下文字段;account_id 解析分两阶段完成在展开具体指标清单之前,先把“指标族 → DataSource → Collector → Metric Type”的关系收敛成统一视图,可以避免后续列表只见指标名、不见来源链路。对于首版范围内的所有指标,都应能够映射回明确的 DataSource 与 Collector;如果某个指标无法说明其输入来源或采集责任,就不应直接进入首版清单。
| 指标族 | 主要 DataSource | 主要 Collector | 主要指标类型 | 代表指标 |
|---|---|---|---|---|
| HTTP 请求监控 | HttpRequestLifecycleDataSource | HTTPCollector | Counter、Histogram | request total、duration、status code |
| 检索链路监控 | RetrievalStatsDataSource、retrieval 完成事件 | RetrievalCollector | Counter、Histogram | retrieval requests、zero result、latency |
| 模型链路监控 | ModelUsageDataSource、模型调用事件 | VLMCollector、EmbeddingCollector、ModelUsageCollector | Counter、Histogram | model calls、tokens、duration |
| 资源导入监控 | ResourceIngestionEventDataSource | ResourceIngestionCollector | Counter、Histogram | parse / finalize / summarize / wait duration |
| Session 与异步任务监控 | SessionLifecycleDataSource、TaskStateDataSource、QueuePipelineStateDataSource | TaskTrackerCollector、QueueCollector | Gauge、Counter | task pending、queue backlog、session lifecycle count |
| 诊断与状态监控 | ObserverStateDataSource、QueuePipelineStateDataSource | ObserverHealthCollector、LockCollector、VikingDBCollector | Gauge | component health、lock active、collection health |
| 加密监控 | EncryptionEventDataSource、EncryptionProbeDataSource | EncryptionCollector、EncryptionProbeCollector | Counter、Histogram、Gauge | encrypt count、decrypt duration、root key readiness |
| 系统探针监控 | 各类 *ProbeDataSource | 各类 *ProbeCollector | Gauge、Health | service readiness、storage readiness、kms availability |
| 操作级 Telemetry 指标化 | telemetry adapter / bridge | TelemetryBridgeCollector | Counter、Histogram、Gauge | operation requests、vector scanned、memory extracted |
当前指标对象模型收敛为三类基础指标:Counter、Gauge 与 Histogram。Summary 不纳入当前范围,以避免在聚合语义、实现复杂度与使用收益之间引入不必要的失衡。
当前统一支持三种指标类型:
| 类型 | 用途 | 典型场景 |
|---|---|---|
| Counter | 单调递增计数 | 请求量、错误数、cache 命中数、任务完成数 |
| Gauge | 当前值 | 队列 backlog、运行中任务数、锁持有数、健康状态 |
| Histogram | 分布统计 | 请求耗时、embedding 耗时、VLM 调用耗时、资源处理耗时 |
当前不支持 Summary,原因如下:
标签策略的核心,不在于“尽可能表达更多业务信息”,而在于在可观测性价值与高基数风险之间取得稳定平衡。为此,标签设计必须遵守“低基数优先”原则,默认只允许有限、可枚举、可控的标签集合。
| 标签 | 说明 | 默认策略 |
|---|---|---|
operation | 操作名,如 search.find、resources.add_resource | 默认启用 |
status | ok / error | 默认启用 |
queue | Embedding / Semantic / 其他队列名 | 默认启用 |
level | cache level,如 L0 / L1 / L2 | 默认启用 |
context_type | retrieval context,如 memory / resource | 默认启用 |
component | queue / models / lock / retrieval / vikingdb | 默认启用 |
task_type | 如 session_commit | 默认启用 |
account_id | 租户 ID | 条件启用 |
provider | 模型供应商,如 openai / volcengine | 条件启用 |
model_name | 模型名 | 谨慎启用,需归一化后有限枚举 |
account_id 仅用于:
运行时保护规则如下:
account_id 运行时解析与回退语义在当前实现中,account_id 已不再只是“是否在指标定义里声明一个标签”的静态设计问题,而是一个运行时解析过程。其落地语义如下:
支持集(support set)比 allowlist 更窄
account_id;account_id,不在支持集中的指标也不得携带该标签;最终标签值不是简单的“真实租户 ID 或空”
__unknown__:当前请求/事件无法解析出可信租户;__overflow__:已启用活跃租户数上限保护,当前租户超出上限,统一回落到溢出桶。解析路径按“显式值 > 请求上下文 > 所有权信息”收敛
account_id;__unknown__,而不是直接省略标签。运行时策略作为独立配置对象
enabledmetric_allowlistmax_active_accounts文档层面应明确:account_id 是“有损但可控”的可观测维度
/metrics 中。user_idsession_idresource_urierror_messagequery命名规范的目标,是让同一类指标在不同 collector、不同链路中保持一致的表达方式,减少命名漂移与语义歧义。为此,指标命名统一采用 openviking_<domain>_<metric>_<unit> 模板,并对 Counter / Histogram / Health 指标施加额外约束。
指标命名统一采用:
openviking_<domain>_<metric>_<unit>
示例:
openviking_retrieval_requests_totalopenviking_queue_pendingopenviking_task_runningopenviking_operation_duration_seconds设计要求:
_total 结尾;_seconds;0/1 数值。/metrics 的指标以下指标保留在 /api/v1/stats 或 telemetry JSON 中更合适:
| 数据项 | 原因 |
|---|---|
| memory category 分布 | 需要扫描或查询聚合,更接近业务分析 |
| hotness / staleness 分布 | 不适合高频抓取 |
| 单 session extraction 明细 | 高基数、偏诊断 |
| 原始错误文本 | 高基数且可能包含敏感信息 |
| 任意 resource / URI 级统计 | 高基数 |
所有耗时类 Histogram 统一使用秒级桶,便于跨模块比较:
| 桶值(秒) | 适用场景 |
|---|---|
0.005 / 0.01 / 0.025 | 极短本地操作 |
0.05 / 0.1 / 0.25 | retrieval / cache / 轻量 API |
0.5 / 1.0 / 2.5 | embedding / 中等资源操作 |
5.0 / 10.0 / 30.0 | VLM / wait 模式 / 重处理任务 |
如果某条链路需要独立桶配置,应通过指标定义层配置,而不是在业务埋点中写死。
对于 operation telemetry,只抽取“结束时就已具备、并且不会导致高基数”的字段。结合 docs/zh/guides/05-observability.md、docs/zh/guides/07-operation-telemetry.md 与 openviking/telemetry/operation.py 的当前能力。
| telemetry 字段 | 映射指标 |
|---|---|
summary.operation + summary.status | openviking_operation_requests_total |
summary.duration_ms | openviking_operation_duration_seconds |
summary.tokens.total | 不直接落单独指标;通过对 openviking_operation_tokens_total 的原子分量在查询层聚合得到 |
summary.tokens.llm.input | openviking_operation_tokens_total{token_type="llm_input"} |
summary.tokens.llm.output | openviking_operation_tokens_total{token_type="llm_output"} |
summary.tokens.embedding.total | openviking_operation_tokens_total{token_type="embedding"} |
summary.vector.searches | openviking_vector_searches_total |
summary.vector.scored | openviking_vector_scored_total |
summary.vector.passed | openviking_vector_passed_total |
summary.vector.returned | openviking_vector_returned_total |
summary.vector.scanned | openviking_vector_scanned_total |
| `summary.semantic_nodes.{total | done |
summary.memory.extracted | openviking_memory_extracted_total |
当前代码中,只有进入 ACCOUNT_DIMENSION_SUPPORTED_METRICS 支持集的指标族允许注入 account_id。支持集如下:
| 指标域 | 当前支持 account_id 的指标 |
|---|---|
| HTTP | openviking_http_requests_total、openviking_http_request_duration_seconds、openviking_http_inflight_requests |
| Retrieval | openviking_retrieval_requests_total、openviking_retrieval_results_total、openviking_retrieval_zero_result_total、openviking_retrieval_latency_seconds、openviking_retrieval_rerank_used_total、openviking_retrieval_rerank_fallback_total |
| Resource | openviking_resource_stage_total、openviking_resource_stage_duration_seconds、openviking_resource_wait_duration_seconds |
| Session | openviking_session_lifecycle_total、openviking_session_contexts_used_total、openviking_session_archive_total |
| Operation Telemetry | openviking_operation_requests_total、openviking_operation_duration_seconds、openviking_operation_tokens_total |
| VLM | openviking_vlm_calls_total、openviking_vlm_call_duration_seconds、openviking_vlm_tokens_input_total、openviking_vlm_tokens_output_total、openviking_vlm_tokens_total |
| Embedding | openviking_embedding_requests_total、openviking_embedding_latency_seconds、openviking_embedding_errors_total |
除上述指标外,其他指标族即使在配置上开启了 account_id,当前实现也不会为其注入租户标签。
Openviking 指标体系采用如下兼容性策略:
/metrics 路由地址保持不变;server.observability.metrics.enabled 配置保持不变;server.observability.metrics.exporters.* 作为 exporter 扩展点:默认 Prometheus exporter 可继续工作,OTLP exporter 可按需启用;/api/v1/observer/* 与 /api/v1/stats/* 行为保持不变。| 风险 | 描述 | 缓解策略 |
|---|---|---|
| 指标高基数 | 租户、模型、错误码维度无限增长 | 严格标签白名单 + 上限保护 |
| 抓取放大成本 | StateCollector / ProbeCollector 在 /metrics 时做重查询 | 限制为轻量瞬时状态源与轻量 probe,不扫描业务大表 |
| 迁移期间重复计数 | 老 PrometheusObserver 与新 registry 并存 | 引入过渡期开关,避免双写 |
| 并发锁争用 | 高频写入与高频抓取互相影响 | registry 采用细粒度结构与最小持锁时间 |
| 指标语义漂移 | 同一指标在不同 collector 中含义不一致 | 统一命名文档与 contract test |
| Probe 副作用 | 外部依赖 probe 超时或失败拖慢 /metrics | 为 probe 设置超时、隔离失败并限制 probe 数量 |
| Adapter 语义漂移 | telemetry bridge 或 domain stats bridge 与原始事件语义不一致 | 明确 bridge 边界并增加 contract test |
| 双路径重复上报 | 一级事件链路与 bridge 链路同时写入同类指标 | 以指标所有权清单约束单一写入责任 |