9.4 KiB
采集数据历史快照化改造方案
背景
当前系统的 collected_data 更接近“当前结果表”:
- 同一个
source + source_id会被更新覆盖 - 前端列表页默认读取这张表
collection_tasks只记录任务执行状态,不直接承载数据版本语义
这套方式适合管理后台,但不利于后续做态势感知、时间回放、趋势分析和版本对比。
如果后面需要回答下面这类问题,当前模型会比较吃力:
- 某条实体在过去 7 天如何变化
- 某次采集相比上次新增了什么、删除了什么、值变了什么
- 某个时刻地图上“当时的世界状态”是什么
- 告警是在第几次采集后触发的
因此建议把采集数据改造成“历史快照 + 当前视图”模型。
目标
- 每次触发采集都保留一份独立快照,历史可追溯。
- 管理后台默认仍然只看“当前最新状态”,不增加使用复杂度。
- 后续支持:
- 时间线回放
- 两次采集差异对比
- 趋势分析
- 按快照回溯告警和地图状态
- 尽量兼容现有接口,降低改造成本。
结论
不建议继续用以下两种单一模式:
-
直接覆盖旧数据 问题:没有历史,无法回溯。
-
软删除旧数据再全量新增 问题:语义不清,历史和“当前无效”混在一起,后续统计复杂。
推荐方案:
- 保留历史事实表
- 维护当前视图
- 每次采集对应一个明确的快照批次
推荐数据模型
方案概览
建议拆成三层:
-
collection_tasks继续作为采集任务表,表示“这次采集任务”。 -
data_snapshots新增快照表,表示“某个数据源在某次任务中产出的一个快照批次”。 -
collected_data从“当前结果表”升级为“历史事实表”,每一行归属于一个快照。
同时再提供一个“当前视图”:
- SQL View / 物化视图 / API 查询层封装均可
- 语义是“每个
source + source_id的最新有效记录”
新增表:data_snapshots
建议字段:
| 字段 | 类型 | 含义 |
|---|---|---|
id |
bigint PK | 快照主键 |
datasource_id |
int | 对应数据源 |
task_id |
int | 对应采集任务 |
source |
varchar(100) | 数据源名,如 top500 |
snapshot_key |
varchar(100) | 可选,业务快照标识 |
reference_date |
timestamptz nullable | 这批数据的参考时间 |
started_at |
timestamptz | 快照开始时间 |
completed_at |
timestamptz | 快照完成时间 |
record_count |
int | 快照总记录数 |
status |
varchar(20) | running/success/failed/partial |
is_current |
bool | 当前是否是该数据源最新快照 |
parent_snapshot_id |
bigint nullable | 上一版快照,可用于 diff |
summary |
jsonb | 本次快照统计摘要 |
说明:
collection_tasks偏“执行过程”data_snapshots偏“数据版本”- 一个任务通常对应一个快照,但保留分层更清晰
升级表:collected_data
建议新增字段:
| 字段 | 类型 | 含义 |
|---|---|---|
snapshot_id |
bigint not null | 归属快照 |
task_id |
int nullable | 归属任务,便于追查 |
entity_key |
varchar(255) | 实体稳定键,通常可由 source + source_id 派生 |
is_current |
bool | 当前是否为该实体最新记录 |
previous_record_id |
bigint nullable | 上一个版本的记录 |
change_type |
varchar(20) | created/updated/unchanged/deleted |
change_summary |
jsonb | 字段变化摘要 |
deleted_at |
timestamptz nullable | 对应“本次快照中消失”的实体 |
保留现有字段:
sourcesource_iddata_typenametitledescriptioncountrycitylatitudelongitudevalueunitmetadatacollected_atreference_dateis_valid
当前视图
建议新增一个只读视图:
current_collected_data
语义:
- 对每个
source + source_id只保留最新一条is_current = true且deleted_at is null的记录
这样:
- 管理后台继续像现在一样查“当前数据”
- 历史分析查
collected_data
写入策略
触发按钮语义
“触发”不再理解为“覆盖旧表”,而是:
- 启动一次新的采集任务
- 生成一个新的快照
- 将本次结果写入历史事实表
- 再更新当前视图标记
写入流程
- 创建
collection_tasks记录,状态running - 创建
data_snapshots记录,状态running - 采集器拉取原始数据并标准化
- 为每条记录生成
entity_key- 推荐:
{source}:{source_id}
- 推荐:
- 将本次记录批量写入
collected_data - 与上一个快照做比对,计算:
- 新增
- 更新
- 未变
- 删除
- 更新本批记录的:
change_typeprevious_record_idis_current
- 将上一批同实体记录的
is_current置为false - 将本次快照未出现但上一版存在的实体标记为
deleted - 更新
data_snapshots.status = success - 更新
collection_tasks.status = success
删除语义
这里不建议真的删记录。
建议采用“逻辑消失”模型:
- 历史行永远保留
- 如果某实体在新快照里消失:
- 上一条历史记录补一条“删除状态记录”或标记
change_type = deleted - 同时该实体不再出现在当前视图
- 上一条历史记录补一条“删除状态记录”或标记
这样最适合态势感知。
API 改造建议
保持现有接口默认行为
现有接口:
GET /api/v1/collectedGET /api/v1/collected/{id}GET /api/v1/collected/summary
建议默认仍返回“当前视图”,避免前端全面重写。
新增历史查询能力
建议新增参数或新接口:
1. 当前/历史切换
GET /api/v1/collected?mode=current|history
current:默认,查当前视图history:查历史事实表
2. 按快照查询
GET /api/v1/collected?snapshot_id=123
3. 快照列表
GET /api/v1/snapshots
支持筛选:
datasource_idsourcestatusdate_from/date_to
4. 快照详情
GET /api/v1/snapshots/{id}
返回:
- 快照基础信息
- 统计摘要
- 与上一版的 diff 摘要
5. 快照 diff
GET /api/v1/snapshots/{id}/diff?base_snapshot_id=122
返回:
createdupdateddeletedunchanged
前端改造建议
1. 数据列表页
默认仍看当前数据,不改用户使用习惯。
建议新增:
- “视图模式”
- 当前数据
- 历史数据
- “快照时间”筛选
- “只看变化项”筛选
2. 数据详情页
详情页建议展示:
- 当前记录基础信息
- 元数据动态字段
- 所属快照
- 上一版本对比入口
- 历史版本时间线
3. 数据源管理页
“触发”按钮文案建议改成更准确的:
立即采集
并在详情里补:
- 最近一次快照时间
- 最近一次快照记录数
- 最近一次变化数
迁移方案
Phase 1:兼容式落地
目标:先保留当前页面可用。
改动:
- 新增
data_snapshots - 给
collected_data增加:snapshot_idtask_identity_keyis_currentprevious_record_idchange_typechange_summarydeleted_at
- 现有数据全部补成一个“初始化快照”
- 现有
/collected默认改查当前视图
优点:
- 前端几乎无感
- 风险最小
Phase 2:启用差异计算
目标:采集后可知道本次改了什么。
改动:
- 写入时做新旧快照比对
- 写
change_type - 生成快照摘要
Phase 3:前端态势感知能力
目标:支持历史回放和趋势分析。
改动:
- 快照时间线
- 版本 diff 页面
- 地图时间回放
- 告警和快照关联
唯一性与索引建议
建议保留的业务唯一性
在“同一个快照内部”,建议唯一:
(snapshot_id, source, source_id)
不要在整张历史表上强加:
(source, source_id)唯一
因为历史表本来就应该允许同一实体跨快照存在多条版本。
建议索引
idx_collected_data_snapshot_ididx_collected_data_source_source_ididx_collected_data_entity_keyidx_collected_data_is_currentidx_collected_data_reference_dateidx_snapshots_source_completed_at
风险点
-
存储量会明显增加
- 需要评估保留周期
- 可以考虑冷热分层
-
写入复杂度上升
- 需要批量 upsert / diff 逻辑
-
当前接口语义会从“表”变成“视图”
- 文档必须同步
-
某些采集器缺稳定
source_id- 需要补齐实体稳定键策略
对当前项目的具体建议
结合当前代码,推荐这样落地:
短期
- 先设计并落表:
data_snapshotscollected_data新字段
- 采集完成后每次新增快照
/api/v1/collected默认查is_current = true
中期
- 在
BaseCollector._save_data()中改成:- 生成快照
- 批量写历史
- 标记当前
- 将
CollectionTask.id关联到snapshot.task_id
长期
- 地图接口支持按
snapshot_id查询 - 仪表盘支持“最近一次快照变化量”
- 告警支持绑定到快照版本
最终建议
最终建议采用:
- 历史事实表:保存每次采集结果
- 当前视图:服务管理后台默认查询
- 快照表:承载版本批次和 diff 语义
这样既能保留历史,又不会把当前页面全部推翻重做,是最适合后续做态势感知的一条路径。