上一篇讲了迁移的整体思路,这篇来聊聊具体执行时遇到的坑。有些坑是代码层面的,有些是文档和认知层面的。记录出来,既给自己留个备忘,也给遇到类似问题的人一个参考。
坑一:OptimizedStorageService 写了一堆,但系统根本没用上
这是最尴尬的一个发现。代码库里早就有一个 OptimizedStorageService,号称是"优化后的存储服务",实现了文件保存、元数据管理、迁移逻辑等功能。但当我准备集成它时,发现整个后端代码没有任何地方引用这个类——所有 Repository 和 API 都在用另一个旧的 StorageService。
也就是说,OptimizedStorageService 是个"僵尸代码":它存在,但它没有参与任何实际业务流程。
解决方法:把所有 from ..services.storage_service import StorageService 的引用,统一改成 from ..services.optimized_storage_service import OptimizedStorageService as StorageService。这样业务代码一行不用改,底层实现就切过去了。
坑二:一个漏掉的 import,导致清理功能直接报错
OptimizedStorageService 里有一个 cleanup_old_files 方法,逻辑是清理超过 N 天的旧文件。代码里用到了 timedelta(days=keep_days),但文件顶部的 import 只写了 from datetime import datetime,漏了 timedelta。
这种 bug 平时很难发现——因为清理功能不是每次都会触发,只有在特定条件下调用时才会报错。
解决方法:补上 from datetime import datetime, timedelta。简单一行,但反映了代码审查的疏漏。
坑三:"保存视频文件"实际上是 touch 了一个空文件
save_clip_file 和 save_collection_file 两个方法的实现,居然只是 target_path.touch()——创建一个 0 字节的空文件,然后返回路径。注释里还诚实地说"暂时返回模拟路径"。
这意味着系统一直以为视频文件保存成功了,实际上磁盘上只有空占位符。如果前端或下游服务真的去读取这些文件,会发现文件大小为 0。
解决方法:改成支持实际文件复制的逻辑——如果传入的数据里包含 source_video_path 且文件存在,就 shutil.copy2() 复制过去;否则才创建占位文件等待后续处理流程填充。
坑四:两份文档互相矛盾,一个说 75% 一个说 100%
项目 docs 目录下有两份存储优化报告:
- STORAGE_OPTIMIZATION_PROGRESS_REPORT.md 写着总体完成度 75%,数据迁移阶段仅 33%
- STORAGE_OPTIMIZATION_COMPLETION_REPORT.md 写着总体完成度 100%,所有阶段全部完成
这种矛盾会让人严重误判项目状态。更麻烦的是,"完成报告"里声称已创建的 backend/migrations/optimize_storage_models.py 实际上根本不存在。
解决方法:删除虚假的"完成报告",更新"进度报告"为实际完成状态。同时补建 backend/migrations/ 目录和正式迁移入口文件。
坑五:Pipeline 处理完后还在生成重复的 JSON 文件
数据迁移完成后,我检查了 Pipeline 的处理流程,发现 step6_video.py 在视频生成结束后,仍然调用 save_clip_metadata() 和 save_collection_metadata() 把数据写入 clips_metadata.json 和 collections_metadata.json。
这等于刚拆完炸弹,又装了一个新的。新处理的项目会继续产生双重存储。
解决方法:去掉 run_step6_video() 中的 JSON 保存调用。中间步骤文件(如 step3_all_scored.json、step4_titles.json)保留,因为它们是 Pipeline 步骤间的数据传递介质;但最终输出不应该再以 JSON 形式重复存储。
坑六:DataSyncService 仍然优先读 JSON,而不是数据库
DataSyncService 的职责是把文件系统的处理结果同步到数据库。但它的实现是:先遍历一堆 JSON 文件路径(clips_metadata.json、step4_titles.json 等),读到数据后再写入数据库。
在分离存储架构下,这个逻辑完全反了——数据库应该是唯一数据源。如果 JSON 文件被删或没生成,同步就会失败。
解决方法:在同步方法开头加一层判断:如果数据库里已经存在该项目的切片/合集数据,直接跳过文件系统读取。这样新架构下的项目走数据库,旧项目如果需要补数据还可以从 JSON fallback。
坑七:API 层的"清理重复"接口依赖 JSON 文件
clips.py 里有一个 cleanup-duplicates 接口,它的逻辑是读取 clips_metadata.json 获取"原始"切片列表,然后对比数据库中的记录,不在列表里的就删除。
这个设计在双重存储时代是合理的——JSON 是"源",数据库是"副本"。但在分离存储架构下,JSON 已经不是源了,这个接口会 404 或误删数据。
解决方法:重写接口逻辑。改为直接在数据库内去重:按 title 分组,保留最新的一条,删除其余重复记录;同时删除 video_path 无效的记录。
坑八:旧数据残留比想象中多
迁移完成后做了一次数据一致性检查,发现数据库里躺着 44 个切片、8 个合集、2 个项目的无效记录。它们的共同点是:要么 video_path 指向了旧路径(/Users/zhoukk/autoclip/...),要么项目目录已经不存在了。
这些记录是怎么来的?可能是早期开发时的测试数据,也可能是之前迁移或重构过程中遗漏的。它们不会影响正常功能,但会污染查询结果和统计数据。
解决方法:写了一个 cleanup_orphaned_data.py 脚本,扫描规则是:
- 项目目录不存在 → 删除项目及所有关联切片/合集
- 切片/合集的视频文件不存在 → 删除该记录
执行前同样先备份数据库,然后 dry-run 预览,确认后再执行。
写在最后
这次迁移让我对"技术债"有了更深的体感。很多问题不是代码写错了,而是认知没跟上——代码改了,但处理流程还在按旧逻辑走;文档写了,但和代码实际状态脱节。
填坑的过程也是建立信任的过程:对代码的信任(它真的在做我以为它在做的事),对文档的信任(它描述的是当前状态而不是理想状态),对数据的信任(备份和验证机制让我敢动手)。
如果你也在做类似的数据迁移,我的建议是:慢就是快。备份多花十分钟,可能省下十小时的恢复时间。验证多写几行代码,可能避免一次生产事故。