# Backend 存储 实现位于 `MoShiro.Backend.Storage.Sqlite`,负责实现 `Backend.Application` 中定义的端口接口,只做读写,不含业务逻辑。降采样和分析由 Application 调用 Analysis 完成。 ## 1. 目录布局 ```text data/ ├─ moshiro.db # SQLite:配置、身份、原始块索引、指标、测试结果 ├─ backups/ # Backend 更新前的数据库备份 └─ raw/ └─ 2026-09-27/ # 按 UTC 日期分目录,保留期满整目录删除 ├─ agt_01J…/ # 每个 Agent 一个目录 │ ├─ udp_echo.seg # 段文件:同一天、同一 Kind 的块依次追加 │ ├─ tcp_echo.seg │ └─ tsk_…_icmp_echo.seg # 目标任务 └─ tests/tst_01J…/phase0.seg … ``` - 段文件:把收到的 RawChunk 原样追加写入(块本身已经带 crc,并且自描述),SQLite 只记录 `(segment, offset, length)`。每 10 秒一块也不会产生海量小文件。 - 写入顺序:追加写入 → `fsync`(每批合并执行一次)→ 插入索引 → 由收件箱确认(Fleet 回复 `fleet.outbox.ack`)。启动时截断没有索引记录的段文件尾部。 - 去重:`chunk_id` 已存在时,直接回复 ACK。 ## 2. SQLite 配置 - `journal_mode=WAL`、`synchronous=NORMAL`、`foreign_keys=ON`、`busy_timeout=5000`。 - 单写入者:所有写操作经过一个有界 `Channel`,由一个后台循环执行;读操作使用独立的只读连接池。 - 时间统一存储为 Unix 毫秒 `INTEGER`(UTC);表结构版本记录在 `PRAGMA user_version`,迁移脚本为内置 SQL。 ## 3. 表结构 完整定义以 `Schema.cs` 为准(当前版本 16,迁移为按版本号追加的内置 SQL)。`agents`、`join_tokens` 及公钥、状态版本字段同时作为 Fleet 的 `IFleetStore` 实现,与其他数据放在同一个 `moshiro.db`,由同一个单写入者写入。CI 签名的发布清单存在 `settings` 表中(`release.<组件>.manifest`、`release.<组件>.signature`),面板原样保存和转发。 | 类别 | 表 | 说明 | | --- | --- | --- | | 身份与注册 | `agents`、`join_tokens`、`node_meta` | 节点公钥、状态、地址(含手填标记)、IPv6 诊断、`task_version` / `applied_task_version`、`row_version`(ETag);`node_meta` 保存系统、构建信息与国家 / 运营商;`agents.name` 仅在非 pending 节点间唯一 | | 账户与信任 | `users`、`sessions`、`api_tokens`、`passkeys`、`operator_sets`、`audit_log`、`settings` | 会话与 CSRF、能力化 API 令牌、passkey、操作员集合链、审计日志、键值设置 | | 任务 | `tasks`、`tests`、`test_results` | `tasks.scope` 为 `line` 或 `target`,`agent_id` 为空表示全局模板,`base_task_id` 指向被覆盖的模板;`tests` 记录按需测试及其阶段、地址族、线路;`test_results` 保存指标与曲线 | | 原始数据索引 | `raw_segments`、`raw_chunks` | `raw_chunks` 记录 `(segment, offset, length)`、`series`(`line:[:ipv6]` 或 `target:`)、Kind、任务、测试、起止时间 | | 指标 | `metric_10s`、`metric_1m`、`metric_5m`、`metric_1h`、`metric_versions` | 每个桶一行,`metrics` 为 `AccumulatorCodec` 编码的 `{metric_id: 累加器状态}`;主键 `(agent_id, series, window_start)`;`metric_versions` 保存指标指纹与重算进度 | | 分析调度 | `analysis_queue`、`rollup_dirty` | 待计算窗口(`seq` 递增,完成时只删除序号未变的项,处理期间到达的迟到块不丢更新);待降采样的桶(先低层 5 分钟,再高层 1 小时) | | 观测 | `events`、`host_samples` | 节点事件;每个节点每分钟一行的主机资源汇总 | 新增 Kind 或指标不需要改表结构:指标是 `metric_*.metrics` 中的一个键,Kind 只是 `raw_chunks.kind` 的一个取值。1 秒精度的热层在内存中,不落盘,见 [query.md](query.md) 第 3 节。 ## 4. 保留 | 数据 | 默认保留 | | --- | --- | | 持续任务的原始段文件 | 30 天 | | 按需测试的原始段文件 | 90 天 | | 热层(1s,内存) | 最近 1 小时 | | `metric_10s` | 48 小时 | | `metric_1m` | 30 天 | | `metric_5m` | 365 天 | | `metric_1h`、`tests`、`test_results` | 永久 | | `events` | 365 天 | 保留期在管理端设置中修改,键为 `retention.raw_days`、`retention.test_raw_days`、`retention.metric_10s_hours`、`retention.metric_1m_days`、`retention.metric_5m_days`、`retention.events_days`、`retention.host_days`(主机样本,默认 7 天)。 降采样由 Application 的 `AnalysisScheduler` 驱动:读取下一级的累加器状态,合并后写回上一层,待处理的桶记录在 `rollup_dirty`。 ## 5. 容量估算 以 15 个 Agent、默认模板(`udp_echo` 10 Hz + `tcp_echo` 20 Hz)为例: | 数据 | 估算 | | --- | --- | | 原始数据 | 每条线路约 7 MB/天(udp_echo)+ 14 MB/天(tcp_echo 及内核采样)≈ 21 MB/天;15 条线路约 0.3 GB/天,30 天约 9.5 GB | | 按需测试 | 一次 `hy2_ladder` 约 1.5 MB;每条线路每天 4 次,90 天约 8 GB | | SQLite | 指标窗口和降采样约 1~2 GB/年 | 写入量约每秒 3 块,单写入者可以承载。以上为估算值。 ## 6. 查询 时间段、自动步长、数据源选择、现场合并和降级规则见 [query.md](query.md)。