Record-API 性能优化实战:从“锁”到“快”的深度治理

一、问题背景

近期在生产环境中,我们发现 record-api 服务出现响应缓慢甚至频繁超时的问题。具体表现包括:

  • Record 单个删除接口 请求耗时超过 1 小时

  • 批量删除接口 长时间无响应;

  • 查询 Record 详情接口 耗时高达 10 分钟以上

  • 整个record-api 关联的数据库直接查询不了, 严重影响的电话业务.

随着多个区域客户陆续反馈服务不可用,我们立即展开排查。最终发现问题根源在于 PostgreSQL 数据库中 records 表频繁发生 行级锁竞争(Lock: tuple) ,极大地影响了整体读写性能,进而导致服务雪崩。进一步调查发现:是并发的 delete 请求和一个定时任务在数据库中产生了锁冲突,从而引发了严重的性能瓶颈,最终导致 PostgreSQL 数据库的 CPU 达到 99%,服务基本不可用。

目前Record的单表数据量已经达到了 2 千万, 之前创建了一个索引, 花费了半个小时... 已经从 4 千万数量删除到了 2千万, 删除了之前软删除的1 年之前的数据.

这个表中还有大量的 2 年之前的数据, 未来还可以考虑做数据归档.


二、根因分析:并发写请求与定时任务引发的锁冲突

排查过程中发现如下原因叠加导致锁冲突严重:

  1. 多个大客户高频调用 DELETE 接口(单个 + 批量),形成激烈并发, 它们直接使用 API 调用的方式, 疯狂调用删除;

  2. 后台定时任务 ExtraKeysScheduler 同时频繁更新 records 表中的 historical_keys_log 字段;

  3. 两者操作重叠,产生严重行级锁冲突,阻塞其他事务;

  4. 数据库未设置 socket_timeout,部分连接长期占用连接池资源,最终导致线程耗尽,服务完全雪崩。

异常日志如下所示:

复制代码
org.postgresql.util.PSQLException: ERROR: canceling statement due to lock timeout
Where: while updating tuple (656,9) in relation "records_account_xxx"

三、第一阶段:快速止血与初步优化

暂停高风险任务

  • 立即停止 ExtraKeysScheduler 定时任务,避免与 DELETE 接口抢占锁资源;

  • 稳住主业务服务,防止服务进一步雪崩。

调整 Record 单个删除接口的允许访问频率, 从每秒的50 个请求直接先调整为每秒 5 个请求.

为record-api与数据库的连接设置一个 10 分钟的套接字超时(Socket Timeout)

目的: 此举旨在防止应用在等待数据库响应时无限期地挂起。当数据库操作耗时过长(例如超过10分钟),该设置会强制中断网络连接,从而触发异常。这能确保应用连接可以被及时释放回连接池,避免因长时间的数据库交互导致连接资源耗尽和泄漏

在数据库事务中添加锁等待超时

在 PostgreSQL 数据库事务中配置 锁等待超时(Lock Timeout)

目的: 当一个事务试图获取一个被其他事务持有的锁时,它不会永久等待。该配置将使其在等待一个预设的时间后自动放弃。这可以自动中止因等待锁而阻塞的事务,防止死锁(Deadlocks)的发生,并快速释放数据库资源,提高系统的健壮性。

SQL 查询逻辑优化

1. 移除 SQL 中的动态函数调用,提升索引命中率

原查询示例:

复制代码
select * from extra_keys
where updated_at <= now() - interval '2 hour'
  and updated_at > created_at
  and counter <= 0

优化后:

复制代码
val threshold = now() - Duration.ofHours(2)
...
where updated_at <= :threshold

通过代码提前计算 threshold,提高了索引命中率,显著降低查询耗时。

2. 批量插入改为分批处理,降低事务压力

原逻辑:

复制代码
insert into historical_processor
select ... from records where updated_at between ...

优化方案:

  • 将 SQL 插入迁移至代码层控制;

  • 使用分页拉取数据 + 批量插入方式,缩小事务规模,减轻数据库锁压力。

3. 避免多表 update 导致锁表

原 SQL:

复制代码
update records t
set historical_keys_log = null
from historical_processor h
where h.record_id = t.id ...

优化后使用精准索引:

复制代码
UPDATE records
SET historical_keys_log = null
WHERE account_id = :accountId AND id IN (:recordIds)
  • 使用 (account_id, id) 精准索引;

  • 避免嵌套循环 JOIN 导致的大范围锁表。


