Django 連接池配置與調優完全指南

從 CONN_MAX_AGE 到 PgBouncer,掌握生產環境的最佳配置實踐

連接池配置不當可能導致「連接不足」或「資源浪費」。如何計算合適的池大小?CONN_MAX_AGE 應該設多少秒?PgBouncer 的三種池模式該如何選擇?

本文將提供完整的連接池配置指南,包括參數計算公式、不同場景的推薦配置,以及生產環境的調優實踐。

🎯 連接池配置的核心問題

在配置連接池前,需要回答三個關鍵問題:

1. 需要多少個連接?
   - 太少:高並發時連接不足,請求等待
   - 太多:浪費數據庫資源,內存佔用高

2. 連接保持多久?
   - 太短:頻繁建立/關閉,性能下降
   - 太長:無法及時釋放,連接洩漏風險

3. 如何處理連接異常?
   - 連接斷開怎麼辦?
   - 數據庫重啟如何恢復?
   - 超時如何處理?

⚙️ Django CONN_MAX_AGE 配置

基礎配置

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'postgres',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '5432',

        # 連接配置
        'CONN_MAX_AGE': 600,  # 連接保持 600 秒(10 分鐘)

        # 連接選項
        'OPTIONS': {
            'connect_timeout': 10,  # 連接超時 10 秒
            'options': '-c statement_timeout=30000',  # SQL 超時 30 秒
        },
    }
}

CONN_MAX_AGE 推薦值

開發環境:
  CONN_MAX_AGE: 0
  原因:每次修改代碼重啟,連接池沒意義

測試環境:
  CONN_MAX_AGE: 60  # 1 分鐘
  原因:測試期間流量低,短時間連接即可

生產環境(低流量):
  CONN_MAX_AGE: 600  # 10 分鐘
  原因:流量較低,長時間保持連接

生產環境(高流量):
  CONN_MAX_AGE: None  # 永久連接
  原因:持續高流量,連接始終被使用
  ⚠️ 風險:需要監控連接狀態,防止洩漏

計算連接數

使用 CONN_MAX_AGE 時,連接數計算:

# 最小連接數
min_connections = Worker 數量 × 數據庫數量

# 範例:
# - Gunicorn 4 Workers
# - 2 個數據庫(default + cache)
min_connections = 4 × 2 = 8
# 實際連接數(高並發時)
# 如果 Worker 內有並發查詢,可能創建多個連接
actual_connections = Workers × Databases × 併發查詢數

容量規劃:

場景 1:單數據庫,4 Workers
  最小連接:4 個
  建議數據庫 max_connections:20-50

場景 2:3 個數據庫,8 Workers
  最小連接:24 個
  建議數據庫 max_connections:50-100

場景 3:水平擴展(2 台服務器,各 4 Workers)
  最小連接:8 個(每台 4 個)
  總連接數:8 個
  建議數據庫 max_connections:50+

🔧 django-db-connection-pool 配置

安裝與配置

pip install django-db-connection-pool
# settings.py
DATABASES = {
    'default': {
        # 替換 ENGINE
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',

        'NAME': 'mydb',
        'USER': 'postgres',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '5432',

        # 連接池配置
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,        # 核心連接池大小
            'MAX_OVERFLOW': 5,      # 最大溢出連接數
            'POOL_TIMEOUT': 30,     # 獲取連接超時(秒)
            'POOL_RECYCLE': 3600,   # 連接回收時間(秒)
            'POOL_PRE_PING': True,  # 借用前 ping 測試
        }
    }
}

參數詳解

POOL_SIZE(核心池大小):
  定義:預先建立並保持的連接數
  計算公式:POOL_SIZE = Workers × 1.5
  範例:
    - 4 Workers → POOL_SIZE = 6
    - 8 Workers → POOL_SIZE = 12

