一个让我凌晨三点还在排查的问题,根因居然和“视角”有关

想说件最近实打实踩的坑,也顺便聊聊这些年做开发、跟团队协作下来,越来越深的一个感受——有些Bug,不是技术不够硬,是我们太容易困在自己的岗位视角里,忽略了上下游的关联。

0

问题复盘:

事情发生在一周前。我们组维护的用户中心系统,上线了一个“批量查询用户权限”的接口。上线前,单元测试、集成测试全过,压测也没发现异常。上线后第三天,监控开始报警:接口偶发响应超时,短则200ms,长则十几秒,毫无规律,高峰时段更频繁。

排查一开始就陷入了僵局:

前端同事查了请求参数和缓存策略,确认请求格式没问题,本地调用测试环境接口也正常;
我们后端查了接口日志,入参和业务逻辑都没异常。用Postman单独调用接口也次次正常,初步排除了代码死循环或内存泄漏;
网关同事帮忙抓了包,发现一个关键线索:请求到达后端的时间,与后端返回的时间,差值偶尔会突然拉大。这说明,后端在处理请求时,内部确实有某个环节变慢了;
于是找DBA查慢查询日志,但所有关联SQL的执行时间都在50ms以内,索引也正常。

排查到这里,每个人都觉得自己负责的环节没问题,问题像凭空消失了一样。

直到凌晨,测试组的同事主动介入。他没有止步于“复现Bug”,而是用SkyWalking拉取了完整调用链路,并在压测环境模拟了上线后的真实数据量(比测试环境膨胀了3倍,且大量批量查询场景),终于复现了偶发超时。

顺着链路一层层追下去,最终定位到了根因:不是单个SQL慢,而是ORM代码里隐藏的N+1查询。一段MyBatis-Plus的lambda链式调用,在查询用户列表后,又在循环里调用了getUserPermission()方法,而方法内部再次查询了数据库。测试环境数据量小,循环次数少,问题被完美掩盖;上线后批量查询的循环次数暴涨,单次50ms,循环100次就是5秒,接口响应时间瞬间飙到十秒级。

修复很简单,把循环查询改为批量查询,用IN条件一次性拉取所有用户权限,接口响应时间稳定在了100ms以内。

问题本身不复杂,但修复后复盘时,测试同事的一句话让我琢磨了很久:“这种循环依赖的场景我之前其实想过要测,但测试环境数据量太小,没显出问题来。”

让我真正想聊的,不是这个Bug本身

这场故障让我彻底想明白一件事:在复杂系统里,绝大多数能躲过层层检查的疑难Bug,都长在不同角色的“交界处”。

前端和后端的交界处:后端觉得返回完整嵌套对象方便,前端却可能因解析冗余数据导致列表卡顿;前端在循环里不假思索地调用批量接口,后端的数据库就可能被突如其来的IN查询打爆。前端不懂后端的查询成本,后端不懂前端的渲染痛点,矛盾就出在这儿。

业务代码和数据层的交界处:我写代码时只想着功能实现,觉得链式调用简洁,却忽略了数据量增长对ORM查询的致命影响;DBA只关注到单条SQL的执行计划,却不知道业务代码在循环里调用它。

开发和测试的交界处:我们都容易默认“对方会覆盖所有场景”,于是这种“看似没问题,实际有隐患”的Bug,就溜过了单元测试和集成测试,直到上线才暴露。

我见过最高效的排查,从来不是一个人在专业领域里死磕,而是能跳出岗位,站在对方视角想问题:前端能懂一点后端查询逻辑,后端能预判一点数据增长的性能风险,测试能读懂关键代码逻辑。反过来,我也见过太多低效的拉锯——前端后端互相截日志证明自己没问题,开发和DBA各执一词,时间都在猜疑中耗尽了。

顺带提一句

最近有个合作挺久的团队在招人,前端、后端、测试岗都有,全国不少城市都有HC。我和他们技术负责人挺熟,团队技术氛围不错,待遇和稳定性都算靠谱。感兴趣的朋友可以看一眼:软件工程师社会招聘-表单-金数据

最后回到凌晨三点的那个Bug,其实谁都没有错——代码通过了评审,测试完成了验证,DBA也尽了职责。问题在于,我们都困在自己的岗位视角里,忽略了那个“似乎不属于自己”的交界地带。

现在我判断一个技术人,不只看本专业的深度,更看他对自己边界之外的东西有没有好奇心。

你的舒适区边界在哪儿,排查问题的边界,大概就在哪儿。