建立日期:2026-09-19
状态:持续维护
文档导航:DataDownload README;目标需求见 requirements.md;技术设计见 technical-design.md;当前实现见 implementation-status.md。 范围:DataDownload 首版实现差异、可靠性风险,以及当前阻断全仓测试的既有源码问题。
open、in_progress、blocked、resolved、wont_fix。P0:可能造成数据错误、并发覆盖、返回旧内容或任务永久异常,发布前必须解决。P1:可能造成资源泄漏、内存故障、重要功能缺失或运维风险。P2:产品能力、展示或工程质量未完整对齐设计。P3:仓库既有问题,不直接影响 DataDownload 主链路,但会阻断全仓验证。requirements.md 为准,技术约束以 technical-design.md 为准,当前事实以源码、测试和 implementation-status.md 为准。| ID | 优先级 | 状态 | 问题 |
|---|---|---|---|
| DD-001 | P0 | open | Redis 锁没有续租和 fencing token(观测优先:第 1 层已落地,第 2/3 层待触发) |
| DD-002 | P0 | resolved | PostgreSQL、Mongo Document、RenderSlot 提交不具备崩溃一致性(方向 A:先失效后提交;读侧竞态转 DD-008) |
| DD-003 | P0 | resolved | FileRequestService 未按 RequestID/延迟等待模型实现 |
| DD-004 | P0 | resolved | plan handler 重试不幂等 |
| DD-005 | P0 | resolved | RequestKey 缺少影响正文身份的字段 |
| DD-006 | P0 | resolved | FaultRecord 文档粒度和存储模型错误 |
| DD-007 | P1 | open | Artifact 发布缺少 generation/token CAS 校验 |
| DD-008 | P1 | open | 缓存命中未校验 PostgreSQL 权威 Generation |
| DD-009 | P1 | open | 二进制切换失败会产生孤儿对象 |
| DD-010 | P1 | open | 大文件读取与 ZIP 构建存在内存放大 |
| DD-011 | P1 | open | PostgreSQL migration 多实例并发不安全 |
| DD-012 | P1 | open | 任务删除不是事务或原子状态变更 |
| DD-013 | P1 | resolved | PackageJob PurgeAt 计算基准错误 |
| DD-014 | P1 | open | MinIO Repository 没有传播 context |
| DD-015 | P1 | open | 归档内部文件读取没有大小上限 |
| DD-016 | P2 | open | PostgreSQL 强类型结构仍处于过渡态 |
| DD-017 | P2 | open | Renderer 输出未完整对齐设计 |
| DD-018 | P2 | open | 前端真实预览与归档浏览未实现 |
| DD-019 | P2 | open | 前端批量操作和部分文件选择未实现 |
| DD-020 | P2 | open | 任务、Package 和管理员页面过滤/权限展示不完整 |
| DD-021 | P2 | open | MCP 能力和幂等创建未完整实现 |
| DD-022 | P2 | open | MongoDB 索引与设计索引表不完全一致 |
| DD-023 | P2 | open | 自动化测试覆盖不足 |
| REPO-001~007 | P3 | open | 仓库既有编译和旧测试签名问题 |
open(当前采用「观测优先」策略:先补充可观测性,待真实样本再决定 CAS/TTL 方案)service/cache/redisx/lockx.go、service/services/dataDownloadService.go、dataDownloadBinaryService.go该问题「理论存在、实际频率未知」,真实行数量级与 PG/MinIO 实际延迟均为估算。generation CAS 改造牵连 DD-002/007/008,投入较大。因此先补充观测日志,让问题从「悄悄写坏」变为「事后可判定、可定位」,攒到真实的耗时分布与 owner 丢失样本后,再决定是上 CAS 还是仅调 TTL / 改批量写入。本条在样本充分前保持 open,不因加了日志而视为已修复。
第 1 层(已落地,2026-09-20):service/cache/redisx/lockx.go 的 RedisLock 内建观测,一处覆盖全部锁(content / package / request lease,cache.RedisLock 为其类型别名,调用方透明):
acquired(加锁时刻,单调计时)、label(从 key 前两段推断,如 data-download:content)。SafeRelease 分三档记录:正常释放但持锁 > TTL×70% 记 Warn;释放时发现 not owner(锁被抢走、可能已并发覆盖)记 Error(关键铁证,带 label/key/token/held/ttl);其他释放失败记 Error。errLockNotOwner,统一 Release/LockRelease/Extend/SetIfOwner 的 not-owner 返回值,供 errors.Is 精确判定。go build ./cache/...、go test ./services ./cache/redisx ./database 通过。第 2 层(待补,出问题后再加):写入临界区分段耗时 + 数据规模。
ExecuteStructuredDataDownloadItem / ExecuteBinaryDataDownloadItem 拿锁之后。documentId、taskId、kind、rowCount(结构化)/objectBytes(二进制)、分段耗时(replaceRows/minioPut/mongoWrite)、total、ttl。Debug(生产默认 Warn 级别,不输出,零成本;需要时可临时调级看全量);total ≥ TTL×50% 打 Warn 带完整分段。默认零噪音,与第 1 层职责不重叠(第 1 层管「有没有出事/接近出事」,第 2 层管「出事时慢在哪一步」)。第 3 层(待补,视第 2 层结果再加):ReplaceDataDownloadRows 内部 pg_advisory_xact_lock 获取耗时。
replaceRows 大但 rowCount 不高时,再补此层定位。Warn。resolved(方向 A,2026-09-20)service/services/dataDownloadService.go(ExecuteStructuredDataDownloadItem 提交段)currentArtifact 因 slot.ContentGeneration == document.ContentGeneration(都为 N-1)判定命中,静默返回旧代缓存且不自愈。DetachDataDownloadRenderSlots 从「PG + Upsert 之后」提前到 ReplaceDataDownloadRows 之前。断开 RenderSlot 后 currentArtifact 会因 slot.ArtifactID==nil 判定 miss(GetDataDownloadRenderSlot 无 slot 时返回 ErrNoDocuments,调用方仅在 err==nil 时命中,其余一律重渲染),强制后续读者从 PG 当前 generation 重新渲染。
BinaryObjectKey 直读、无 RenderSlot,不涉及本窗口。go build ./services/...、go vet ./services/、go test ./services ./repositories/pgsql ./database 通过;崩溃注入的端到端恢复测试仍归 DD-023。resolveddata-download:request:{DocumentID} 租约,RequestID 使用租约 token;等待者携带 AwaitRequestID 按 15/30/60 秒退避重新入队,保持 pending,不占用 worker。租约自动续期,提交前校验 owner token,完成标记通过 Lua 在仍持有租约时原子写入。Mutable 同轮等待者复用对应 RequestID 的结果;Dynamic 完成等待后重新竞争下一轮租约。resolvedservice/task/queue/dataDownloadQueue/dataDownloadQueue.go:132-195(taskId,itemKey) 的无序 BulkWrite upsert,并回填已存在 item 的 _id;稳定 Asynq TaskID 冲突按“已经调度”处理。plan 和 site_validation 重规划均可重复执行。resolvedservice/services/dataDownloadService.go:168-170UpdateLog 不带日期和时区,MQTT/EMS 按需包含 SubType,按本地日期查询或解析的类型包含实际时区,FaultRecord 使用 DeviceSN/DevType 设备级身份。每种类型使用独立 content_version 合并管理 query/parser/schema 语义版本,RendererVersion 不进入 RequestKey。ItemKey 不再充当文档身份。resolvedDeviceSN + DevType + DataDate item;执行阶段建立 DataDownloadFaultRecordEntry 多文件目录与任务级 DataDownloadFileAttempt,每个录波文件独立上传、发布和重试,不再创建每日 ZIP。TaskDocument 路径包含设备和日期,任务详情返回文件目录及 Attempt,并提供校验 task owner/admin 的任务入口单文件下载接口。DeviceSN + DevType + DataDate 文档单元;成功文件立即可用,文件级失败使 item/task 为 partial,旧当前文件在刷新失败时继续服务但不能计作新任务成功;上游后续未返回的历史文件继续保留;首版一键重试全部失败文件,不提供单文件人工重试。USER_AI,AI 以数据可用性为中心查询,AI任务仅管理员可见。DataDownloadReport.json;D+2 后完整查询且无失败条目才能 Frozen。DataDownloadReport.json。相关 Service、Queue、Controller、Route、Mongo/PostgreSQL 包测试通过。PublishDataDownloadArtifact 直接按 (documentId,format) upsert Slot。service/services/dataDownloadBinaryService.go:129-160[]byte/bytes.Buffer。service/database/data_download_schema.goservice/repositories/mongo/dataDownloadRepo.go 的 DeleteDataDownloadTask。resolvedservice/services/dataDownloadCleanupService.go:27PurgeAt = ExpiresAt + 30天,不再以清理实际执行时间为基准;下载与清理使用同一个 Package 锁,避免流式响应期间删除对象。service/repositories/minio/dataDownloadRepo.go 和现有 utils/minio。service/services/dataDownloadArchiveService.go。payload jsonb、通用 UpdateLog 表和冗余字段。<pre>,未生成 ECharts。SystemUpdate、aHubUpdate 两个 Sheet。{state,updatedAt}。updatedAt,设计为 createdAt。{taskId,logicalPath}。{status,createdAt} 和 expiresAt 索引。至少需要增加:
这些问题不是 DataDownload 引入,但当前会使 go test ./... 失败。
service/models/model_pgsql/siteOriginalData.go:54unexpected keyword type。service/cmd/gen/fwh_pro.gocfg.MySQLDSN。service/cmd/gen/pgsql/alarm_snapshot.gocfg.PostgresDSN。service/cmd/test_plane_v2、service/repositories/mqtt/fwh_mqtt_test/test、service/test/shutdownStatistics、service/test/snapTest、service/test/getSnapTest、service/test/temp 等。service/test/getApowerSn/getApowerSn.gores.Result 为 *[]domain.FwhSiteInfo,需要先判空并解引用。service/test/getSnapTest/getAlarmSnapshotTest.gojobs.GetAlarmSnapshot。service/test/temp/runtimeSort.goGetSiteAnalysisManageList 的调用参数数量与当前签名不一致。