# 车辆数据最小落库合约 ## 目标 车辆接入系统只持久化能回答业务问题、能回放纠错、能支撑高频查询的数据。协议细节默认保存在 raw,不向上层业务表扩散。 ## 第一性原则 1. raw 是证据层,必须能证明收到过什么、解析成什么。 2. Kafka 是回放层,消费者失败后优先靠 Kafka lag 追平。 3. Redis 是当前态缓存,不是历史库。 4. TDengine 只承担高写入时间序列:raw、位置历史。 5. MySQL 只承担低基数业务状态:身份映射、实时快照、日指标。 6. 同一个事实只落一个主表,其他表只存查询必要的投影。 7. 新字段先进入 `parsed_json`,只有稳定查询需求出现后才提升为列。 ## 目标表边界 | 存储 | 表/Key | 保留内容 | 不保留内容 | | --- | --- | --- | --- | | TDengine | `raw_frames` | raw hex/text、完整 `parsed_json`、解析状态、协议标签、车辆标识标签 | `fields_json` 这类可从 `parsed_json`/业务表再得到的重复字段 | | TDengine | `raw_frame_payload_chunks` | 超长 raw/parsed payload 分片 | 业务查询字段 | | TDengine | `vehicle_locations` | 时间、VIN/协议标签、经纬度、速度、方向、SOC、总里程等位置核心字段 | 完整协议 JSON、注册鉴权信息、phone/device_id/vehicle_key 兜底标识 | | MySQL | `vehicle_realtime_snapshot` | 每个协议+VIN 的最新事件时间、接收时间、车牌、事件 ID | phone、device、source endpoint、完整 JSON | | MySQL | `vehicle_realtime_location` | 每个协议+VIN 的最新位置核心字段 | raw、parsed JSON、消息头内部字段 | | MySQL | `vehicle_daily_mileage` | 日期、VIN、协议、日里程、首末总里程、样本数 | 临时 vehicle key、泛化 metric key/value、每帧细节、位置点列表 | | MySQL | `vehicle_identity_binding` | 人工维护或导入的 VIN、车牌、phone、device_id 映射 | 注册历史、协议状态 | | MySQL | `jt808_registration` | JT808 phone 主键下的注册、鉴权、VIN 匹配状态、来源端点 | GB32960/MQTT 注册信息、位置历史 | | Redis | `vehicle:realtime-raw:{protocol}:{vin}` | 每协议最新完整 parsed 状态 | 历史数据、统计结果 | ## 当前应收敛的重复点 1. `vehicle_mileage_points` 与 `vehicle_locations.total_mileage_km` 重复。 - 状态:Go 写入链路已停止创建和写入 `vehicle_mileage_points`。 - 兼容:`/api/history/mileage-points` 已改为从 `vehicle_locations` 读取 `total_mileage_km IS NOT NULL`。 - 生产:ECS TDengine 历史库已在上线前重建,`vehicle_mileage_points` 不再存在。 2. `raw_frames.fields_json` 与 `raw_frames.parsed_json`、`vehicle_locations` 重复。 - 状态:新建 raw schema 和 raw 写入已停止使用 `fields_json`。 - 兼容:raw 查询只选择公共列,不依赖 `fields_json`;旧表保留该列也不影响查询。 - 生产:ECS TDengine 历史库已在上线前重建,`raw_frames` 不再包含 `fields_json`。 3. `vehicle_daily_mileage` 不再接收临时 `vehicle_key`。 - 原因:日统计是正式业务指标,应只统计已经定位到 VIN 的车辆。 - 无 VIN 的 JT808 等数据保留在 raw/Redis/session 中用于排查和待绑定,不进入正式车辆指标。 - 生产:RDS 已在上线前删除泛化 `vehicle_daily_metric`,改为专用 `vehicle_daily_mileage`。 4. `vehicle_locations` 不再接收临时身份兜底字段。 - 原因:位置历史是正式车辆时间序列,只按 `protocol + vin` 分表和查询。 - 无 VIN 的位置帧仍保留在 `raw_frames.parsed_json`,待 identity/binding 修复后可从 raw 重放补写。 - 生产:ECS TDengine `vehicle_locations` 已在上线前重建为 `protocol`、`vin` 两个 tag。 5. identity 层只保留两张表。 - `vehicle_identity_binding`:VIN 与 plate/phone/device_id 的映射,供 808 等协议反查 VIN。 - `jt808_registration`:808 注册、鉴权、最新活跃和 VIN 匹配状态。 - 生产:RDS 已在上线前删除旧 `vehicle_identity_binding_registration`、`vehicle_identity_bindings`,gateway 启动会自动创建最小 schema。 ## 字段提升规则 协议字段进入系统后分三层: 1. `parsed_json`:默认入口,保存完整结构化解析。 2. 核心列:只有跨协议稳定查询需要时才提升,例如经纬度、速度、SOC、总里程。 3. 指标表:只有聚合口径稳定、产品需要分页/排序/报表时才持久化。 反例: - 不因为某个协议字段存在就增加 MySQL 列。 - 不为了方便调试在 snapshot/location 放完整 JSON。 - 不为 telemetry field 配置服务提前复制一份字段表;字段配置服务应从 raw/Kafka 回放生成自己的结果。 ## 删除或迁移顺序 1. 先写测试证明旧 API 能从新主表读到同等结果。 2. 改代码停止写重复表或重复列。 3. 部署后观察 Kafka lag、writer 成功计数、API 结果。 4. 查询生产库确认旧表不再新增。 5. 做一次备份或导出。 6. 删除旧表或旧列。 任何一步无法证明安全,就停在兼容状态,不直接删生产数据。