
点击蓝字关注我们

pgcat 和 EDB EPAS
$ git clone https://github.com/postgresml/pgcat.git$ cd pgcat
pgcat包默认会创建一个postgresql,本例我只需要pgcat,不需要创建postgresql,所以修改docker-compose.yml如下:
$ cat docker-compose.ymlversion: "3"services:pgcat:build: .network_mode: host ## epas和pgcat在同一主机,所以配置docker使用host网络command:- "pgcat"- "/etc/pgcat/pgcat.toml"volumes:- "${PWD}/examples/docker/pgcat.toml:/etc/pgcat/pgcat.toml"ports:- "9930:9930"- "6432:6432"
pgcat需要一个配置文件,但仓库中自带的配置文件涉及太多功能,这里为了演示,简化配置。docker启动时会读./examples/docker/pgcat.toml配置文件内容,pool_mode默认transaction事务模式内容如下:
$ cat examples/docker/pgcat.toml[general]port = 6432idle_timeout = 60000worker_threads = 8wait_time_before_ban = 0admin_port = 9930admin_username = "admin"admin_password = "admin"[pools.edb]# shard 0[pools.edb.shards.0]database = "edb"[[pools.edb.shards.0.servers]]host = "host.containers.internal"port = 5444role = "primary"[[pools.edb.databases]]database = "edb"shard = 0[pools.edb.users.0]username = "enterprisedb"password = "enterprisedb"pool_size = 1
告诉pgcat如何连接以及在哪里连接,并且pgcat本身应该在6432端口运行。本文后续部分唯一会调整的配置就是pool_size。
启动docker容器pgcat:
$ sudo docker compose up
如何不出意外,这时pgcat容器应该正常启动,终端显示一些信息:
[pgcat] | 2025-12-08T02:46:51.928418Z INFO ThreadId(01) pgcat::pool: [pool: edb][user: enterprisedb] creating new pool[pgcat] | 2025-12-08T02:46:51.928468Z INFO ThreadId(01) pgcat: Config autoreloader: disabled[pgcat] | 2025-12-08T02:46:51.928481Z INFO ThreadId(01) pgcat: Waiting for clients[pgcat] | 2025-12-08T02:46:51.928637Z INFO ThreadId(04) pgcat::pool: Creating a new server connection Address { id: 0, host: "host.containers.internal", port: 5444, shard: 0, database: "edb", role: Primary, replica_number: 0, address_index: 0, username: "enterprisedb", pool_name: "edb", mirrors: [], stats: AddressStats { total: AddressStatFields { xact_count: 0, query_count: 0, bytes_received: 0, bytes_sent: 0, xact_time: 0, query_time: 0, wait_time: 0, errors: 0 }, current: AddressStatFields { xact_count: 0, query_count: 0, bytes_received: 0, bytes_sent: 0, xact_time: 0, query_time: 0, wait_time: 0, errors: 0 }, averages: AddressStatFields { xact_count: 0, query_count: 0, bytes_received: 0, bytes_sent: 0, xact_time: 0, query_time: 0, wait_time: 0, errors: 0 }, averages_updated: false }, error_count: 0 }[pgcat] | 2025-12-08T02:46:51.928855Z INFO ThreadId(06) pgcat::stats: Events reporter started[pgcat] | 2025-12-08T02:47:19.895497Z WARN ThreadId(05) pgcat: Client disconnected with error ClientSocketError("password code", ClientIdentifier { application_name: "psql", username: "enterprisedb", pool_name: "edb" })[pgcat] | 2025-12-08T02:48:21.931056Z INFO ThreadId(05) pgcat::server: Server connection closed Address { id: 0, host: "host.containers.internal", port: 5444, shard: 0, database: "edb", role: Primary, replica_number: 0, address_index: 0, username: "enterprisedb", pool_name: "edb", mirrors: [], stats: AddressStats { total: AddressStatFields { xact_count: 0, query_count: 0, bytes_received: 0, bytes_sent: 0, xact_time: 0, query_time: 0, wait_time: 0, errors: 0 }, current: AddressStatFields { xact_count: 0, query_count: 0, bytes_received: 0, bytes_sent: 0, xact_time: 0, query_time: 0, wait_time: 0, errors: 0 }, averages: AddressStatFields { xact_count: 0, query_count: 0, bytes_received: 0, bytes_sent: 0, xact_time: 0, query_time: 0, wait_time: 0, errors: 0 }, averages_updated: false }, error_count: 0 }, session duration: 0d 00:01:29.990[nuser@epas-1 ~]$ sudo docker psEmulate Docker CLI using podman. Create etc/containers/nodocker to quiet msg.CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESaee70d4a1cd9 localhost/pgcat_pgcat:latest pgcat etc/pgcat/... 8 hours ago Up 5 minutes pgcat_pgcat_1