MAX_OVERFLOW(溢出連接數):
  定義:高峰時可額外創建的臨時連接數
  計算公式:MAX_OVERFLOW = POOL_SIZE × 0.5
  範例:
    - POOL_SIZE = 10 → MAX_OVERFLOW = 5

  最大連接數 = POOL_SIZE + MAX_OVERFLOW

POOL_TIMEOUT(獲取超時):
  定義:等待連接可用的最大時間
  推薦值:
    - API 服務:10-30 秒
    - 後台任務:60-300 秒
  說明:超時後拋出 TimeoutError

POOL_RECYCLE(連接回收):
  定義:連接使用多久後自動回收重建
  推薦值:3600 秒(1 小時)
  原因:
    - 防止長時間連接失效
    - 應對數據庫防火牆超時設置
    - 避免連接狀態異常

POOL_PRE_PING(借用前 ping):
  定義:借用連接前先測試連接是否存活
  推薦值:True
  開銷:每次借用增加 ~1ms,但避免使用失效連接

池大小計算公式

# 方法 1:基於 QPS 計算
POOL_SIZE = (QPS × 平均查詢時間) / 1000

# 範例:
# - QPS = 1000
# - 平均查詢時間 = 10ms
POOL_SIZE = (1000 × 10) / 1000 = 10

# 方法 2:基於並發數計算
POOL_SIZE = 預期並發數 × 1.2

# 範例:
# - 預期同時處理 20 個請求
POOL_SIZE = 20 × 1.2 = 24

# 方法 3:基於 Worker 數量(保守)
POOL_SIZE = Workers × 2

# 範例:
# - 8 個 Workers
POOL_SIZE = 8 × 2 = 16

不同場景的推薦配置

# 場景 1:低流量 API(< 100 QPS)
DATABASES = {
    'default': {
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',
        'POOL_OPTIONS': {
            'POOL_SIZE': 5,
            'MAX_OVERFLOW': 2,
            'POOL_TIMEOUT': 30,
            'POOL_RECYCLE': 3600,
        }
    }
}

# 場景 2:中流量 API(100-1000 QPS)
DATABASES = {
    'default': {
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',
        'POOL_OPTIONS': {
            'POOL_SIZE': 15,
            'MAX_OVERFLOW': 10,
            'POOL_TIMEOUT': 20,
            'POOL_RECYCLE': 3600,
        }
    }
}

# 場景 3:高流量 API(> 1000 QPS)
DATABASES = {
    'default': {
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',
        'POOL_OPTIONS': {
            'POOL_SIZE': 30,
            'MAX_OVERFLOW': 15,
            'POOL_TIMEOUT': 10,
            'POOL_RECYCLE': 1800,
        }
    }
}

🚀 PgBouncer 配置(生產級)

安裝

# Ubuntu/Debian
sudo apt install pgbouncer

# macOS
brew install pgbouncer

# CentOS/RHEL
sudo yum install pgbouncer

核心配置文件

# /etc/pgbouncer/pgbouncer.ini

[databases]
# 格式:alias = host=... port=... dbname=...
mydb = host=localhost port=5432 dbname=mydb
mydb_readonly = host=replica.example.com port=5432 dbname=mydb

[pgbouncer]
# 監聽設置
listen_addr = 127.0.0.1
listen_port = 6432

# 認證
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

# 連接池模式(重要!)
pool_mode = transaction

# 連接池大小
default_pool_size = 20          # 每個數據庫的默認連接數
min_pool_size = 5               # 最小保持連接數
reserve_pool_size = 5           # 保留連接數(應急)
max_client_conn = 1000          # 最大客戶端連接數
max_db_connections = 50         # 單個數據庫最大連接數

# 超時設置
server_idle_timeout = 600       # 伺服器連接空閒超時(秒)
server_lifetime = 3600          # 伺服器連接最大生存時間(秒)
server_connect_timeout = 15     # 連接數據庫超時(秒)
query_timeout = 0               # 查詢超時(0 = 無限制)

# 日誌
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1

認證文件

# /etc/pgbouncer/userlist.txt
"postgres" "md5a7b3c4d5e6f7g8h9i0j1k2l3m4n5o6"
"django_user" "md5..."

