Django使用pgpool2时unable to read data简单处理方案

最近发现用Django写的某项目时不时会有报错发生,我们用的是postgresql+pgpool2的组合,日志显示报错信息如下:

1
2
3
4
5
psycopg2.OperationalError: unable to read data
DETAIL: child connection forced to terminate due to client_idle_limit:10 is reached
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.

从报错日志来看,是由于pgpool2的client_idle_limit参数设置得太短,设置成了10秒,自动将10秒不活动的客户端连接给关闭了,但Django这边并没有感知数据库连接已经断开,还在继续使用数据库连接,导致了这个报错。

在我们的项目中,这种报错通常会出现在一些异步的定时任务中,例如一个celery的异步任务,先连接了一次数据库,获取了一些信息,执行一些时间较长的任务(可能会超过10秒),然后再次连接数据库,由于我们直接使用模型,Django自己维护了数据库连接,这个时候就会出现上文描述的错误了。

由于公司规范的问题,我们无法调整pgpool2的client_idle_limit配置。虽然Django的数据库配置有CONN_MAX_AGE这个参数,但在我们这个情况下这个参数也是无效的,关键问题在于Django未能感知pgpool2的连接池断开了。

网上一顿查询后发现个简单的解决方案:在再次使用数据库前,手动断开Django的数据库连接,让Django重新连接数据库。代码如下:

1
2
3
from django import db

db.connections.close_all()

下面用一个简单的示例来测试这样修改是否有效果:

先创建一个模型用于数据写入:

1
2
3
4
5
6
7
8
9
10
11
"""
@author:knktc
@create:2019-12-16 09:43
"""
from django.db import models

class TestPgpool(models.Model):
secs = models.IntegerField(help_text='sleep seconds')
status = models.CharField(max_length=16, help_text='set start when test starts and set end when test ends')
create_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)

再写一个简单的view用于测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""
@author:knktc
@create:2019-12-16 09:44
"""

import time
from .models import TestPgpool
from django.http import JsonResponse
from django import db

def test_pgpool(request):
""" test write data after a sleep time """
secs = int(request.GET.get('secs'))

# create new object
obj = TestPgpool(secs=secs, status='start')
obj.save()

# close connection manually
db.connections.close_all()

# sleep sometime
time.sleep(secs)

# use the object again
obj.status = 'end'
obj.save()

return JsonResponse({'status_code': 0, 'sleep_secs': secs, 'id': obj.id})

这个view接受一个GET参数secs,用于指定两次读取数据库的间隔(通过sleep实现)。如果未手动执行关闭数据库的操作,secs超过client_idle_limit设置后就会报错。如果手动关闭了数据库,则不会出现报错。

需要说明的是,这只是个简单的解决方案。如果需要完美地解决问题,则需要修改下Django维护数据库连接的方式,能让Django感知pgpool已断开连接,或是自动进行数据库连接重试。

坚持原创技术分享,您的支持将鼓励我继续创作!