共享的理解
现在EPAS运行着,pgcat也运行着。用psql连接到pgcat:
$ [nuser@epas-1 ~]$ psql -h 192.168.31.101 -U enterprisedb -p 6432 edb⽤户 enterprisedb 的⼝令:psql (17.6.0)输⼊ "help" 来获取帮助信息.edb=#edb=#edb=#
pgcat正在拦截客户端(psql)和epas客户端后端之间的所有交互。但从客户端本身很难判断连接的是池化工具还是直接连接到epas。
编辑这个psql会话的提示符,以便在添加更多连接时更容易区分:
edb=# \set PROMPT1 '[client1] %/%R%x%# '[client1] edb=# \set PROMPT2 '[client1] %/%R%x%# '[client1] edb=#
现在运行一个简单的查询:
[client1] edb=# SELECT 'hello world';?column?-------------hello world(1 row)
检查一下查询实际在哪个PostgreSQL客户端后端PID上执行:
[client1] edb=# SELECT pg_backend_pid();pg_backend_pid----------------89583(1 row)
现在做一些会话本地的事情,比如修改时区。假设在纽约,所以时区是Asia/Shanghai:
[client1] edb=# SHOW timezone;TimeZone------------------Asia/Shanghai(1 row)
现在把它改成America/Los_Angeles:
[client1] edb=# SET timezone TO 'America/Los_Angeles';SET[client1] edb=# SHOW timezone;TimeZone---------------------America/Los_Angeles(1 row)
保持这个psql会话打开,在另一个终端中再打开一个psql会话:
$ psql -h localhost -U enterprisedb -p 6432 edb⽤户 enterprisedb 的⼝令:psql (17.6.0)输⼊ "help" 来获取帮助信息.edb=#
也设置一下这个会话的提示符以便区分:
edb=# \set PROMPT1 '[client2] %/%R%x%# '[client2] edb=# \set PROMPT2 '[client2] %/%R%x%# '[client2] edb=#
在client2中时区应该是Asia/Shanghai,因为这是默认值:
[client2] postgres=# SHOW timezone;TimeZone------------------Asia/Shanghai(1 row)
现在检查epas客户端后端PID:
[client2] edb=# SELECT pg_backend_pid();pg_backend_pid----------------89583(1 row)
在EPAS/PostgreSQL中,如果直接连接到数据库,这些PID应该是唯一的。一个客户端对应一个进程。但现在看到多个客户端之间共享同一个进程。
回到client1检查一下timezone设置,确保它没有被重置:
[client1] postgres=# SHOW timezone;TimeZone---------------------America/Los_Angeles(1 row)
仍然是它自己的值。因此,池化工具看起来好像每个客户端都有自己的会话,就像每个客户端都有自己独立的EPAS/PostgreSQL客户端后端一样,即使有多个客户端共享同一个EPAS/PostgreSQL客户端后端。
Pool_size=1
这些池化工具的基本工作原理是维护一个打开的EPAS客户端后端连接池。在事务池化模式下,它会从池中获取一个客户端后端连接,并在事务结束时释放它。EPAS中的每个语句都是一个事务,因此每个客户端仅在语句执行期间持有池化连接。
如果一个客户端运行一个执行时间较长的语句,它可能会让其他客户端无法访问池化连接。当设置pool_size = 1时,这种情况最容易观察到,因为只允许池化工具拥有单个打开的epas客户端后端。
例如,在client2上调用pg_sleep()15秒。它会挂起15秒才会返回:
[client2] edb=# SELECT pg_sleep(15);pg_sleep----------(1 row)
在client2上按下Enter键后,立即在client1上运行SELECT 1。client1会挂起几秒钟,然后返回错误:
[client1] edb=# SELECT 1;FATAL: could not get connection from the pool - AllServersDown
但一旦client2的sleep完成并且语句执行完毕,client1就能够再次运行命令:
[client1] edb=# SELECT 1;FATAL: could not get connection from the pool - AllServersDown-- 稍等片刻后 --[client1] edb=# SELECT 1;?column?----------1(1 row)
说明此时在共享同一个PostgreSQL客户端后端!
接下来来试试交互式事务。
在client1上用一个BEGIN启动交互式事务:
[client1] edb=# BEGIN;BEGIN[client1] edb=*#
在client2上尝试运行SELECT 1:
[client2] edb=# select 1;FATAL: could not get connection from the pool - AllServersDown
它挂起并失败了。然后在client1的事务中运行一个查询,然后提交:
[client1] edb=# BEGIN;BEGIN[client1] edb=*# SELECT 1;?column?----------1(1 row)[client1] edb=*# COMMIT;COMMIT
现在回到client2,一旦client1的事务完成,就可以再次访问PostgreSQL客户端后端:
[client2] edb=# SELECT 1;FATAL: could not get connection from the pool - AllServersDown-- client1提交后[client2] edb=# SELECT 1;?column?----------1(1 row)
所以当只允许一个EPAS/PostgreSQL客户端后端时,可以观察到这种行为。现在2个客户端后端看看会发生什么。
pool_size = 2
将pool_size从1改为2。然后Ctrl-c停止pgcat服务器并重新运行它。关闭两个psql会话并启动两个新的会话。给它们同样的提示符前缀[client1]和[client2]。
查看client1当前的EPAS客户端后端PID:
$ [nuser@epas-1 ~]$ psql -h 192.168.31.101 -U enterprisedb -p 6432 edb⽤户 enterprisedb 的⼝令:psql (17.6.0)输⼊ "help" 来获取帮助信息.edb=# \set PROMPT1 '[client1] %/%R%x%# '[client1] edb=# \set PROMPT2 '[client1] %/%R%x%# '[client1] edb=#[client1] edb=# SELECT pg_backend_pid();pg_backend_pid----------------91137(1 row)
对client2做同样的操作:
$ psql -h 192.168.31.101 -U enterprisedb -p 6432 edb⽤户 enterprisedb 的⼝令:psql (17.6.0)输⼊ "help" 来获取帮助信息.edb=# \set PROMPT1 '[client2] %/%R%x%# '[client2] edb=# \set PROMPT2 '[client2] %/%R%x%# '[client2] edb=# SELECT pg_backend_pid();pg_backend_pid----------------91137(1 row)
可以看出,即使允许pgcat使用多个PostgreSQL客户端后端,它仍然只使用一个。
如果现在有一个阻塞的客户端连接,如果在client2上启动一个交互式事务:
[client2] edb=# BEGIN;BEGIN[client2] edb=*#
看看它的客户端后端PID是什么:
[client2] edb=# BEGIN;BEGIN[client2] edb=*# SELECT pg_backend_pid();pg_backend_pid----------------91137(1 row)
还是和之前一样的那个。让这个事务保持打开状态。
然后在client1上执行一个简单的SELECT 1:
[client1] edb=# SELECT 1;?column?----------1(1 row)
看看client1运行在哪个客户端后端PID上:
[client1] edb=# SELECT pg_backend_pid();pg_backend_pid----------------91250(1 row)
和之前的不同!因此,由于一个客户端阻塞了连接的使用,而pool_size允许使用更多连接,pgcat就打开了一个新的EPAS客户端后端供客户端连接使用。
现在提交client2上的事务,并检查它在该事务之后的EPAS客户端后端PID:
[client2] postgres=# BEGIN;BEGIN[client2] postgres=*# SELECT pg_backend_pid();pg_backend_pid----------------20809(1 row)[client2] postgres=*# COMMIT;COMMIT[client2] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91250(1 row)
再检查一次。再一次。再一次。
[client2] postgres=# [client2] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91137(1 row)[client2] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91250(1 row)[client2] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91137(1 row)
client1也一样:
[client1] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91250(1 row)[client1] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91137(1 row)[client1] postgres=# SELECT pg_backend_pid();pg_backend_pid----------------91250(1 row)
所以基本上,逻辑是客户端会随机地从现有连接池中获取连接,除非所有现有连接都被阻塞,否则不会创建新的连接。每个客户端可能在每次事务中都运行在完全不同的PostgreSQL客户端后端上。
会话池化
连接池工具可能支持几种池化模式,session会话模式,transaction事务模式和statement语句模式。
在会话池化模式下,每个客户端连接都映射到一个EPAS/PostgreSQL客户端后端。就像直接连接到EPAS/PostgreSQL一样。这意味着像LISTEN/NOTIFY这样的功能可以毫无问题地工作。只是当客户端连接结束后,池化工具不会让EPAS/PostgreSQL客户端后端退出,而是保留它以便将来客户端连接复用。
会话池化有助于避免建立连接的延迟,同时可以帮助扩展客户端连接数量,因为池化工具会将连接排队到可配置的超时时间,而PostgreSQL一旦达到max_connections[1]就会立即断开连接。对于扩展客户端连接数量的效果不如事务池化那么显著。
基本上,池化工具默认使用事务池化,因为这种模式可以处理更多连接。当用户需要事务池化不支持的功能或行为时,就会切换到会话池化。
关于会话池化,不在些做验证,读者可以自行验证学习。
引用链接
[1]max_connections:https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-MAX-CONNECTIONS
关于公司
感谢您关注新智锦绣科技(北京)有限公司!作为 Elastic 的 Elite 合作伙伴及 EnterpriseDB 在国内的唯一代理和服务合作伙伴,我们始终致力于技术创新和优质服务,帮助企业客户实现数据平台的高效构建与智能化管理。无论您是关注 Elastic 生态系统,还是需要 EnterpriseDB 的支持,我们都将为您提供专业的技术支持和量身定制的解决方案。
欢迎关注我们,获取更多技术资讯和数字化转型方案,共创美好未来!
![]() | ![]() |
Elastic 微信群 | EDB 微信群 |

发现“分享”和“赞”了吗,戳我看看吧






