forked from erp-dev/erp
feat: shipment_delivery
This commit is contained in:
161
docs/2026-04-02-rds-pgbouncer-server-side-cursor-issue.md
Normal file
161
docs/2026-04-02-rds-pgbouncer-server-side-cursor-issue.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# 2026-04-02 RDS PgBouncer 命名游标问题记录
|
||||
|
||||
## 背景
|
||||
|
||||
生产环境在 Django Admin 页面访问 `SalesItem` 编辑页时出现如下错误:
|
||||
|
||||
```text
|
||||
ProgrammingError at /admin/shipment/salesitem/1/change/
|
||||
cursor "_django_curs_128437320623808_sync_1" already exists
|
||||
```
|
||||
|
||||
本地开发/测试环境未复现该问题。
|
||||
|
||||
## 现象
|
||||
|
||||
- 出错页面:`/admin/shipment/salesitem/<id>/change/`
|
||||
- 错误类型:PostgreSQL `ProgrammingError`
|
||||
- 关键错误信息:`cursor "... already exists"`
|
||||
- 特征:属于数据库连接/游标状态错误,而不是表结构错误
|
||||
|
||||
## 排查结论
|
||||
|
||||
### 1. 不是 migrate 未执行导致
|
||||
|
||||
本次错误不符合迁移缺失的典型表现。
|
||||
|
||||
如果是 migration 未执行,通常会看到:
|
||||
|
||||
- 某字段不存在
|
||||
- 某表不存在
|
||||
- 约束不存在
|
||||
- 类型不匹配
|
||||
|
||||
而本次是“命名游标已经存在”,属于运行时数据库连接状态问题。
|
||||
|
||||
### 2. 当前 `SalesItem` Admin 实现本身没有明显异常
|
||||
|
||||
`shipment` 模块中的 admin 配置较为简单,没有明显会主动触发复杂命名游标行为的自定义逻辑:
|
||||
|
||||
- 参考:[shipment/admin.py](/home/f/coding/flower/shipment/admin.py)
|
||||
|
||||
因此本问题更像是生产环境数据库接入方式引发,而不是该页面逻辑本身写坏。
|
||||
|
||||
### 3. 根因判断:阿里云 RDS 自带 PgBouncer/连接池 与 Django server-side cursors 兼容性问题
|
||||
|
||||
生产环境已确认使用“阿里云 RDS 自带代理/连接池”。
|
||||
|
||||
Django 在 PostgreSQL 下会在部分场景使用 server-side cursors(尤其与 `QuerySet.iterator()` 相关)。
|
||||
当应用经过连接池/代理访问数据库时,连接池不能稳定保证命名游标依赖的“同一物理连接上下文”,从而可能触发:
|
||||
|
||||
- 命名游标残留
|
||||
- 游标状态错乱
|
||||
- `cursor "... already exists"`
|
||||
|
||||
这类问题在连接池场景下比直连 PostgreSQL 更容易出现。
|
||||
|
||||
## 代码与配置侧观察
|
||||
|
||||
### 数据库配置
|
||||
|
||||
项目数据库配置位于:
|
||||
|
||||
- [flower/settings.py](/home/f/coding/flower/flower/settings.py)
|
||||
|
||||
原有配置特征:
|
||||
|
||||
- 使用 PostgreSQL
|
||||
- 支持 `CONN_MAX_AGE`
|
||||
- 未显式设置 `DISABLE_SERVER_SIDE_CURSORS`
|
||||
|
||||
### 项目内的连接池配置
|
||||
|
||||
仓库自带的 PgBouncer 配置位于:
|
||||
|
||||
- [deploy/pgbouncer/pgbouncer.ini](/home/f/coding/flower/deploy/pgbouncer/pgbouncer.ini)
|
||||
|
||||
仓库内自建 PgBouncer 使用的是 `session` 模式。
|
||||
但生产环境此次问题来自阿里云 RDS 自带代理/连接池,实际行为应以云侧代理为准,不能简单等同于本仓库自带 PgBouncer 配置。
|
||||
|
||||
## 修复决策
|
||||
|
||||
考虑到:
|
||||
|
||||
- 生产环境不能放弃连接池
|
||||
- 该系统存在较多大查询
|
||||
- 生产已经出现与命名游标相关的实际错误
|
||||
|
||||
最终决定采用 Django 官方支持的兼容方案:
|
||||
|
||||
在数据库配置中加入:
|
||||
|
||||
```python
|
||||
'DISABLE_SERVER_SIDE_CURSORS': True,
|
||||
```
|
||||
|
||||
并保留:
|
||||
|
||||
```python
|
||||
'CONN_MAX_AGE': env.int('CONN_MAX_AGE', default=0),
|
||||
```
|
||||
|
||||
## 修复位置
|
||||
|
||||
已在以下位置加入配置:
|
||||
|
||||
- [flower/settings.py](/home/f/coding/flower/flower/settings.py)
|
||||
|
||||
配置位置为 `DATABASES['default']` 顶层,与 `ENGINE`、`HOST`、`PORT`、`CONN_MAX_AGE` 同级,不放在 `OPTIONS` 中。
|
||||
|
||||
## 采用该修复的原因
|
||||
|
||||
### 为什么不优先怀疑 migration
|
||||
|
||||
因为错误类型明显属于连接/游标状态,不属于 schema 不一致。
|
||||
|
||||
### 为什么不取消连接池
|
||||
|
||||
生产环境仍需要连接池承载系统中的大查询与整体数据库连接管理。
|
||||
|
||||
### 为什么选择禁用 server-side cursors
|
||||
|
||||
这是 Django 官方针对连接池/代理场景给出的标准兼容方向之一。
|
||||
|
||||
对当前项目而言,这一决策的风险低于继续保留命名游标并承受生产不稳定性。
|
||||
|
||||
## 可能的副作用
|
||||
|
||||
启用 `DISABLE_SERVER_SIDE_CURSORS=True` 后:
|
||||
|
||||
- 普通页面和常规 ORM 查询通常不会受功能性影响
|
||||
- 依赖 `QuerySet.iterator()` 的超大结果集遍历,可能失去 server-side cursor 带来的部分性能/内存优势
|
||||
|
||||
项目中需要后续重点观察的批量遍历代码包括:
|
||||
|
||||
- [printing/tasks.py](/home/f/coding/flower/printing/tasks.py)
|
||||
- [api_v1/external_product_image_backfill.py](/home/f/coding/flower/api_v1/external_product_image_backfill.py)
|
||||
|
||||
也就是说,该修复更可能带来“部分后台任务性能特征变化”,而不是“开发环境或生产环境无法正确运行”。
|
||||
|
||||
## 对开发环境的影响判断
|
||||
|
||||
开发环境同样可以启用 `DISABLE_SERVER_SIDE_CURSORS=True`,不会导致系统无法运行。
|
||||
|
||||
主要影响仍然是:
|
||||
|
||||
- 大结果集遍历时的性能和内存行为可能更保守
|
||||
|
||||
但这不属于严重副作用。
|
||||
|
||||
## 结论
|
||||
|
||||
本次问题定性为:
|
||||
|
||||
- **生产环境数据库连接池代理与 Django server-side cursors 的兼容性问题**
|
||||
|
||||
本次修复决策为:
|
||||
|
||||
- **保留连接池**
|
||||
- **在 Django 数据库配置中启用 `DISABLE_SERVER_SIDE_CURSORS=True`**
|
||||
|
||||
该决策用于降低生产环境中命名游标相关错误的出现概率,并保持系统整体连接池架构不变。
|
||||
Reference in New Issue
Block a user