PG:prepared statement `1` already exist问题推敲

这里记录一个没有搞定的问题。问题的起因是由于生产环境pg更改表主键类型导致(int->bigint)机器io负载很高,pgbouncer连接pg server超时,然后上层掉用方过来找才发现。当时果断停止修改表结构,重启pgbouncer释放已有连接使pg回收相关session资源。同时我们维护的推送中间层服务也重启(实际不需要重启,当时报出大量的pgbouncer连接不上的错误,只要pgbouncer恢复接受连接,sql.go中的连接池会自动丢弃bad connnection,生成新的)。重启后,发现只有一个机房的集群都启动成功,并正常开始工作,另一个机房推送服务都启动不了。

单机房服务启动不了的问题比较简单,运行bin会panic,原因是程序defer stmt.Close()放在了错误的位置导致npe。值得注意的是db.Prepare这个语句的报错prepared statement 1 already exist。这个错误分几种情况讨论:

  • app直连db,db.Prepare会通知pg server建立语句name和语句临时对象的映射关系方便重用,这个name是需要全局唯一的,即不同连接使用相同stmt name也会报错,这可以认为是直连的情况下,pg server认为是当前global是一个session。
  • app通过pgbouncer连接db,pgbouncer维护与两端的连接池,本身的作用就是连接池的横向扩展,因为如果app单纯使用sdk,连接池依赖于本地服务器的能力,抽象出一层,一般都是扩展用的。
  • app和pgbouncer中间再增加一层ELB,这个才使pgbouncer具备扩展的能力,但也会复杂化访问流程。ELB是通用负载均衡组件,所以它仅仅是把请求吞下来并给后面相应的负载能力足够的ip:port上,所以当处理一些有上下文关系的请求时可能存在问题。例如,db.Prepare之后,需要使用同一个conn发送接下来的Exec请求(lib/pq中是这么处理的会找之前发送Prepare请求的conn)。

基于上述推敲,遇到already exists问题,pgbouncer如果没有重启,会持续报出错误,因为lib/pq中的conn对于stmt的计数都是从0开始。

PG:prepared statement `1` already exist问题推敲
Share this