四、第二阶段:任务调度重构与区域验证

初步优化上线后,加拿大区域表现稳定。但在美国和欧洲区域上线时暴露出新问题:

  • Job 被暂停数日,堆积 47 万条未处理记录

  • 一次性全量处理导致事务超大,再次出现超时问题。

定时任务重构方案

  1. 动态获取每个分表对应的 account_id

    SELECT regexp_replace(c.relname, '^records_v2_account_', '') AS account_id
    FROM pg_inherits i
    JOIN pg_class c ON i.inhrelid = c.oid
    WHERE i.inhparent = 'records_v2'::regclass

  2. 按 account_id 分批处理,每批处理 500 条记录

  3. 任务执行频率由每 10 分钟提升为每分钟

  4. 缩小事务粒度,显著降低锁竞争。

上线结果:

  • us-east-1 和 eu-central-1 成功跑完全部任务;

  • Job 单次执行耗时 < 1 秒;

  • 所有积压数据 1 天内全部清理完毕。


五、第三阶段:Delete 接口体系重构

旧架构问题

  • 高并发触发 DELETE 接口;

  • 同一 record_id 被多次重复删除;

  • 与定时任务竞争锁,导致服务冻结;

  • 无法保障数据一致性与稳定性。

重构方案

1. 引入 Redis 分布式锁
  • 按 record_id 加锁,避免并发冲突;

  • 设置锁超时为 5 秒,快速失败反馈。

2. 使用 FOR UPDATE SKIP LOCKED
  • 避免读取已被锁住的记录;

  • 仅处理可立即锁定的数据行。

3. 支持异步删除架构
  • 加入 Feature Flag:BULK_DELETE_RECORDS_BY_QUEUE

  • 将删除任务统一入 Redis 队列;

  • 后台异步线程消费处理;

  • 提升主线程响应速度与系统稳定性。

Redis 队列结构优化

  • deleteRecordsByIds → 入队 storeDeleteRecordsToQueue

  • deleteByPhoneNumbers / ExternalIds → 校验通过后入队;

  • 响应码设计优化:入队成功返回 202 Accepted


六、经验教训总结

成功经验

  • 快速定位问题 SQL,调整查询逻辑与索引使用方式;

  • 第一时间中止高风险任务,控制故障范围;

  • 架构从同步转异步,从粗放转精细,服务鲁棒性显著提升;

  • 合理引入 Redis 锁机制、限流控制与异步调度;

  • 使用 FOR UPDATE SKIP LOCKED 回避锁冲突,保障系统运行。

遗留问题与优化方向

  • DELETE 接口缺乏全局频控:将引入 API Rate Limiter 限流机制;

  • Job 执行缺乏断点续跑能力:计划引入状态记录与幂等重试逻辑;

  • 增强观测能力:增加事务耗时、锁等待、连接池状态等指标监控。


七、结语

此次 record-api 性能治理经历了从 性能雪崩 → 快速止血 → 架构优化 → 稳定运行 的全过程。

我们不仅解决了服务宕机问题,更通过深入分析和分层治理实现了系统的可持续优化。

这次实战过程中的技术方法和策略,希望能为其他团队应对高并发与数据库性能问题提供实用借鉴。

相关推荐
吃喝不愁霸王餐APP开发者43 分钟前
霸王餐试吃资格发放:Redis HyperLogLog亿级去重与Lua脚本原子性
数据库·redis·lua
q***484144 分钟前
【MySQL】视图
数据库·mysql·oracle
007php0071 小时前
nginx加速缓存导致Event-Stream消息延迟问题的解决方案
运维·网络·数据库·nginx·缓存·面试·职场和发展
o***11141 小时前
智能生成ER图工具。使用 SQL 生成 ER 图:让数据库设计更高效
数据库·sql·oracle
拾忆,想起1 小时前
Dubbo序列化方式全解析:从原理到实战的性能优化指南
服务器·网络·微服务·性能优化·架构·dubbo
u***42071 小时前
Spring Data JDBC 详解
java·数据库·spring
k***92161 小时前
深入了解 MySQL 中的 JSON_CONTAINS
数据库·mysql·json
小石头 100861 小时前
【MySql】CRUD
数据库·mysql·adb
k***21601 小时前
【HTML+CSS】使用HTML与后端技术连接数据库
css·数据库·html