docs: rewrite README and AGENTS.md etc..
This commit is contained in:
1 parent
cc64df3e70
commit
a9bf849697
22 files changed
+514
-665
No files matched your search
+23
-139
@@ -1,162 +1,53 @@
|
||||
# Backend 存储
|
||||
|
||||
实现位于 `MoShiro.Backend.Storage.Sqlite`,负责实现 `Backend.Application` 中定义的端口接口,**只做读写,不含业务逻辑**。降采样和分析由 Application 调用 Analysis 完成。
|
||||
实现位于 `MoShiro.Backend.Storage.Sqlite`,负责实现 `Backend.Application` 中定义的端口接口,只做读写,不含业务逻辑。降采样和分析由 Application 调用 Analysis 完成。
|
||||
|
||||
## 1. 目录布局
|
||||
|
||||
```text
|
||||
data/
|
||||
├─ moshiro.db # SQLite:配置、身份、原始块索引、指标、测试结果
|
||||
├─ backups/ # Backend 更新前的数据库备份
|
||||
└─ raw/
|
||||
└─ 2026-09-27/ # 按 UTC 日期分目录,保留期满整目录删除
|
||||
├─ agt_01J…/ # 每条线路或每个 Agent 一个目录
|
||||
├─ 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。
|
||||
- 段文件:把收到的 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`,由一个后台循环执行;读操作使用独立的只读连接池。
|
||||
- 单写入者:所有写操作经过一个有界 `Channel`,由一个后台循环执行;读操作使用独立的只读连接池。
|
||||
- 时间统一存储为 Unix 毫秒 `INTEGER`(UTC);表结构版本记录在 `PRAGMA user_version`,迁移脚本为内置 SQL。
|
||||
|
||||
## 3. 表结构
|
||||
|
||||
`agents`、`join_tokens` 以及公钥和状态版本字段,同时作为 Fleet 的 `IFleetStore` 存储(由 `MoShiro.Backend.Storage.Sqlite` 实现),和 MoShiro 的其他数据放在同一个 `moshiro.db` 中,由同一个单写入者写入。CI 签名的发布清单存在 `settings` 表里(`release.<组件>.manifest`、`release.<组件>.signature`),面板只原样保存和转发。
|
||||
完整定义以 `Schema.cs` 为准(当前版本 16,迁移为按版本号追加的内置 SQL)。`agents`、`join_tokens` 及公钥、状态版本字段同时作为 Fleet 的 `IFleetStore` 实现,与其他数据放在同一个 `moshiro.db`,由同一个单写入者写入。CI 签名的发布清单存在 `settings` 表中(`release.<组件>.manifest`、`release.<组件>.signature`),面板原样保存和转发。
|
||||
|
||||
```sql
|
||||
-- 身份与注册 -------------------------------------------------------------
|
||||
CREATE TABLE agents (
|
||||
id TEXT PRIMARY KEY, -- agt_<ULID>
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL, -- pending | active | disabled
|
||||
pubkey BLOB NOT NULL, -- ECDSA P-256 身份公钥
|
||||
tls_spki BLOB NOT NULL, -- 面向 Probe 的证书指纹(由 tls_pubkey 算出)
|
||||
tls_pubkey BLOB, -- 面向 Probe 的证书公钥,Agent 在 hello 里上报;Probe 用它固定证书、派生敲门密钥
|
||||
public_host TEXT NOT NULL, -- 自动识别(Agent 上报的公网地址),手填后标记 public_host_manual,不再被覆盖
|
||||
probe_tcp_port INTEGER NOT NULL, probe_udp_ports TEXT NOT NULL,
|
||||
labels TEXT NOT NULL DEFAULT '[]',
|
||||
public_visible INTEGER NOT NULL DEFAULT 1, -- 是否在 public API 中展示
|
||||
kinds TEXT, agent_version TEXT, local_limits TEXT, -- 来自 fleet.hello
|
||||
task_version INTEGER NOT NULL DEFAULT 0, applied_task_version INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL, last_seen_at INTEGER, row_version INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
| 类别 | 表 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 身份与注册 | `agents`、`probes`、`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:<probe_id>[:ipv6]` 或 `target:<task_id>`)、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` | 节点事件;每个节点每分钟一行的主机资源汇总 |
|
||||
|
||||
CREATE TABLE join_tokens (
|
||||
id TEXT PRIMARY KEY, token_sha256 BLOB NOT NULL UNIQUE, note TEXT,
|
||||
labels TEXT NOT NULL DEFAULT '[]', auto_approve INTEGER NOT NULL,
|
||||
max_uses INTEGER NOT NULL, uses INTEGER NOT NULL DEFAULT 0,
|
||||
expires_at INTEGER NOT NULL, revoked_at INTEGER, created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE host_samples ( -- 主机资源占用,每个 Agent 每分钟一行(schema v3)
|
||||
agent_id TEXT NOT NULL, at INTEGER NOT NULL,
|
||||
cpu REAL, mem_used INTEGER, mem_total INTEGER, disk_used INTEGER, disk_total INTEGER,
|
||||
net_rx_bps INTEGER, net_tx_bps INTEGER, uptime INTEGER,
|
||||
PRIMARY KEY (agent_id, at)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE probes ( -- 每个 Probe 身份一行(吊销只设 revoked_at)
|
||||
id TEXT PRIMARY KEY, name TEXT NOT NULL, pubkey BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL, revoked_at INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE,
|
||||
password_hash BLOB NOT NULL, password_salt BLOB NOT NULL, iterations INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE sessions (
|
||||
id_sha256 BLOB PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
csrf_token BLOB NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE api_tokens (
|
||||
id TEXT PRIMARY KEY, name TEXT NOT NULL, scope TEXT NOT NULL, -- read | admin
|
||||
token_sha256 BLOB NOT NULL UNIQUE, created_at INTEGER NOT NULL, last_used_at INTEGER, revoked_at INTEGER
|
||||
);
|
||||
CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL); -- 如 site.public = true
|
||||
|
||||
-- 任务 ------------------------------------------------------------------
|
||||
CREATE TABLE tasks (
|
||||
id TEXT PRIMARY KEY, -- tsk_<ULID>
|
||||
scope TEXT NOT NULL, -- line | target
|
||||
agent_id TEXT REFERENCES agents(id) ON DELETE CASCADE, -- 为 NULL 时,是适用于所有 Agent 的 line 模板
|
||||
base_task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE, -- 针对某个 Agent 覆盖模板时,指向模板
|
||||
kind TEXT NOT NULL,
|
||||
target TEXT, -- 仅 target 任务
|
||||
params TEXT NOT NULL DEFAULT '{}',
|
||||
schedule TEXT NOT NULL, -- continuous | every:<分钟>
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
updated_at INTEGER NOT NULL, row_version INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE tests ( -- 按需测试(once)
|
||||
id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, plan TEXT NOT NULL, params TEXT NOT NULL,
|
||||
trigger TEXT NOT NULL, -- api | schedule:<task_id>
|
||||
status TEXT NOT NULL, -- queued | running | succeeded | failed | aborted
|
||||
error_code TEXT, requested_at INTEGER NOT NULL, started_at INTEGER, finished_at INTEGER
|
||||
);
|
||||
CREATE INDEX ix_tests_agent ON tests (agent_id, requested_at);
|
||||
|
||||
-- 原始数据索引 ------------------------------------------------------------
|
||||
CREATE TABLE raw_segments (
|
||||
id INTEGER PRIMARY KEY, day TEXT NOT NULL, path TEXT NOT NULL UNIQUE, bytes INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE raw_chunks (
|
||||
chunk_id TEXT PRIMARY KEY, segment_id INTEGER NOT NULL, offset INTEGER NOT NULL, length INTEGER NOT NULL,
|
||||
agent_id TEXT NOT NULL, series TEXT NOT NULL, -- line | target:<task_id>
|
||||
kind TEXT NOT NULL, task_id TEXT, test_id TEXT, phase INTEGER,
|
||||
start_ts INTEGER NOT NULL, duration_ms INTEGER NOT NULL, partial INTEGER NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
CREATE INDEX ix_raw_series ON raw_chunks (agent_id, series, start_ts);
|
||||
CREATE INDEX ix_raw_test ON raw_chunks (test_id) WHERE test_id IS NOT NULL;
|
||||
|
||||
-- 指标(通用存储:每个桶一行,内容为 指标id → 累加器状态 的映射)----------------
|
||||
-- 分层:metric_10s(48 小时)、metric_1m(30 天)、metric_5m(365 天)、metric_1h(永久)
|
||||
-- 另有 1s 精度的内存热层(最近 1 小时,不落盘),见 query.md 第 3 节
|
||||
CREATE TABLE metric_1m ( -- 60 s
|
||||
agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL,
|
||||
partial INTEGER NOT NULL,
|
||||
metrics BLOB NOT NULL, -- AccumulatorCodec 编码的 {metric_id: state}
|
||||
PRIMARY KEY (agent_id, series, window_start)
|
||||
) WITHOUT ROWID;
|
||||
-- metric_10s 结构相同(由流式路径直接写入,不经过降采样)
|
||||
-- metric_5m / metric_1h 结构相同(window_start 换成 bucket_start,另加 windows 计数)
|
||||
|
||||
CREATE TABLE test_results (
|
||||
test_id TEXT PRIMARY KEY, metrics BLOB NOT NULL, series BLOB -- series 为逐秒或逐档的曲线
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE metric_versions (metric_id TEXT PRIMARY KEY, fingerprint TEXT NOT NULL,
|
||||
recompute_from INTEGER, recompute_cursor INTEGER);
|
||||
|
||||
CREATE TABLE analysis_queue (
|
||||
agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL,
|
||||
not_before INTEGER NOT NULL, reason TEXT NOT NULL, -- new | late_chunk | recompute
|
||||
PRIMARY KEY (agent_id, series, window_start)
|
||||
) WITHOUT ROWID;
|
||||
CREATE TABLE rollup_dirty (level INTEGER NOT NULL, agent_id TEXT NOT NULL, series TEXT NOT NULL,
|
||||
bucket_start INTEGER NOT NULL, PRIMARY KEY (level, agent_id, series, bucket_start)) WITHOUT ROWID;
|
||||
|
||||
-- 事件 ------------------------------------------------------------------
|
||||
CREATE TABLE events (id INTEGER PRIMARY KEY, agent_id TEXT, kind TEXT NOT NULL,
|
||||
at INTEGER NOT NULL, detail TEXT);
|
||||
CREATE INDEX ix_events_time ON events (at);
|
||||
```
|
||||
|
||||
**要点:新增 Kind 或指标都不需要改表结构。** 指标是 `metric_*.metrics` 里的一个键;Kind 只是 `raw_chunks.kind` 的一个取值。
|
||||
新增 Kind 或指标不需要改表结构:指标是 `metric_*.metrics` 中的一个键,Kind 只是 `raw_chunks.kind` 的一个取值。1 秒精度的热层在内存中,不落盘,见 [query.md](query.md) 第 3 节。
|
||||
|
||||
## 4. 保留
|
||||
|
||||
| 数据 | 默认保留 |
|
||||
| --- | --- |
|
||||
| 持续任务的原始段文件 | **30 天** |
|
||||
| 持续任务的原始段文件 | 30 天 |
|
||||
| 按需测试的原始段文件 | 90 天 |
|
||||
| 热层(1s,内存) | 最近 1 小时 |
|
||||
| `metric_10s` | 48 小时 |
|
||||
@@ -165,9 +56,9 @@ CREATE INDEX ix_events_time ON events (at);
|
||||
| `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.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 中的 `Downsampler` 负责:读取下一级的累加器状态,用 `AccumulatorCodec.Merge` 合并后写回。所有保留期都可以配置。
|
||||
降采样由 Application 的 `AnalysisScheduler` 驱动:读取下一级的累加器状态,合并后写回上一层,待处理的桶记录在 `rollup_dirty`。
|
||||
|
||||
## 5. 容量估算
|
||||
|
||||
@@ -175,19 +66,12 @@ CREATE INDEX ix_events_time ON events (at);
|
||||
|
||||
| 数据 | 估算 |
|
||||
| --- | --- |
|
||||
| 原始数据 | 每条线路约 7 MB/天(udp_echo)+ 14 MB/天(tcp_echo 及内核采样)≈ 21 MB/天;15 条线路约 0.3 GB/天,30 天约 **9.5 GB** |
|
||||
| 原始数据 | 每条线路约 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 块,单写入者绰绰有余。M5 阶段会用模拟数据验证这些估算。
|
||||
写入量约每秒 3 块,单写入者可以承载。以上为估算值。
|
||||
|
||||
## 6. 查询
|
||||
|
||||
时间段、自动步长、数据源选择、现场合并和降级规则见 [query.md](query.md)。
|
||||
|
||||
## 7. 与实现的差异(以 `Schema.cs` 为准)
|
||||
|
||||
- `agents`:`name` 不再 `UNIQUE`,改为“已建立(非 pending)节点之间唯一”的部分唯一索引,pending 节点可以重名;新增 `task_hash`(有效任务集的哈希,用来判断版本是否要递增)、`rejected`(节点上报的拒绝原因)、`meta`(注册元数据)。
|
||||
- `tests`:新增 `phase`、`phase_count`;`test_results` 的 `metrics` / `series` 保存 JSON。
|
||||
- `analysis_queue`:新增 `seq`,每次入队递增;完成时只删除序号没变的项,处理期间到达的迟到块不会丢更新。
|
||||
- `rollup_dirty`:先处理低层(5 分钟)再处理高层(1 小时)。
|
||||
Reference in new issue
Block a user