生成密碼 hash:

# PostgreSQL 密碼 hash
echo -n "passwordusername" | md5sum
# 前面加上 "md5" 前綴

池模式選擇

1. session(會話池):
   pool_mode = session

   特性:
     - 連接在整個客戶端會話期間保持
     - 客戶端斷開後,連接歸還池

   適合:
     - 需要使用臨時表
     - 需要 SET 變量在會話內生效
     - 傳統應用遷移

   Django 兼容性:✅ 完全兼容
   連接復用率:低

2. transaction(事務池,推薦):
   pool_mode = transaction

   特性:
     - 連接在事務結束後立即歸還
     - 同一客戶端的不同事務可能使用不同連接

   適合:
     - 高並發 Web 應用
     - 無狀態查詢
     - Django ORM(默認行為)

   Django 兼容性:✅ 兼容(不使用臨時表)
   連接復用率:高(推薦)

3. statement(語句池):
   pool_mode = statement

   特性:
     - 每條 SQL 執行後立即歸還連接
     - 無法使用事務、預處理語句

   Django 兼容性:❌ 不兼容(Django 依賴事務)
   連接復用率:極高(但 Django 不可用)

池大小計算

# PgBouncer 的 default_pool_size 計算

# 方法 1:基於應用服務器數量
default_pool_size = 應用服務器數量 × 2

# 範例:3 台服務器,各 4 Workers
default_pool_size = 3 × 2 = 6

# 方法 2:基於預期並發
default_pool_size = 預期數據庫並發查詢數 × 1.5

# 範例:預期同時 10 個查詢
default_pool_size = 10 × 1.5 = 15

# max_client_conn(客戶端連接數)
max_client_conn = 應用服務器總 Workers 數 × 2

# 範例:3 台服務器 × 4 Workers = 12
max_client_conn = 12 × 2 = 24

推薦配置(不同規模)

# 小型應用(1-2 台服務器,< 100 QPS)
[pgbouncer]
pool_mode = transaction
default_pool_size = 10
max_client_conn = 100
max_db_connections = 20

# 中型應用(3-5 台服務器,100-1000 QPS)
[pgbouncer]
pool_mode = transaction
default_pool_size = 20
max_client_conn = 500
max_db_connections = 50

# 大型應用(> 5 台服務器,> 1000 QPS)
[pgbouncer]
pool_mode = transaction
default_pool_size = 30
max_client_conn = 2000
max_db_connections = 100

Django 配置(連接 PgBouncer)

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'postgres',
        'PASSWORD': 'password',
        'HOST': '127.0.0.1',
        'PORT': '6432',  # ← PgBouncer 端口,不是 5432

        # ⚠️ 重要:不要設置 CONN_MAX_AGE
        # 讓 PgBouncer 管理連接生命週期
        'CONN_MAX_AGE': None,
    }
}

📊 性能監控與調優

監控指標

需要監控的關鍵指標:

1. 連接池使用率:
   - 當前使用連接數 / 池大小
   - 目標:60-80%
   - 過低:池太大,浪費資源
   - 過高:池太小,可能不足

2. 等待連接次數:
   - 請求等待可用連接的次數
   - 目標:0 或接近 0
   - 過高:需要增加池大小

3. 連接超時錯誤:
   - POOL_TIMEOUT 超時次數
   - 目標:0
   - 發生:立即增加池大小或優化查詢

4. 連接創建/銷毀頻率:
   - 溢出連接的創建頻率
   - 目標:偶爾發生
   - 頻繁:POOL_SIZE 設置過小

Django ORM 監控

# 查看當前連接狀態
from django.db import connection

# 連接信息
print(f"連接 ID: {id(connection.connection)}")
print(f"連接是否打開: {connection.connection is not None}")

# 查詢統計
from django.db import reset_queries
from django.conf import settings

settings.DEBUG = True  # 開啟查詢記錄
reset_queries()

