Files
planet/docs/collected-data-history-plan.md
2026-03-25 17:19:10 +08:00

9.4 KiB
Raw Blame History

采集数据历史快照化改造方案

背景

当前系统的 collected_data 更接近“当前结果表”:

  • 同一个 source + source_id 会被更新覆盖
  • 前端列表页默认读取这张表
  • collection_tasks 只记录任务执行状态,不直接承载数据版本语义

这套方式适合管理后台,但不利于后续做态势感知、时间回放、趋势分析和版本对比。
如果后面需要回答下面这类问题,当前模型会比较吃力:

  • 某条实体在过去 7 天如何变化
  • 某次采集相比上次新增了什么、删除了什么、值变了什么
  • 某个时刻地图上“当时的世界状态”是什么
  • 告警是在第几次采集后触发的

因此建议把采集数据改造成“历史快照 + 当前视图”模型。

目标

  1. 每次触发采集都保留一份独立快照,历史可追溯。
  2. 管理后台默认仍然只看“当前最新状态”,不增加使用复杂度。
  3. 后续支持:
    • 时间线回放
    • 两次采集差异对比
    • 趋势分析
    • 按快照回溯告警和地图状态
  4. 尽量兼容现有接口,降低改造成本。

结论

不建议继续用以下两种单一模式:

  • 直接覆盖旧数据 问题:没有历史,无法回溯。

  • 软删除旧数据再全量新增 问题:语义不清,历史和“当前无效”混在一起,后续统计复杂。

推荐方案:

  • 保留历史事实表
  • 维护当前视图
  • 每次采集对应一个明确的快照批次

推荐数据模型

方案概览

建议拆成三层:

  1. collection_tasks 继续作为采集任务表,表示“这次采集任务”。

  2. data_snapshots 新增快照表,表示“某个数据源在某次任务中产出的一个快照批次”。

  3. 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 对应“本次快照中消失”的实体

保留现有字段:

  • source
  • source_id
  • data_type
  • name
  • title
  • description
  • country
  • city
  • latitude
  • longitude
  • value
  • unit
  • metadata
  • collected_at
  • reference_date
  • is_valid

当前视图

建议新增一个只读视图:

current_collected_data

语义:

  • 对每个 source + source_id 只保留最新一条 is_current = truedeleted_at is null 的记录

这样:

  • 管理后台继续像现在一样查“当前数据”
  • 历史分析查 collected_data

写入策略

触发按钮语义

“触发”不再理解为“覆盖旧表”,而是:

  • 启动一次新的采集任务
  • 生成一个新的快照
  • 将本次结果写入历史事实表
  • 再更新当前视图标记

写入流程

  1. 创建 collection_tasks 记录,状态 running
  2. 创建 data_snapshots 记录,状态 running
  3. 采集器拉取原始数据并标准化
  4. 为每条记录生成 entity_key
    • 推荐:{source}:{source_id}
  5. 将本次记录批量写入 collected_data
  6. 与上一个快照做比对,计算:
    • 新增
    • 更新
    • 未变
    • 删除
  7. 更新本批记录的:
    • change_type
    • previous_record_id
    • is_current
  8. 将上一批同实体记录的 is_current 置为 false
  9. 将本次快照未出现但上一版存在的实体标记为 deleted
  10. 更新 data_snapshots.status = success
  11. 更新 collection_tasks.status = success

删除语义

这里不建议真的删记录。
建议采用“逻辑消失”模型:

  • 历史行永远保留
  • 如果某实体在新快照里消失:
    • 上一条历史记录补一条“删除状态记录”或标记 change_type = deleted
    • 同时该实体不再出现在当前视图

这样最适合态势感知。

API 改造建议

保持现有接口默认行为

现有接口:

  • GET /api/v1/collected
  • GET /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_id
  • source
  • status
  • date_from/date_to

4. 快照详情

GET /api/v1/snapshots/{id}

返回:

  • 快照基础信息
  • 统计摘要
  • 与上一版的 diff 摘要

5. 快照 diff

GET /api/v1/snapshots/{id}/diff?base_snapshot_id=122

返回:

  • created
  • updated
  • deleted
  • unchanged

前端改造建议

1. 数据列表页

默认仍看当前数据,不改用户使用习惯。

建议新增:

  • “视图模式”
    • 当前数据
    • 历史数据
  • “快照时间”筛选
  • “只看变化项”筛选

2. 数据详情页

详情页建议展示:

  • 当前记录基础信息
  • 元数据动态字段
  • 所属快照
  • 上一版本对比入口
  • 历史版本时间线

3. 数据源管理页

“触发”按钮文案建议改成更准确的:

  • 立即采集

并在详情里补:

  • 最近一次快照时间
  • 最近一次快照记录数
  • 最近一次变化数

迁移方案

Phase 1兼容式落地

目标:先保留当前页面可用。

改动:

  1. 新增 data_snapshots
  2. collected_data 增加:
    • snapshot_id
    • task_id
    • entity_key
    • is_current
    • previous_record_id
    • change_type
    • change_summary
    • deleted_at
  3. 现有数据全部补成一个“初始化快照”
  4. 现有 /collected 默认改查当前视图

优点:

  • 前端几乎无感
  • 风险最小

Phase 2启用差异计算

目标:采集后可知道本次改了什么。

改动:

  1. 写入时做新旧快照比对
  2. change_type
  3. 生成快照摘要

Phase 3前端态势感知能力

目标:支持历史回放和趋势分析。

改动:

  1. 快照时间线
  2. 版本 diff 页面
  3. 地图时间回放
  4. 告警和快照关联

唯一性与索引建议

建议保留的业务唯一性

在“同一个快照内部”,建议唯一:

  • (snapshot_id, source, source_id)

不要在整张历史表上强加:

  • (source, source_id) 唯一

因为历史表本来就应该允许同一实体跨快照存在多条版本。

建议索引

  • idx_collected_data_snapshot_id
  • idx_collected_data_source_source_id
  • idx_collected_data_entity_key
  • idx_collected_data_is_current
  • idx_collected_data_reference_date
  • idx_snapshots_source_completed_at

风险点

  1. 存储量会明显增加

    • 需要评估保留周期
    • 可以考虑冷热分层
  2. 写入复杂度上升

    • 需要批量 upsert / diff 逻辑
  3. 当前接口语义会从“表”变成“视图”

    • 文档必须同步
  4. 某些采集器缺稳定 source_id

    • 需要补齐实体稳定键策略

对当前项目的具体建议

结合当前代码,推荐这样落地:

短期

  1. 先设计并落表:
    • data_snapshots
    • collected_data 新字段
  2. 采集完成后每次新增快照
  3. /api/v1/collected 默认查 is_current = true

中期

  1. BaseCollector._save_data() 中改成:
    • 生成快照
    • 批量写历史
    • 标记当前
  2. CollectionTask.id 关联到 snapshot.task_id

长期

  1. 地图接口支持按 snapshot_id 查询
  2. 仪表盘支持“最近一次快照变化量”
  3. 告警支持绑定到快照版本

最终建议

最终建议采用:

  • 历史事实表:保存每次采集结果
  • 当前视图:服务管理后台默认查询
  • 快照表:承载版本批次和 diff 语义

这样既能保留历史,又不会把当前页面全部推翻重做,是最适合后续做态势感知的一条路径。