postgres_fdw 和 PgBouncer transaction 模式兼容性问题
使用 postgres_fdw 时从远程 Postgres 数据库切换到 PgBouncer 连接池的 transaction 模式时,绝大部分数据库查询都能正常工作,但在某些情况下会遇到错误,提示类似如下信息:
relation "user" does not exist
这个错误比较奇怪,因为无论切换前后数据库结构是一致的,更加奇怪的是这个错误并不是每次查询都会出现,而是偶尔出现,概率不足 1%。比较可疑的原因是 search_path 设置的问题。在相关错误查询后面增加打印当前数据库连接信息的 SQL 语句执行结果:
SELECT
current_database() AS db,
current_user AS user,
session_user AS session_user,
inet_server_addr()::text AS server_addr,
inet_server_port() AS server_port,
current_schema() AS cur_schema,
current_schemas(true) AS all_schemas,
current_setting('search_path', true) AS search_path,
version() AS pg_version;
得到如下日志
debug_result {'cur_schema': 'pg_catalog', 'all_schemas': ['pg_catalog'], 'search_path': 'pg_catalog', 'pg_version': 'PostgreSQL ***'}
debug_result {'cur_schema': 'public', 'all_schemas': ['pg_catalog', 'public'], 'search_path': '"$user", public', 'pg_version': 'PostgreSQL ***'}
上面的日志清晰的表明了问题所在:连接的 search_path 被设置为了 pg_catalog,而不是预期的 public。
查看 postgres_fdw 的源代码,发现在这个位置强制设置了使用 pg_catalog 作为 search_path:
https://github.com/postgres/postgres/blob/master/contrib/postgres_fdw/postgres_fdw.c#L3918-L3920
(void) set_config_option("search_path", "pg_catalog",
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
这样问题就清晰了:在 PgBouncer 的 transaction 模式下,连接池会复用数据库连接,而 postgres_fdw 在使用时会尝试将 search_path 设置为 pg_catalog,导致后续的查询无法找到正确的表,因为 postgres_fdw 的表使用概率非常低,所以这个错误也就偶尔出现。