# 執行一些查詢
User.objects.all()
Product.objects.filter(price__gt=100)

# 查看查詢
from django.db import connection
print(f"總查詢數: {len(connection.queries)}")
for query in connection.queries:
    print(f"SQL: {query['sql']}")
    print(f"耗時: {query['time']}s")

PgBouncer 監控

-- 連接 PgBouncer 管理界面
psql -h 127.0.0.1 -p 6432 -U postgres pgbouncer

-- 查看連接池狀態
SHOW POOLS;

-- 範例輸出:
 database |   user   | cl_active | cl_waiting | sv_active | sv_idle | sv_used
----------+----------+-----------+------------+-----------+---------+---------
 mydb     | postgres |        15 |          0 |        10 |       5 |      10

-- 解釋:
-- cl_active:  客戶端活躍連接
-- cl_waiting: 等待連接的客戶端
-- sv_active:  數據庫活躍連接
-- sv_idle:    數據庫空閒連接
-- sv_used:    數據庫已使用連接

-- 查看統計信息
SHOW STATS;

-- 查看配置
SHOW CONFIG;

-- 重新加載配置(不中斷連接)
RELOAD;

🎯 調優實戰案例

案例 1:連接不足

症狀:

TimeoutError: QueuePool limit of size 10 overflow 5 reached

診斷:

# 連接池使用率 = 100%
# 等待連接次數:頻繁
# 請求響應時間:增加

解決方案:

# 增加池大小
'POOL_OPTIONS': {
    'POOL_SIZE': 15,  # 10 → 15
    'MAX_OVERFLOW': 10,  # 5 → 10
}

案例 2:連接浪費

症狀:

數據庫連接數:50
實際使用率:20%

診斷:

# 連接池配置過大
# 大量空閒連接

解決方案:

# 減小池大小
'POOL_OPTIONS': {
    'POOL_SIZE': 10,  # 20 → 10
    'MAX_OVERFLOW': 5,  # 10 → 5
}

案例 3:連接頻繁創建/銷毀

症狀:

溢出連接創建頻率:每秒 10+次

診斷:

# 核心池太小
# 高峰時頻繁創建臨時連接

解決方案:

# 增加核心池,減少溢出池
'POOL_OPTIONS': {
    'POOL_SIZE': 20,  # 10 → 20
    'MAX_OVERFLOW': 5,  # 10 → 5
}

📋 配置檢查清單

上線前檢查

✅ 連接池配置:
  - [ ] POOL_SIZE 根據 QPS 計算
  - [ ] MAX_OVERFLOW 設置合理
  - [ ] POOL_TIMEOUT 不要太短
  - [ ] POOL_RECYCLE 設置為 3600 秒
  - [ ] POOL_PRE_PING 開啟

✅ 數據庫配置:
  - [ ] max_connections 大於池大小
  - [ ] 數據庫 timeout 設置合理
  - [ ] 索引優化完成

✅ 監控配置:
  - [ ] 連接池使用率監控
  - [ ] 等待連接次數監控
  - [ ] 超時錯誤告警
  - [ ] 慢查詢日誌

✅ 壓力測試:
  - [ ] 模擬高並發場景
  - [ ] 驗證連接池不會耗盡
  - [ ] 確認無連接洩漏

🎯 最佳實踐總結

1. 選擇合適的方案:
   小型應用: CONN_MAX_AGE = 600
   中型應用: django-db-connection-pool
   大型應用: PgBouncer(transaction 模式)

2. 池大小計算:
   保守估算: Workers × 2
   精確計算: (QPS × 平均查詢時間) / 1000

3. 監控與調優:
   目標使用率: 60-80%
   等待次數: 接近 0
   定期審查: 每月檢查配置

4. 安全設置:
   POOL_PRE_PING: True(檢查連接)
   POOL_RECYCLE: 3600(定期回收)
   POOL_TIMEOUT: 30(避免無限等待)

下一篇將詳細講解連接洩漏問題的排查與解決方法。

0%