
当你的 SCA 平台悄悄罢工了一周而你甚至没发现……前言每个运维人都经历过这样的时刻——系统安静地运行着你以为一切安好直到某天有人问“咦这个平台的数据怎么一周没更新了”本文记录了一次 Dependency-Track以下简称 DT服务因项目数量堆积导致雪崩式故障的完整排查与恢复过程。从发现问题到定位根因从备份数据到大规模清理从脚本中断到恢复执行——全程踩坑坑坑真实。希望这份记录能帮到同样在用 DT 做 SCA 的你至少……别重蹈我们的覆辙。一、案发现场数据断更一周某天收到反馈SAST 安全平台的前端数据停更了。查看接口返回最新记录停留在一周前。第一反应数据流链路断了。这条链路是CI 构建 → SCA 扫描 → 上传 SBOM 到 DT → SAST 平台拉取数据 → 前端展示。任何一环出问题前端就不会有新数据。于是开始逐环排查。二、揪出元凶DT 服务累瘫了2.1 网页能开登录失败DT 的前端页面可以正常加载但登录请求始终失败。这说明前端静态资源没问题但后端 API 服务有毛病。2.2 容器状态unhealthy查看 Docker 容器状态发现 DT 的 apiserver 容器状态为unhealthy。进一步查看健康检查日志连续失败次数已经累积到了15899 次。按默认 3 秒一次的健康检查间隔算大约已经连续失败了16 个小时。一个健康检查连续失败一万多次才被发现——这大概就是沉默的大多数。2.3 日志里的真相打开 DT 应用日志满屏都是这样的内容INFO [ProjectMetricsUpdateTask] Executing metrics update for project xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx INFO [ProjectMetricsUpdateTask] Executing metrics update for project yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy ...每秒刷出十几条永无止境。DT 有一个定时任务ProjectMetricsUpdateTask会为每一个项目执行指标更新。当项目数量达到 ~20,000 个时单轮指标更新就需要数十分钟。API 线程长期被占用健康检查端点无法在 3 秒内响应于是容器被标记为unhealthy。根因确认项目太多DT 被累瘫了。三、为什么会有两万个项目这个问题的答案可能会让很多 DT 用户会心一笑——CI 流水线每次构建都会创建一个新的项目版本。一个服务一天构建 3 次一年就是 ~1,000 个版本。几个服务跑两年轻松破万。这些历史版本在 DT 里都是独立的项目每个都要参与指标更新计算。如果没有配置自动清理它们就会像滚雪球一样越积越多直到某一天把服务压垮。因果链CI 每次构建创建新项目版本两年没清理 → 项目数累积至 ~20,000 → 指标更新任务单轮耗时过长 → API 健康检查持续超时 → 容器标记为 unhealthy → CI 的 SCA 步骤上传 SBOM 失败 → 前端数据断更四、抢救过程4.1 第一步先让服务活过来重启 DT 容器。重启后 API 恢复响应健康检查回到healthy。但这只是续命不清理的话很快又会回到老样子。4.2 第二步备份备份备份在做任何删除操作之前先做全量数据库备份。这是铁律。# 使用 Docker 容器执行 pg_dump避免版本不兼容问题dockerrun--rm-v/backup:/backup\postgres:12\pg_dump-hDB_HOST-UDB_USER-dDB_NAME-Fc-f/backup/dtrack_backup.dump备份文件 3.6 GB。有了这个就算删错了也能回滚。踩坑记录系统自带的pg_dump版本太旧9.2无法连接 PG 12必须用 Docker 容器或安装匹配版本的客户端。Docker Hub 直连超时需要配置镜像加速。操作系统已 EOL官方 yum 源返回 404安装新工具困难重重。4.3 第三步确定清理范围API 分页的坑首先尝试通过 DT API 获取项目列表。查询总数没问题X-Total-Count响应头返回 20,093但分页拉取时遇到了 bug——无论怎么调整pageNum和pageSize参数返回的数据始终一样。解决方案绕过 API直接用 SQL 导出COPY(SELECTUUID,NAME,VERSION,LAST_BOM_IMPORTEDFROMPROJECT)TOSTDOUTWITHCSV HEADER导出 20,094 个项目干净利落。留存策略的选择清理脚本按LAST_BOM_IMPORTED最后一次 SBOM 导入时间过滤超过指定天数未活动的标记为待删除。策略保留删除评价30 天71619,378太激进容易误伤180 天4,74215,352平衡之选最终选择 180 天——既保留了半年的数据用于合规审计又能删掉 76% 的冗余项目。4.4 第四步编写清理脚本核心思路很简单从 CSV 读取项目清单按时间戳过滤分出保留和待删除多线程并发调用DELETE /api/v1/project/{uuid}fromconcurrent.futuresimportThreadPoolExecutor,as_completed WORKERS6# 6 线程并发万级数据约 1~2 小时withThreadPoolExecutor(max_workersWORKERS)asex:futures[ex.submit(delete_project,item)foriteminto_delete]forfutinas_completed(futures):# 统计成功/失败数...脚本开发踩坑记坑表现原因解决类型错误TypeError: int object is not subscriptableAPI 返回的时间戳有时是整数毫秒有时是字符串写兼容解析函数两种格式都处理权限不足API 返回 403API Key 缺少VIEW_PORTFOLIO权限管理页面补勾权限分页失效每页数据相同DT 4.11 分页 bug改用 SQL 直接导出日志不输出tail 看不到进度Python stdout 重定向时块缓冲使用python3 -u关闭缓冲4.5 第五步执行删除先 Dry Run 验证数字确认无误后后台执行# Dry Run只算不删几秒钟出结果python3-udt_cleanup.py$API_KEY# 确认数字合理后后台真删nohuppython3-udt_cleanup.py$API_KEYcleanup.log21删除速度约100 个/分钟6 线程并发。中途中断了怎么办脚本因故中断后数据库已从 20,094 降到了 11,404。恢复步骤重新导出 CSV旧清单包含已删项目会产生大量 404Dry Run 验证当前状态下的保留/删除数量重新执行脚本关键教训中断后绝不能直接重跑旧脚本。CSV 清单必须和数据库当前状态同步否则就是在用过期地图导航。五、清理后的验证删除完成后需要完成以下验证闭环SQL 计数验证SELECT COUNT(*) FROM PROJECT应落在预期区间兜底重跑重新导出 CSV 再跑一轮清除因 500 错误残留的项目DT 健康检查API 返回 200 OK容器状态healthyCI 链路测试触发一次完整构建确认 SCA 上传成功、前端出新数据六、防止东山再起清理只是治标不建立长效机制项目还会继续堆积。6.1 监控告警部署健康检查脚本每小时检测一次DT API 是否响应项目总数是否超过阈值如 6,000异常时记录告警日志6.2 定期自动清理利用 DT 内置功能或自定义脚本设置每周自动清理超过 N 天的不活跃项目。6.3 源头治理最重要措施说明CI 复用项目版本在 Jenkinsfile 中固定 version 字段避免每次构建创建新项目DT 自动删除策略启用 DT 设置中的N 天无活动自动删除功能治标不如治本。与其事后花几个小时删项目不如从一开始就不让它无限堆积。七、经验总结给 DT 运维人的建议一定要备份任何删除操作前先做全量数据库备份。3.6 GB 的备份换来的是后悔药。Dry Run 是必须的先算后删确认数字合理再动手。SQL 比 API 靠谱大规模数据查询直接走 SQLDT 的 API 分页在数据量大时可能失效。并发要适度6 线程是万级数据清理的甜点——太慢等不起太快数据库连接池会爆。日志要即时可见python3 -u关闭缓冲否则你以为脚本在跑其实它在憋大招。中断后重新导出永远不要用过期清单继续操作。给 DT 开发者的建议API 分页应该可靠这是基础功能不应该 silently broken。指标更新应该分批20,000 个项目不应该在一轮内全部处理应该分批 限流。健康检查应该更智能如果指标更新任务还在跑健康检查应该考虑这个因素而不是简单粗暴地超时判死。八、写在最后这次故障的本质是一个典型的温水煮青蛙场景项目每天多几个看起来没什么直到某天数量突破了临界值服务就崩了。而最讽刺的是——在故障发生前DT 一直在努力工作只不过它努力的方向是更新那两万个项目的指标而不是响应你的健康检查。希望这篇文章能帮到正在读的你。如果你的 DT 项目数已经超过了 5,000不妨现在就去看一眼它的健康状态。也许它正在沉默地挣扎。本文基于 Dependency-Track 4.11.4 版本的真实运维经验撰写。文中涉及的命令和脚本已在生产环境验证。具体环境配置可能因版本和部署方式不同而有所差异请根据实际情况调整。