[PATCH] postgres_fdw connection caching - cause remote sessions linger till the local session exit
Hi,
When a query on foreign table is executed from a local session using
postgres_fdw, as expected the local postgres backend opens a
connection which causes a remote session/backend to be opened on the
remote postgres server for query execution.
One observation is that, even after the query is finished, the remote
session/backend still persists on the remote postgres server. Upon
researching, I found that there is a concept of Connection Caching for
the remote connections made using postgres_fdw. Local backend/session
can cache up to 8 different connections per backend. This caching is
useful as it avoids the cost of reestablishing new connections per
foreign query.
However, at times, there may be situations where the long lasting
local sessions may execute very few foreign queries and remaining all
are local queries, in this scenario, the remote sessions opened by the
local sessions/backends may not be useful as they remain idle and eat
up the remote server connections capacity. This problem gets even
worse(though this use case is a bit imaginary) if all of
max_connections(default 100 and each backend caching 8 remote
connections) local sessions open remote sessions and they are cached
in the local backend.
I propose to have a new session level GUC called
"enable_connectioncache"(name can be changed if it doesn't correctly
mean the purpose) with the default value being true which means that
all the remote connections are cached. If set to false, the
connections are not cached and so are remote sessions closed by the local
backend/session at
the end of each remote transaction.
Attached the initial patch(based on commit
9550ea3027aa4f290c998afd8836a927df40b09d), test setup.
Another approach to solve this problem could be that (based on Robert's
idea[1]/messages/by-id/CA+Tgmob_ksTOgmbXhno+k5XXPOK+-JYYLoU3MpXuutP4bH7gzA@mail.gmail.com) automatic clean up of cache entries, but letting users decide
on caching also seems to be good.
Please note that documentation is still pending.
Thoughts?
Test Case:
without patch:
1. Run the query on foreign table
2. Look for the backend/session opened on the remote postgres server, it
exists till the local session remains active.
with patch:
1. SET enable_connectioncache TO false;
2. Run the query on the foreign table
3. Look for the backend/session opened on the remote postgres server, it
should not exist.
[1]: /messages/by-id/CA+Tgmob_ksTOgmbXhno+k5XXPOK+-JYYLoU3MpXuutP4bH7gzA@mail.gmail.com
/messages/by-id/CA+Tgmob_ksTOgmbXhno+k5XXPOK+-JYYLoU3MpXuutP4bH7gzA@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v1-enable_connectioncache-GUC-for-postgres_fdw-connection-caching.patchapplication/x-patch; name=v1-enable_connectioncache-GUC-for-postgres_fdw-connection-caching.patchDownload
From d4594067b29ab1414e9741a7e6abd91daf078201 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 22 Jun 2020 10:07:53 +0530
Subject: [PATCH v1] enable_connectioncache GUC for postgres_fdw connection
caching
postgres_fdw connection caching - cause remote sessions linger
till the local session exit.
---
contrib/postgres_fdw/connection.c | 3 ++-
src/backend/utils/hash/dynahash.c | 11 +++++++++++
src/backend/utils/misc/guc.c | 15 +++++++++++++++
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/include/utils/hsearch.h | 4 ++++
5 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 52d1fe3563..de994f678b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -869,7 +869,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
- entry->changing_xact_state)
+ entry->changing_xact_state ||
+ scan.enableconncache == false)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index 2688e27726..a5448f4af4 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -276,6 +276,9 @@ static bool has_seq_scans(HTAB *hashp);
*/
static MemoryContext CurrentDynaHashCxt = NULL;
+/* parameter for enabling fdw connection hashing */
+bool enable_connectioncache = true;
+
static void *
DynaHashAlloc(Size size)
{
@@ -1383,6 +1386,14 @@ hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp)
status->hashp = hashp;
status->curBucket = 0;
status->curEntry = NULL;
+ status->enableconncache = true;
+
+ /* see if the cache was for postgres_fdw connections and
+ user chose to disable connection caching*/
+ if ((strcmp(hashp->tabname,"postgres_fdw connections") == 0) &&
+ !enable_connectioncache)
+ status->enableconncache = false;
+
if (!hashp->frozen)
register_seq_scan(hashp);
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 75fc6f11d6..f7a57415fc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -127,6 +127,7 @@ extern char *temp_tablespaces;
extern bool ignore_checksum_failure;
extern bool ignore_invalid_pages;
extern bool synchronize_seqscans;
+extern bool enable_connectioncache;
#ifdef TRACE_SYNCSCAN
extern bool trace_syncscan;
@@ -2041,6 +2042,20 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
+ {
+ {"enable_connectioncache", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Enables postgres_fdw connection caching."),
+ gettext_noop("The postgres_fdw connections are cached and "
+ "reused within the same session by default. "
+ "Setting this parameter to false, discards the "
+ "remote connection at the end of the transaction."),
+ GUC_EXPLAIN
+ },
+ &enable_connectioncache,
+ true,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3a25287a39..1a55177df2 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -184,7 +184,7 @@
#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
# (change requires restart)
#backend_flush_after = 0 # measured in pages, 0 disables
-
+#enable_connectioncache = on
#------------------------------------------------------------------------------
# WRITE-AHEAD LOG
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index f1deb9beab..77546a7c16 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -114,6 +114,10 @@ typedef struct
HTAB *hashp;
uint32 curBucket; /* index of current bucket */
HASHELEMENT *curEntry; /* current entry in bucket */
+ /* if true, fdw connections in a session are cached, else
+ discarded at the end of every remote transaction.
+ */
+ bool enableconncache;
} HASH_SEQ_STATUS;
/*
--
2.25.1
On Mon, Jun 22, 2020 at 11:26 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Hi,
When a query on foreign table is executed from a local session using
postgres_fdw, as expected the local postgres backend opens a
connection which causes a remote session/backend to be opened on the
remote postgres server for query execution.One observation is that, even after the query is finished, the remote
session/backend still persists on the remote postgres server. Upon
researching, I found that there is a concept of Connection Caching for
the remote connections made using postgres_fdw. Local backend/session
can cache up to 8 different connections per backend. This caching is
useful as it avoids the cost of reestablishing new connections per
foreign query.However, at times, there may be situations where the long lasting
local sessions may execute very few foreign queries and remaining all
are local queries, in this scenario, the remote sessions opened by the
local sessions/backends may not be useful as they remain idle and eat
up the remote server connections capacity. This problem gets even
worse(though this use case is a bit imaginary) if all of
max_connections(default 100 and each backend caching 8 remote
connections) local sessions open remote sessions and they are cached
in the local backend.I propose to have a new session level GUC called
"enable_connectioncache"(name can be changed if it doesn't correctly
mean the purpose) with the default value being true which means that
all the remote connections are cached. If set to false, the
connections are not cached and so are remote sessions closed by the local backend/session at
the end of each remote transaction.Attached the initial patch(based on commit
9550ea3027aa4f290c998afd8836a927df40b09d), test setup.
Few comments:
#backend_flush_after = 0 # measured in pages, 0 disables
-
+#enable_connectioncache = on
This guc could be placed in CONNECTIONS AND AUTHENTICATION section.
+
+ /* see if the cache was for postgres_fdw connections and
+ user chose to disable connection caching*/
+ if ((strcmp(hashp->tabname,"postgres_fdw connections") == 0) &&
+ !enable_connectioncache)
Should be changed to postgres style commenting like:
/*
* See if the cache was for postgres_fdw connections and
* user chose to disable connection caching.
*/
+ /* if true, fdw connections in a session are cached, else
+ discarded at the end of every remote transaction.
+ */
+ bool enableconncache;
Should be changed to postgres style commenting.
+/* parameter for enabling fdw connection hashing */
+bool enable_connectioncache = true;
+
Should this be connection caching?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Mon, Jun 22, 2020 at 11:26 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Attached the initial patch(based on commit
9550ea3027aa4f290c998afd8836a927df40b09d), test setup.
make check is failing
sysviews.out 2020-06-27 07:22:32.162146320 +0530
@@ -73,6 +73,7 @@
name | setting
--------------------------------+---------
enable_bitmapscan | on
+ enable_connectioncache | on
one of the test expect files needs to be updated.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Sun, Jun 21, 2020 at 10:56 PM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
When a query on foreign table is executed from a local session using
postgres_fdw, as expected the local postgres backend opens a
connection which causes a remote session/backend to be opened on the
remote postgres server for query execution.[...]
I propose to have a new session level GUC called
"enable_connectioncache"(name can be changed if it doesn't correctly
mean the purpose) with the default value being true which means that
all the remote connections are cached. If set to false, the
connections are not cached and so are remote sessions closed by the local
backend/session at
the end of each remote transaction.[...]
Thoughts?
Test Case:
without patch:
1. Run the query on foreign table
2. Look for the backend/session opened on the remote postgres server, it
exists till the local session remains active.with patch:
1. SET enable_connectioncache TO false;
2. Run the query on the foreign table
3. Look for the backend/session opened on the remote postgres server, it
should not exist.
If this is just going to apply to postgres_fdw why not just have that
module provide a function "disconnect_open_sessions()" or the like that
does this upon user command? I suppose there would be some potential value
to having this be set per-user but that wouldn't preclude the usefulness of
a function. And by having a function the usefulness of the GUC seems
reduced. On a related note is there any entanglement here with the
supplied dblink and/or dblink_fdw [1]The only place I see "dblink_fdw" in the documentation is in the dblink module's dblink_connect page. I would probably modify that page to say: "It is recommended to use the foreign-data wrapper dblink_fdw (installed by this module) when defining the foreign server." (adding the parenthetical). modules as they do provide connect
and disconnect functions and also leverages postgres_fdw (or dblink_fdw if
specified, which brings us back to the question of whether this option
should be respected by that FDW).
Otherwise, I would imagine that having multiple queries execute before
wanting to drop the connection would be desirable so at minimum a test case
that does something like:
SELECT count(*) FROM remote.tbl1;
-- connection still open
SET enable_connectioncache TO false;
SELECT count(*) FROM remote.tbl2;
-- now it was closed
Or maybe even better, have the close action happen on a transaction
boundary.
And if it doesn't just apply to postgres_fdw (or at least doesn't have to)
then the description text should be less specific.
David J.
[1]: The only place I see "dblink_fdw" in the documentation is in the dblink module's dblink_connect page. I would probably modify that page to say: "It is recommended to use the foreign-data wrapper dblink_fdw (installed by this module) when defining the foreign server." (adding the parenthetical).
module's dblink_connect page. I would probably modify that page to say:
"It is recommended to use the foreign-data wrapper dblink_fdw (installed by
this module) when defining the foreign server." (adding the parenthetical).
Thanks for the responses.
If this is just going to apply to postgres_fdw why not just have that module provide a function "disconnect_open_sessions()" or the like that does this upon user command? I suppose there would be some potential value to having this be set per-user but that wouldn't preclude the usefulness of a function. And by having a function the usefulness of the GUC seems reduced.
The idea of having module-specific functions to remove cached entries
seems like a good idea. Users have to frequently call these functions
to clean up the cached entries in a long lasting single session. This
may not
be always possible if these sessions are from an application not from
a psql-like client which is a more frequent scenario in the customer
use cases. In this case users might have to change their application
code that is
issuing queries to postgres server to include these functions.
Assuming the fact that the server/session configuration happens much
before the user application starts to submit actual database queries,
having a GUC just helps to avoid making such function calls in between
the session, by having to set the GUC either to true if required to
cache connections or to off if not to cache connections.
On a related note is there any entanglement here with the supplied dblink and/or dblink_fdw [1] modules as they do provide connect and disconnect functions and also leverages postgres_fdw (or dblink_fdw if specified, which brings us back to the question of whether this option should be respected by that FDW).
I found that dblink also has the connection caching concept and it
does provide a user a function to disconnect/remove cached connections
using a function, dblink_disconnect() using connection name as it's
input.
IMO, this solution seems a bit problematic as explained in my first
response in this mail.
The postgres_fdw connection caching and dblink connection caching has
no link at all. Please point me if I'm missing anything here.
But probably, this GUC can be extended from a bool to an enum of type
config_enum_entry and use it for dblink as well. This is extensible as
well. Please let me know if this is okay, so that I can code for it.
Otherwise, I would imagine that having multiple queries execute before wanting to drop the connection would be desirable so at minimum a test case that does something like:
SELECT count(*) FROM remote.tbl1;
-- connection still open
SET enable_connectioncache TO false;
SELECT count(*) FROM remote.tbl2;
-- now it was closedOr maybe even better, have the close action happen on a transaction boundary.
This is a valid scenario, as the same connection can be used in the
same transaction multiple times. With my attached initial patch above
the point is already covered. The decision to cache or not cache the
connection happens at the main transaction end i.e. in
pgfdw_xact_callback().
And if it doesn't just apply to postgres_fdw (or at least doesn't have to) then the description text should be less specific.
If we are agreed on a generic GUC for postgres_fdw, dblink and so on.
I will change the description and documentation accordingly.
Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, 22 Jun 2020 at 14:56, Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Hi,
When a query on foreign table is executed from a local session using
postgres_fdw, as expected the local postgres backend opens a
connection which causes a remote session/backend to be opened on the
remote postgres server for query execution.One observation is that, even after the query is finished, the remote
session/backend still persists on the remote postgres server. Upon
researching, I found that there is a concept of Connection Caching for
the remote connections made using postgres_fdw. Local backend/session
can cache up to 8 different connections per backend. This caching is
useful as it avoids the cost of reestablishing new connections per
foreign query.However, at times, there may be situations where the long lasting
local sessions may execute very few foreign queries and remaining all
are local queries, in this scenario, the remote sessions opened by the
local sessions/backends may not be useful as they remain idle and eat
up the remote server connections capacity. This problem gets even
worse(though this use case is a bit imaginary) if all of
max_connections(default 100 and each backend caching 8 remote
connections) local sessions open remote sessions and they are cached
in the local backend.I propose to have a new session level GUC called
"enable_connectioncache"(name can be changed if it doesn't correctly
mean the purpose) with the default value being true which means that
all the remote connections are cached. If set to false, the
connections are not cached and so are remote sessions closed by the local backend/session at
the end of each remote transaction.
I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Jun 30, 2020 at 12:23:28PM +0900, Masahiko Sawada wrote:
I propose to have a new session level GUC called
"enable_connectioncache"(name can be changed if it doesn't correctly
mean the purpose) with the default value being true which means that
all the remote connections are cached. If set to false, the
connections are not cached and so are remote sessions closed by the local backend/session at
the end of each remote transaction.I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.
I thought we would add a core capability, idle_session_timeout, which
would disconnect idle sessions, and the postgres_fdw would use that. We
have already had requests for idle_session_timeout, but avoided it
because it seemed better to tell people to monitor pg_stat_activity and
terminate sessions that way, but now that postgres_fdw needs it too,
there might be enough of a requirement to add it.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EnterpriseDB https://enterprisedb.com
The usefulness of a cup is in its emptiness, Bruce Lee
On Tue, Jun 30, 2020 at 8:54 AM Masahiko Sawada <
masahiko.sawada@2ndquadrant.com> wrote:
On Mon, 22 Jun 2020 at 14:56, Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:Hi,
When a query on foreign table is executed from a local session using
postgres_fdw, as expected the local postgres backend opens a
connection which causes a remote session/backend to be opened on the
remote postgres server for query execution.One observation is that, even after the query is finished, the remote
session/backend still persists on the remote postgres server. Upon
researching, I found that there is a concept of Connection Caching for
the remote connections made using postgres_fdw. Local backend/session
can cache up to 8 different connections per backend. This caching is
useful as it avoids the cost of reestablishing new connections per
foreign query.However, at times, there may be situations where the long lasting
local sessions may execute very few foreign queries and remaining all
are local queries, in this scenario, the remote sessions opened by the
local sessions/backends may not be useful as they remain idle and eat
up the remote server connections capacity. This problem gets even
worse(though this use case is a bit imaginary) if all of
max_connections(default 100 and each backend caching 8 remote
connections) local sessions open remote sessions and they are cached
in the local backend.I propose to have a new session level GUC called
"enable_connectioncache"(name can be changed if it doesn't correctly
mean the purpose) with the default value being true which means that
all the remote connections are cached. If set to false, the
connections are not cached and so are remote sessions closed by thelocal backend/session at
the end of each remote transaction.
I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.
+1
I have not looked at the implementation, but I agree that here problem
is with postgres_fdw so we should try to solve that by keeping it limited
to postgres_fdw. I liked the idea of passing it as an option to the FDW
connection.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Rushabh Lathia
I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.
If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.
Overall, though we define the server object in a single session, it
will be used in multiple sessions, having an
option at the per-server level would not be a good idea.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Wed, Jul 1, 2020 at 2:45 PM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
In my opinion, in such cases, one needs to create two server object one with
keep-connection ON and one with keep-connection off. And need to decide
to use appropriate for the particular session.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.Overall, though we define the server object in a single session, it
will be used in multiple sessions, having an
option at the per-server level would not be a good idea.With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
--
Rushabh Lathia
I thought we would add a core capability, idle_session_timeout, which
would disconnect idle sessions, and the postgres_fdw would use that. We
have already had requests for idle_session_timeout, but avoided it
because it seemed better to tell people to monitor pg_stat_activity and
terminate sessions that way, but now that postgres_fdw needs it too,
there might be enough of a requirement to add it.
If we were to use idle_session_timeout (from patch [1]/messages/by-id/763A0689-F189-459E-946F-F0EC4458980B@hotmail.com) for the remote
session to go off without
having to delete the corresponding entry from local connection cache and
after that if we submit foreign query from local session, then below
error would occur,
which may not be an expected behaviour. (I took the patch from [1]/messages/by-id/763A0689-F189-459E-946F-F0EC4458980B@hotmail.com and
intentionally set the
idle_session_timeout to a low value on remote server, issued a
foreign_tbl query which
caused remote session to open and after idle_session_timeout , the
remote session
closes and now issue the foreign_tbl query from local session)
postgres=# SELECT * FROM foreign_tbl;
ERROR: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
CONTEXT: remote SQL command: START TRANSACTION ISOLATION LEVEL REPEATABLE READ
postgres=#
Another way is that if we are thinking to use idle_session_timeout
infra on the local postgres server to remove cached entries
from the local connection cache, then the question arises:
do we intend to use the same configuration parameter value set for
idle_session_timeout for connection cache as well?
Probably not, as we might use different values for different purposes
of the same idle_session_timeout parameter,
let's say 2000sec for idle_session_timeout and 1000sec for connection
cache cleanup.
[1]: /messages/by-id/763A0689-F189-459E-946F-F0EC4458980B@hotmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Show quoted text
On Wed, Jul 1, 2020 at 3:33 PM Rushabh Lathia <rushabh.lathia@gmail.com> wrote:
On Wed, Jul 1, 2020 at 2:45 PM Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> wrote:
I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.In my opinion, in such cases, one needs to create two server object one with
keep-connection ON and one with keep-connection off. And need to decide
to use appropriate for the particular session.One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.Overall, though we define the server object in a single session, it
will be used in multiple sessions, having an
option at the per-server level would not be a good idea.With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com--
Rushabh Lathia
On Wed, Jul 1, 2020 at 3:54 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
If we were to use idle_session_timeout (from patch [1]) for the remote
session to go off without
having to delete the corresponding entry from local connection cache and
after that if we submit foreign query from local session, then below
error would occur,
which may not be an expected behaviour. (I took the patch from [1] and
intentionally set the
idle_session_timeout to a low value on remote server, issued a
foreign_tbl query which
caused remote session to open and after idle_session_timeout , the
remote session
closes and now issue the foreign_tbl query from local session)postgres=# SELECT * FROM foreign_tbl;
ERROR: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
CONTEXT: remote SQL command: START TRANSACTION ISOLATION LEVEL REPEATABLE READ
postgres=#
This is actually strange. AFAIR the code, without looking at the
current code, when a query picks a foreign connection it checks its
state. It's possible that the connection has not been marked bad by
the time you fire new query. If the problem exists probably we should
fix it anyway since the backend at the other end of the connection has
higher chances of being killed while the connection was sitting idle
in the cache.
--
Best Wishes,
Ashutosh Bapat
On Wed, 1 Jul 2020 at 18:14, Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.
Is there use-case in practice where different backends need to have
different connection cache setting even if all of them connect the
same server? I thought since the problem that this feature is trying
to resolve is not to eat up the remote server connections capacity by
disabling connection cache, we’d like to disable connection cache to
the particular server, for example, which sets low max_connections.
Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
If we were to use idle_session_timeout (from patch [1]) for the remote
session to go off without
having to delete the corresponding entry from local connection cache and
after that if we submit foreign query from local session, then below
error would occur,
which may not be an expected behaviour. (I took the patch from [1] and
intentionally set the
idle_session_timeout to a low value on remote server, issued a
foreign_tbl query which
caused remote session to open and after idle_session_timeout , the
remote session
closes and now issue the foreign_tbl query from local session)postgres=# SELECT * FROM foreign_tbl;
ERROR: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
CONTEXT: remote SQL command: START TRANSACTION ISOLATION LEVEL
REPEATABLE READ
postgres=#
This is actually strange. AFAIR the code, without looking at the
current code, when a query picks a foreign connection it checks its
state. It's possible that the connection has not been marked bad by
the time you fire new query. If the problem exists probably we should
fix it anyway since the backend at the other end of the connection has
higher chances of being killed while the connection was sitting idle
in the cache.
Thanks Ashutosh for the suggestion. One way, we could solve the above
problem is that, upon firing the new foreign query from local backend using
cached
connection, (assuming the remote backend/session that was cached in the
local backed got
killed by some means), instead of failing the query in the local
backend/session, upon
detecting error from remote backend, we could just delete the cached old
entry and try getting another
connection to remote backend/session, cache it and proceed to submit the
query. This has to happen only at
the beginning of remote xact.
This way, instead of failing(as mentioned above " server closed the
connection unexpectedly"),
the query succeeds if the local session is able to get a new remote backend
connection.
I worked on a POC patch to prove the above point. Attaching the patch.
Please note that, the patch doesn't contain comments and has some issues
like having some new
variable in PGconn structure and the things like.
If the approach makes some sense, then I can rework properly on the patch
and probably
can open another thread for the review and other stuff.
The way I tested the patch:
1. select * from foreign_tbl;
/*from local session - this results in a
remote connection being cached in
the connection cache and
a remote backend/session is opened.
*/
2. kill the remote backend/session
3. select * from foreign_tbl;
/*from local session - without patch
this throws error "ERROR: server closed the connection unexpectedly"
with path - try to use
the cached connection at the beginning of remote xact, upon receiving
error from remote postgres
server, instead of aborting the query, delete the cached entry, try to
get a new connection, if it
gets, cache it and use that for executing the query, query succeeds.
*/
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Wed, Jul 1, 2020 at 7:13 PM Masahiko Sawada <
masahiko.sawada@2ndquadrant.com> wrote:
Show quoted text
On Wed, 1 Jul 2020 at 18:14, Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I've not looked at your patch deeply but if this problem is talking
only about postgres_fdw I think we should improve postgres_fdw, not
adding a GUC to the core. It’s not that all FDW plugins use connection
cache and postgres_fdw’s connection cache is implemented within
postgres_fdw, I think we should focus on improving postgres_fdw. I
also think it’s not a good design that the core manages connections to
remote servers connected via FDW. I wonder if we can add a
postgres_fdw option for this purpose, say keep_connection [on|off].
That way, we can set it per server so that remote connections to the
particular server don’t remain idle.If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.Is there use-case in practice where different backends need to have
different connection cache setting even if all of them connect the
same server? I thought since the problem that this feature is trying
to resolve is not to eat up the remote server connections capacity by
disabling connection cache, we’d like to disable connection cache to
the particular server, for example, which sets low max_connections.Regards,
--
Masahiko Sawada http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v1-0001-Retry-cached-remote-connections-in-case-if-remote.patchapplication/octet-stream; name=v1-0001-Retry-cached-remote-connections-in-case-if-remote.patchDownload
From 64bcaf49bcb3a435692e95031ed4c86849f31820 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 2 Jul 2020 15:21:24 +0530
Subject: [PATCH v1] Retry cached remote connections in case if remote backend
is killed
Remote connections are cached for postgres_fdw in the local backend.
There are high chances that the remote backend can no logner be
avaiable i.e. it can get killed, the subsequent foreign queries
from local backend/session that uses cached connection fails as
the remote backend woule have become unavailable/killed. So,
this patch, solves this problem,
1. local backend/session uses cached connection, but recieves error
2. upon receiving the first error, it deletes the cached entry
3. try to get new connection at the start of begin remote xact
---
contrib/postgres_fdw/connection.c | 100 ++++++++++++++++++++++--------
src/interfaces/libpq/fe-connect.c | 1 +
src/interfaces/libpq/libpq-int.h | 2 +
3 files changed, 77 insertions(+), 26 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 52d1fe3563..1298eb3a89 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -16,6 +16,7 @@
#include "access/xact.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "libpq-int.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -60,6 +61,14 @@ typedef struct ConnCacheEntry
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
+/* Used for retrying remote connections in postgres_fdw. */
+typedef enum
+{
+ REMOTE_CONNECTION_RETRY_INIT,
+ CAN_RETRY_REMOTE_CONNECTION,
+ DO_RETRY_REMOTE_CONNECTION
+}PGRemoteRetryConnectionType;
+
/*
* Connection cache (initialized on first use)
*/
@@ -109,6 +118,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
bool found;
ConnCacheEntry *entry;
ConnCacheKey key;
+ uint8 retrycount = 0;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -180,35 +190,54 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
* connection. (If connect_pg_server throws an error, the cache entry
* will remain in a valid empty state, ie conn == NULL.)
*/
- if (entry->conn == NULL)
+ while(true)
{
- ForeignServer *server = GetForeignServer(user->serverid);
+ if (entry->conn == NULL)
+ {
+ ForeignServer *server = GetForeignServer(user->serverid);
- /* Reset all transient state fields, to be sure all are clean */
- entry->xact_depth = 0;
- entry->have_prep_stmt = false;
- entry->have_error = false;
- entry->changing_xact_state = false;
- entry->invalidated = false;
- entry->server_hashvalue =
- GetSysCacheHashValue1(FOREIGNSERVEROID,
- ObjectIdGetDatum(server->serverid));
- entry->mapping_hashvalue =
- GetSysCacheHashValue1(USERMAPPINGOID,
- ObjectIdGetDatum(user->umid));
-
- /* Now try to make the connection */
- entry->conn = connect_pg_server(server, user);
-
- elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\" (user mapping oid %u, userid %u)",
- entry->conn, server->servername, user->umid, user->userid);
- }
+ /* Reset all transient state fields, to be sure all are clean */
+ entry->xact_depth = 0;
+ entry->have_prep_stmt = false;
+ entry->have_error = false;
+ entry->changing_xact_state = false;
+ entry->invalidated = false;
+ entry->server_hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ entry->mapping_hashvalue =
+ GetSysCacheHashValue1(USERMAPPINGOID,
+ ObjectIdGetDatum(user->umid));
+
+ /* Now try to make the connection */
+ entry->conn = connect_pg_server(server, user);
+
+ elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\" (user mapping oid %u, userid %u)",
+ entry->conn, server->servername, user->umid, user->userid);
+ }
- /*
- * Start a new transaction or subtransaction if needed.
- */
- begin_remote_xact(entry);
+ if (retrycount == 0)
+ entry->conn->remote_retry_conn = CAN_RETRY_REMOTE_CONNECTION;
+ else
+ entry->conn->remote_retry_conn = REMOTE_CONNECTION_RETRY_INIT;
+ /*
+ * Start a new transaction or subtransaction if needed.
+ */
+ begin_remote_xact(entry);
+
+ retrycount++;
+ if (entry->conn->remote_retry_conn == DO_RETRY_REMOTE_CONNECTION)
+ {
+ disconnect_pg_server(entry);
+ continue;
+ }
+ else
+ {
+ entry->conn->remote_retry_conn = REMOTE_CONNECTION_RETRY_INIT;
+ break;
+ }
+ }
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -457,6 +486,11 @@ do_sql_command(PGconn *conn, const char *sql)
if (!PQsendQuery(conn, sql))
pgfdw_report_error(ERROR, NULL, conn, false, sql);
res = pgfdw_get_result(conn, sql);
+
+ if (conn->remote_retry_conn == DO_RETRY_REMOTE_CONNECTION &&
+ res == NULL)
+ return;
+
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pgfdw_report_error(ERROR, res, conn, true, sql);
PQclear(res);
@@ -490,9 +524,15 @@ begin_remote_xact(ConnCacheEntry *entry)
else
sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ";
entry->changing_xact_state = true;
+
do_sql_command(entry->conn, sql);
- entry->xact_depth = 1;
+
entry->changing_xact_state = false;
+
+ if (entry->conn->remote_retry_conn == DO_RETRY_REMOTE_CONNECTION)
+ return;
+
+ entry->xact_depth = 1;
}
/*
@@ -617,7 +657,15 @@ pgfdw_get_result(PGconn *conn, const char *query)
if (wc & WL_SOCKET_READABLE)
{
if (!PQconsumeInput(conn))
+ {
+ if (conn->remote_retry_conn == CAN_RETRY_REMOTE_CONNECTION)
+ {
+ conn->remote_retry_conn = DO_RETRY_REMOTE_CONNECTION;
+ return NULL;
+ }
+
pgfdw_report_error(ERROR, NULL, conn, false, query);
+ }
}
}
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 27c9bb46ee..0385489a37 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -3929,6 +3929,7 @@ makeEmptyPGconn(void)
conn->rowBuf = (PGdataValue *) malloc(conn->rowBufLen * sizeof(PGdataValue));
initPQExpBuffer(&conn->errorMessage);
initPQExpBuffer(&conn->workBuffer);
+ conn->remote_retry_conn = 0;
if (conn->inBuffer == NULL ||
conn->outBuffer == NULL ||
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..1a09cea499 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -522,6 +522,8 @@ struct pg_conn
* connection */
#endif
+ uint8 remote_retry_conn;
+
/* Buffer for current error message */
PQExpBufferData errorMessage; /* expansible string */
--
2.25.1
If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.In my opinion, in such cases, one needs to create two server object one with
keep-connection ON and one with keep-connection off. And need to decide
to use appropriate for the particular session.
Yes, having two variants of foreign servers: one with keep-connections
on (this can be default behavior,
even if user doesn't mention this option, internally it can be treated
as keep-connections on) ,
and if users need no connection hashing, another foreign server with
all other options same but keep-connections
off.
This looks okay to me, if we want to avoid a core session level GUC.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.Is there use-case in practice where different backends need to have
different connection cache setting even if all of them connect the
same server?
Currently, connection cache exists at each backend/session level and
gets destroyed
on backend/session exit. I think the same cached connection can be
used until it gets invalidated
due to user mapping or server definition changes.
One way is to have a shared memory based connection cache instead of
backend level cache,
but it has its own problems, like maintenance, invalidation, dealing
with concurrent usages etc.
I thought since the problem that this feature is trying
to resolve is not to eat up the remote server connections capacity by
disabling connection cache, we’d like to disable connection cache to
the particular server, for example, which sets low max_connections.
Currently, the user mapping oid acts as the key for the cache's hash
table, so the cache entries
are not made directly using foreign server ids though each entry would
have some information related
to foreign server.
Just to reiterate, the main idea if this feature is to give the user
a way to choose, whether to use connection caching or not,
if he decides that his session uses remote queries very rarely, then
he can disable, or if the remote queries are more frequent in
a particular session, he can choose to use connection caching.
In a way, this feature addresses the point that local sessions not
eating up remote connections/sessions by
letting users decide(as users know better when to cache or when not
to) to cache or not cache the remote connections
and thus releasing them immediately if there is not much usage from
local session.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Jul 2, 2020 at 4:29 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
This is actually strange. AFAIR the code, without looking at the
current code, when a query picks a foreign connection it checks its
state. It's possible that the connection has not been marked bad by
the time you fire new query. If the problem exists probably we should
fix it anyway since the backend at the other end of the connection has
higher chances of being killed while the connection was sitting idle
in the cache.Thanks Ashutosh for the suggestion. One way, we could solve the above
problem is that, upon firing the new foreign query from local backend using cached
connection, (assuming the remote backend/session that was cached in the local backed got
killed by some means), instead of failing the query in the local backend/session, upon
detecting error from remote backend, we could just delete the cached old entry and try getting another
connection to remote backend/session, cache it and proceed to submit the query. This has to happen only at
the beginning of remote xact.
Yes, I believe that would be good.
This way, instead of failing(as mentioned above " server closed the connection unexpectedly"),
the query succeeds if the local session is able to get a new remote backend connection.
In GetConnection() there's a comment
/*
* We don't check the health of cached connection here, because it would
* require some overhead. Broken connection will be detected when the
* connection is actually used.
*/
Possibly this is where you want to check the health of connection when
it's being used the first time in a transaction.
I worked on a POC patch to prove the above point. Attaching the patch.
Please note that, the patch doesn't contain comments and has some issues like having some new
variable in PGconn structure and the things like.
I don't think changing the PGConn structure for this is going to help.
It's a libpq construct and used by many other applications/tools other
than postgres_fdw. Instead you could use ConnCacheEntry for the same.
See how we track invalidated connection and reconnect upon
invalidation.
If the approach makes some sense, then I can rework properly on the patch and probably
can open another thread for the review and other stuff.The way I tested the patch:
1. select * from foreign_tbl;
/*from local session - this results in a
remote connection being cached in
the connection cache and
a remote backend/session is opened.
*/
2. kill the remote backend/session
3. select * from foreign_tbl;
/*from local session - without patch
this throws error "ERROR: server closed the connection unexpectedly"
with path - try to use
the cached connection at the beginning of remote xact, upon receiving
error from remote postgres
server, instead of aborting the query, delete the cached entry, try to
get a new connection, if it
gets, cache it and use that for executing the query, query succeeds.
*/
This will work. Be cognizant of the fact that the same connection may
be used by multiple plan nodes.
--
Best Wishes,
Ashutosh Bapat
On Wed, Jul 1, 2020 at 5:15 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.Overall, though we define the server object in a single session, it
will be used in multiple sessions, having an
option at the per-server level would not be a good idea.
You present this here as if it should be a Boolean (on or off) but I
don't see why that should be the case. You can imagine trying to close
connections if they have been idle for a certain length of time, or if
there are more than a certain number of them, rather than (or in
addition to) always/never. Which one is best, and why?
I tend to think this is better as an FDW property rather than a core
facility, but I'm not 100% sure of that and I think it likely depends
somewhat on the answers we choose to the questions in the preceding
paragraph.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
If I understand it correctly, your suggestion is to add
keep_connection option and use that while defining the server object.
IMO having keep_connection option at the server object level may not
serve the purpose being discussed here.
For instance, let's say I create a foreign server in session 1 with
keep_connection on, and I want to use that
server object in session 2 with keep_connection off and session 3 with
keep_connection on and so on.
One way we can change the server's keep_connection option is to alter
the server object, but that's not a good choice,
as we have to alter it at the system level.Overall, though we define the server object in a single session, it
will be used in multiple sessions, having an
option at the per-server level would not be a good idea.You present this here as if it should be a Boolean (on or off) but I
don't see why that should be the case. You can imagine trying to close
connections if they have been idle for a certain length of time, or if
there are more than a certain number of them, rather than (or in
addition to) always/never. Which one is best, and why?
If the cached connection idle time property is used (I'm thinking we
can define it per server object) then the local backend might have to
close the connections which are lying unused more than idle time. To
perform this task, the local backend might have to do it before it
goes into idle state(as suggested by you in [1]/messages/by-id/CA+Tgmob_ksTOgmbXhno+k5XXPOK+-JYYLoU3MpXuutP4bH7gzA@mail.gmail.com). Please correct, if
my understanding/thinking is wrong here.
If the connection clean up is to be done by the local backend, then a
point can be - let say a local session initially issues few foreign
queries for which connections are cached, and it keeps executing all
local queries, without never going to idle mode(I think this scenario
looks too much impractical to me), then we may never clean the unused
cached connections. If this scenario is really impractical if we are
sure that there are high chances that the local backend goes to idle
mode, then the idea of having per-server-object idle time and letting
the local backend clean it up before it goes to idle mode looks great
to me.
I tend to think this is better as an FDW property rather than a core
facility, but I'm not 100% sure of that and I think it likely depends
somewhat on the answers we choose to the questions in the preceding
paragraph.
I completely agree on having it as a FDW property.
[1]: /messages/by-id/CA+Tgmob_ksTOgmbXhno+k5XXPOK+-JYYLoU3MpXuutP4bH7gzA@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Wed, Jul 8, 2020 at 9:26 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
If the cached connection idle time property is used (I'm thinking we
can define it per server object) then the local backend might have to
close the connections which are lying unused more than idle time. To
perform this task, the local backend might have to do it before it
goes into idle state(as suggested by you in [1]). Please correct, if
my understanding/thinking is wrong here.If the connection clean up is to be done by the local backend, then a
point can be - let say a local session initially issues few foreign
queries for which connections are cached, and it keeps executing all
local queries, without never going to idle mode(I think this scenario
looks too much impractical to me), then we may never clean the unused
cached connections. If this scenario is really impractical if we are
sure that there are high chances that the local backend goes to idle
mode, then the idea of having per-server-object idle time and letting
the local backend clean it up before it goes to idle mode looks great
to me.
If it just did it before going idle, then what about sessions that
haven't reached the timeout at the point when we go idle, but do reach
the timeout later? And how would the FDW get control at the right time
anyway?
I tend to think this is better as an FDW property rather than a core
facility, but I'm not 100% sure of that and I think it likely depends
somewhat on the answers we choose to the questions in the preceding
paragraph.I completely agree on having it as a FDW property.
Right, but not everyone does. It looks to me like there have been
votes on both sides.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Thanks all for the ideas. There have been various points/approaches
discussed in the entire email chain so far.
I would like to summarize all of them here, so that we can agree on
one of the options and proceed further with this feature.
The issue this feature is trying to solve:
In postgres_fdw, rarely used remote connections lie ilde in the
connection cache(per backend) and so are remote sessions, for long
lasting local sessions which may unnecessarily eatup connections on
remote postgres servers.
Approach #1:
A new session level GUC (proposed name "enable_connectioncache"), when
set to true(which is by default) caches the remote connections
otherwise not. When set to false, everytime foreign query is issued a
new connection is made at the remote xact begin and dropped from the
connection cache at the remote xact end. This GUC applies to all the
foreign servers that are used in the session, it may not be possible
to have the control at the foreign server level. It may not be a good
idea to have postgres core controlling postgres_fdw property.
Approach #2:
A new postgres_fdw function, similar to dblink's dblink_disconnect(),
(possibly named postgres_fdw_disconnect_open_connections()). Seems
easy, but users have to frequently call this function to clean up the
cached entries. This may not be always possible, requires some sort of
monitoring and issuing this new disconnect function from in between
application code.
Approach #3:
A postgres_fdw foreign server level option: keep_connection(on/off).
When set to on (which is by default), caches the entries related to
that particular foreign server otherwise not. This gives control at
the foreign server level, which may not be possible with a single GUC.
It also addresses the concern that having postgres core solving
postgres_fdw problem. But, when the same foreign server is to be used
in multiple other sessions with different keep_connection
options(on/off), then a possible solution is to have two foreign
server definitions for the same server, one with keep_connection on
and another with off and use the foreign server accordingly and when
there is any change in other foreign server properties/options, need
to maintain the two versions of foreign servers.
Approach #4:
A postgres_fdw foreign server level option: connection idle time, the
amount of idle time for that server cached entry, after which the
cached entry goes away. Probably the backend, before itself going to
idle, has to be checking the cached entries and see if any of the
entries has timed out. One problem is that, if the backend just did it
before going idle, then what about sessions that haven't reached the
timeout at the point when we go idle, but do reach the timeout later?
I tried to summarize and put in the points in a concise manner,
forgive if I miss anything.
Thoughts?
Credits and thanks to: vignesh C, David G. Johnston, Masahiko Sawada,
Bruce Momjian, Rushabh Lathia, Ashutosh Bapat, Robert Haas.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Jul 14, 2020 at 6:09 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Thanks all for the ideas. There have been various points/approaches
discussed in the entire email chain so far.
I would like to summarize all of them here, so that we can agree on
one of the options and proceed further with this feature.
In my opinion, approach #2 seems easy to implement and it's hard to
imagine anyone finding much to complain about there, but it's not that
powerful either, because it isn't automatic. Now the other approaches
have to do with the way in which this should be controlled, and I
think there are two separate questions.
1. Should this be controlled by (a) a core GUC, (b) a postgres_fdw
GUC, (c) a postgres_fdw server-level option?
2. Should it be (a) a timeout or (b) a Boolean (keep vs. don't keep)?
With regard to #1, even if we decided on a core GUC, I cannot imagine
that we'd accept enable_connectioncache as a name, because most
enable_whatever GUCs are for the planner, and this is something else.
Also, underscores between some words but not all words is a lousy
convention; let's not do more of that. Apart from those points, I
don't have a strong opinion; other people might. With regard to #2, a
timeout seems a lot more powerful, but also harder to implement
because you'd need some kind of core changes to let the FDW get
control at the proper time. Maybe that's an argument for 2(b), but I
have a bit of a hard time believing that 2(b) will provide a good user
experience. I doubt that most people want to have to decide between
slamming the connection shut even if it's going to be used again
almost immediately and keeping it open until the end of time. Those
are two pretty extreme positions.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Jul 14, 2020 at 03:38:49PM +0530, Bharath Rupireddy wrote:
Approach #4:
A postgres_fdw foreign server level option: connection idle time, the
amount of idle time for that server cached entry, after which the
cached entry goes away. Probably the backend, before itself going to
idle, has to be checking the cached entries and see if any of the
entries has timed out. One problem is that, if the backend just did it
before going idle, then what about sessions that haven't reached the
timeout at the point when we go idle, but do reach the timeout later?
Imagine implementing idle_in_session_timeout (which is useful on its
own), and then, when you connect to a foreign postgres_fdw server, you
set idle_in_session_timeout on the foreign side, and it just
disconnects/exits after an idle timeout. There is nothing the sending
side has to do.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EnterpriseDB https://enterprisedb.com
The usefulness of a cup is in its emptiness, Bruce Lee
On Tue, Jul 14, 2020 at 10:28 PM Bruce Momjian <bruce@momjian.us> wrote:
On Tue, Jul 14, 2020 at 03:38:49PM +0530, Bharath Rupireddy wrote:
Approach #4:
A postgres_fdw foreign server level option: connection idle time, the
amount of idle time for that server cached entry, after which the
cached entry goes away. Probably the backend, before itself going to
idle, has to be checking the cached entries and see if any of the
entries has timed out. One problem is that, if the backend just did it
before going idle, then what about sessions that haven't reached the
timeout at the point when we go idle, but do reach the timeout later?Imagine implementing idle_in_session_timeout (which is useful on its
own), and then, when you connect to a foreign postgres_fdw server, you
set idle_in_session_timeout on the foreign side, and it just
disconnects/exits after an idle timeout. There is nothing the sending
side has to do.
Assuming we use idle_in_session_timeout on remote backends, the
remote sessions will be closed after timeout, but the locally cached
connection cache entries still exist and become stale. The subsequent
queries that may use the cached connections will fail, of course these
subsequent queries can retry the connections only at the beginning of
a remote txn but not in the middle of a remote txn, as being discussed
in [1]/messages/by-id/CALj2ACUAi23vf1WiHNar_LksM9EDOWXcbHCo-fD4Mbr1d=78YQ@mail.gmail.com. For instance, in a long running local txn, let say we used a
remote connection at the beginning of the local txn(note that it will
open a remote session and it's entry is cached in local connection
cache), only we use the cached connection later at some point in the
local txn, by then let say the idle_in_session_timeout has happened on
the remote backend and the remote session would have been closed. The
long running local txn will fail instead of succeeding. Isn't it a
problem here? Please correct me, If I miss anything.
IMHO, we are not fully solving the problem with
idle_in_session_timeout on remote backends though we are addressing
the main problem partly by letting the remote sessions close by
themselves.
[1]: /messages/by-id/CALj2ACUAi23vf1WiHNar_LksM9EDOWXcbHCo-fD4Mbr1d=78YQ@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Aug 03, 2020 at 04:41:58PM +0530, Bharath Rupireddy wrote:
IMHO, we are not fully solving the problem with
idle_in_session_timeout on remote backends though we are addressing
the main problem partly by letting the remote sessions close by
themselves.
This patch fails to compile on Windows. And while skimming through
the patch, I can see that you are including libpq-int.h in a place
different than src/interfaces/libpq/. This is incorrect as it should
remain strictly as a header internal to libpq.
--
Michael
On Tue, Sep 29, 2020 at 11:21 AM Michael Paquier <michael@paquier.xyz> wrote:
On Mon, Aug 03, 2020 at 04:41:58PM +0530, Bharath Rupireddy wrote:
IMHO, we are not fully solving the problem with
idle_in_session_timeout on remote backends though we are addressing
the main problem partly by letting the remote sessions close by
themselves.This patch fails to compile on Windows. And while skimming through
the patch, I can see that you are including libpq-int.h in a place
different than src/interfaces/libpq/. This is incorrect as it should
remain strictly as a header internal to libpq.
Unfortunately, we have not arrived at a final solution yet, please
ignore this patch. I will post a new patch, once the solution is
finalized. I will move it to the next commit fest if okay.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Sep 29, 2020 at 11:29:45AM +0530, Bharath Rupireddy wrote:
Unfortunately, we have not arrived at a final solution yet, please
ignore this patch. I will post a new patch, once the solution is
finalized. I will move it to the next commit fest if okay.
If you are planning to get that addressed, moving it to next CF is
fine by me. Thanks for the update!
--
Michael
Status update for a commitfest entry.
This thread was inactive for a while and from the latest messages, I see that the patch needs some further work.
So I move it to "Waiting on Author".
The new status of this patch is: Waiting on Author
Hi,
On 2020-11-06 18:56, Anastasia Lubennikova wrote:
Status update for a commitfest entry.
This thread was inactive for a while and from the latest messages, I
see that the patch needs some further work.
So I move it to "Waiting on Author".The new status of this patch is: Waiting on Author
I had a look on the initial patch and discussed options [1]/messages/by-id/CALj2ACUFNydy0uo0JL9A1isHQ9pFe1Fgqa_HVanfG6F8g21nSQ@mail.gmail.com to proceed
with this issue. I agree with Bruce about idle_session_timeout, it would
be a nice to have in-core feature on its own. However, this should be a
cluster-wide option and it will start dropping all idle connection not
only foreign ones. So it may be not an option for some cases, when the
same foreign server is used for another load as well.
Regarding the initial issue I prefer point #3, i.e. foreign server
option. It has a couple of benefits IMO: 1) it may be set separately on
per foreign server basis, 2) it will live only in the postgres_fdw
contrib without any need to touch core. I would only supplement this
postgres_fdw foreign server option with a GUC, e.g.
postgres_fdw.keep_connections, so one could easily define such behavior
for all foreign servers at once or override server-level option by
setting this GUC on per session basis.
Attached is a small POC patch, which implements this contrib-level
postgres_fdw.keep_connections GUC. What do you think?
[1]: /messages/by-id/CALj2ACUFNydy0uo0JL9A1isHQ9pFe1Fgqa_HVanfG6F8g21nSQ@mail.gmail.com
/messages/by-id/CALj2ACUFNydy0uo0JL9A1isHQ9pFe1Fgqa_HVanfG6F8g21nSQ@mail.gmail.com
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
Attachments:
pgfdw_keep_connections.difftext/x-diff; name=pgfdw_keep_connections.diffDownload
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ab3226287d..64f0e96635 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -28,6 +28,8 @@
#include "utils/memutils.h"
#include "utils/syscache.h"
+#include "postgres_fdw.h"
+
/*
* Connection cache hash table entry
*
@@ -948,6 +950,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
+ !keep_connections ||
entry->changing_xact_state)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9c5aaacc51..4cd5f71223 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -45,6 +45,8 @@
#include "utils/sampling.h"
#include "utils/selfuncs.h"
+#include "postgres_fdw.h"
+
PG_MODULE_MAGIC;
/* Default CPU cost to start up a foreign query. */
@@ -301,6 +303,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -505,6 +509,15 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections, true, PGC_USERSET, 0, NULL,
+ NULL, NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
Thanks for the interest shown!
On Wed, Nov 18, 2020 at 1:07 AM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
I had a look on the initial patch and discussed options [1] to proceed
with this issue. I agree with Bruce about idle_session_timeout, it would
be a nice to have in-core feature on its own. However, this should be a
cluster-wide option and it will start dropping all idle connection not
only foreign ones. So it may be not an option for some cases, when the
same foreign server is used for another load as well.
With idle_session_timeout the remote idle backends may go away, part
of our problem is solved. But we also need to clear that connection
entry from the local backend's connection cache.
Regarding the initial issue I prefer point #3, i.e. foreign server
option. It has a couple of benefits IMO: 1) it may be set separately on
per foreign server basis, 2) it will live only in the postgres_fdw
contrib without any need to touch core. I would only supplement this
postgres_fdw foreign server option with a GUC, e.g.
postgres_fdw.keep_connections, so one could easily define such behavior
for all foreign servers at once or override server-level option by
setting this GUC on per session basis.
Below is what I have in my mind, mostly inline with yours:
a) Have a server level option (keep_connetion true/false, with the
default being true), when set to false the connection that's made with
this foreign server is closed and cached entry from the connection
cache is deleted at the end of txn in pgfdw_xact_callback.
b) Have postgres_fdw level GUC postgres_fdw.keep_connections default
being true. When set to false by the user, the connections, that are
used after this, are closed and removed from the cache at the end of
respective txns. If we don't use a connection that was cached prior to
the user setting the GUC as false, then we may not be able to clear
it. We can avoid this problem by recommending users either to set the
GUC to false right after the CREATE EXTENSION postgres_fdw; or else
use the function specified in (c).
c) Have a new function that gets defined as part of CREATE EXTENSION
postgres_fdw;, say postgres_fdw_discard_connections(), similar to
dblink's dblink_disconnect(), which discards all the remote
connections and clears connection cache. And we can also have server
name as input to postgres_fdw_discard_connections() to discard
selectively.
Thoughts? If okay with the approach, I will start working on the patch.
Attached is a small POC patch, which implements this contrib-level
postgres_fdw.keep_connections GUC. What do you think?
I see two problems with your patch: 1) It just disconnects the remote
connection at the end of txn if the GUC is set to false, but it
doesn't remove the connection cache entry from ConnectionHash. 2) What
happens if there are some cached connections, user set the GUC to
false and not run any foreign queries or not use those connections
thereafter, so only the new connections will not be cached? Will the
existing unused connections still remain in the connection cache? See
(b) above for a solution.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020-11-18 16:39, Bharath Rupireddy wrote:
Thanks for the interest shown!
On Wed, Nov 18, 2020 at 1:07 AM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:Regarding the initial issue I prefer point #3, i.e. foreign server
option. It has a couple of benefits IMO: 1) it may be set separately
on
per foreign server basis, 2) it will live only in the postgres_fdw
contrib without any need to touch core. I would only supplement this
postgres_fdw foreign server option with a GUC, e.g.
postgres_fdw.keep_connections, so one could easily define such
behavior
for all foreign servers at once or override server-level option by
setting this GUC on per session basis.Below is what I have in my mind, mostly inline with yours:
a) Have a server level option (keep_connetion true/false, with the
default being true), when set to false the connection that's made with
this foreign server is closed and cached entry from the connection
cache is deleted at the end of txn in pgfdw_xact_callback.
b) Have postgres_fdw level GUC postgres_fdw.keep_connections default
being true. When set to false by the user, the connections, that are
used after this, are closed and removed from the cache at the end of
respective txns. If we don't use a connection that was cached prior to
the user setting the GUC as false, then we may not be able to clear
it. We can avoid this problem by recommending users either to set the
GUC to false right after the CREATE EXTENSION postgres_fdw; or else
use the function specified in (c).
c) Have a new function that gets defined as part of CREATE EXTENSION
postgres_fdw;, say postgres_fdw_discard_connections(), similar to
dblink's dblink_disconnect(), which discards all the remote
connections and clears connection cache. And we can also have server
name as input to postgres_fdw_discard_connections() to discard
selectively.Thoughts? If okay with the approach, I will start working on the patch.
This approach looks solid enough from my perspective to give it a try. I
would only make it as three separate patches for an ease of further
review.
Attached is a small POC patch, which implements this contrib-level
postgres_fdw.keep_connections GUC. What do you think?I see two problems with your patch: 1) It just disconnects the remote
connection at the end of txn if the GUC is set to false, but it
doesn't remove the connection cache entry from ConnectionHash.
Yes, and this looks like a valid state for postgres_fdw and it can get
into the same state even without my patch. Next time GetConnection()
will find this cache entry, figure out that entry->conn is NULL and
establish a fresh connection. It is not clear for me right now, what
benefits we will get from clearing also this cache entry, except just
doing this for sanity.
2) What
happens if there are some cached connections, user set the GUC to
false and not run any foreign queries or not use those connections
thereafter, so only the new connections will not be cached? Will the
existing unused connections still remain in the connection cache? See
(b) above for a solution.
Yes, they will. This could be solved with that additional disconnect
function as you proposed in c).
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
On Wed, Nov 18, 2020 at 10:32 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
Below is what I have in my mind, mostly inline with yours:
a) Have a server level option (keep_connetion true/false, with the
default being true), when set to false the connection that's made with
this foreign server is closed and cached entry from the connection
cache is deleted at the end of txn in pgfdw_xact_callback.
b) Have postgres_fdw level GUC postgres_fdw.keep_connections default
being true. When set to false by the user, the connections, that are
used after this, are closed and removed from the cache at the end of
respective txns. If we don't use a connection that was cached prior to
the user setting the GUC as false, then we may not be able to clear
it. We can avoid this problem by recommending users either to set the
GUC to false right after the CREATE EXTENSION postgres_fdw; or else
use the function specified in (c).
c) Have a new function that gets defined as part of CREATE EXTENSION
postgres_fdw;, say postgres_fdw_discard_connections(), similar to
dblink's dblink_disconnect(), which discards all the remote
connections and clears connection cache. And we can also have server
name as input to postgres_fdw_discard_connections() to discard
selectively.Thoughts? If okay with the approach, I will start working on the patch.
This approach looks solid enough from my perspective to give it a try. I
would only make it as three separate patches for an ease of further
review.
Thanks! I will make separate patches and post them soon.
Attached is a small POC patch, which implements this contrib-level
postgres_fdw.keep_connections GUC. What do you think?I see two problems with your patch: 1) It just disconnects the remote
connection at the end of txn if the GUC is set to false, but it
doesn't remove the connection cache entry from ConnectionHash.Yes, and this looks like a valid state for postgres_fdw and it can get
into the same state even without my patch. Next time GetConnection()
will find this cache entry, figure out that entry->conn is NULL and
establish a fresh connection. It is not clear for me right now, what
benefits we will get from clearing also this cache entry, except just
doing this for sanity.
By clearing the cache entry we will have 2 advantages: 1) we could
save a(small) bit of memory 2) we could allow new connections to be
cached, currently ConnectionHash can have only 8 entries. IMHO, along
with disconnecting, we can also clear off the cache entry. Thoughts?
2) What
happens if there are some cached connections, user set the GUC to
false and not run any foreign queries or not use those connections
thereafter, so only the new connections will not be cached? Will the
existing unused connections still remain in the connection cache? See
(b) above for a solution.Yes, they will. This could be solved with that additional disconnect
function as you proposed in c).
Right.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020-11-19 07:11, Bharath Rupireddy wrote:
On Wed, Nov 18, 2020 at 10:32 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:Thanks! I will make separate patches and post them soon.
Attached is a small POC patch, which implements this contrib-level
postgres_fdw.keep_connections GUC. What do you think?I see two problems with your patch: 1) It just disconnects the remote
connection at the end of txn if the GUC is set to false, but it
doesn't remove the connection cache entry from ConnectionHash.Yes, and this looks like a valid state for postgres_fdw and it can get
into the same state even without my patch. Next time GetConnection()
will find this cache entry, figure out that entry->conn is NULL and
establish a fresh connection. It is not clear for me right now, what
benefits we will get from clearing also this cache entry, except just
doing this for sanity.By clearing the cache entry we will have 2 advantages: 1) we could
save a(small) bit of memory 2) we could allow new connections to be
cached, currently ConnectionHash can have only 8 entries. IMHO, along
with disconnecting, we can also clear off the cache entry. Thoughts?
IIUC, 8 is not a hard limit, it is just a starting size. ConnectionHash
is not a shared-memory hash table, so dynahash can expand it on-the-fly
as follow, for example, from the comment before hash_create():
* Note: for a shared-memory hashtable, nelem needs to be a pretty good
* estimate, since we can't expand the table on the fly. But an
unshared
* hashtable can be expanded on-the-fly, so it's better for nelem to be
* on the small side and let the table grow if it's exceeded. An overly
* large nelem will penalize hash_seq_search speed without buying much.
Also I am not sure that by doing just a HASH_REMOVE you will free any
memory, since hash table is already allocated (or expanded) to some
size. So HASH_REMOVE will only add removed entry to the freeList, I
guess.
Anyway, I can hardly imagine bloating of ConnectionHash to be a problem
even in the case, when one has thousands of foreign servers all being
accessed during a single backend life span.
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
On Thu, Nov 19, 2020 at 5:39 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
By clearing the cache entry we will have 2 advantages: 1) we could
save a(small) bit of memory 2) we could allow new connections to be
cached, currently ConnectionHash can have only 8 entries. IMHO, along
with disconnecting, we can also clear off the cache entry. Thoughts?IIUC, 8 is not a hard limit, it is just a starting size. ConnectionHash
is not a shared-memory hash table, so dynahash can expand it on-the-fly
as follow, for example, from the comment before hash_create():
Thanks! Yes this is true. I was wrong earlier. I verified that 8 is
not a hard limit.
Also I am not sure that by doing just a HASH_REMOVE you will free any
memory, since hash table is already allocated (or expanded) to some
size. So HASH_REMOVE will only add removed entry to the freeList, I
guess.Anyway, I can hardly imagine bloating of ConnectionHash to be a problem
even in the case, when one has thousands of foreign servers all being
accessed during a single backend life span.
Okay. I will not add the code to remove the entries from cache.
Here is how I'm making 4 separate patches:
1. new function and it's documentation.
2. GUC and it's documentation.
3. server level option and it's documentation.
4. test cases for all of the above patches.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Here is how I'm making 4 separate patches:
1. new function and it's documentation.
2. GUC and it's documentation.
3. server level option and it's documentation.
4. test cases for all of the above patches.
Hi, I'm attaching the patches here. Note that, though the code changes
for this feature are small, I divided them up as separate patches to
make review easy.
v1-0001-postgres_fdw-function-to-discard-cached-connections.patch
This patch adds a new function that gets defined as part of CREATE
EXTENSION postgres_fdw; postgres_fdw_disconnect() when called with a
foreign server name discards the associated connections with the
server name. When called without any argument, discards all the
existing cached connections.
v1-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache-connections.patch
This patch adds a new GUC postgres_fdw.keep_connections, default being
on, when set to off no remote connections are cached by the local
session.
v1-0003-postgres_fdw-server-level-option-keep_connection.patch
This patch adds a new server level option, keep_connection, default
being on, when set to off, the local session doesn't cache the
connections associated with the foreign server.
v1-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patch
This patch adds the tests and documentation related to this feature.
Please review the patches.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v1-0001-postgres_fdw-function-to-discard-cached-connections.patchapplication/octet-stream; name=v1-0001-postgres_fdw-function-to-discard-cached-connections.patchDownload
From 0d160ccec962232033417ce3c86b9443a62ea802 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 23 Nov 2020 11:50:42 +0530
Subject: [PATCH v1] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
---
contrib/postgres_fdw/connection.c | 100 +++++++++++++++++++++
contrib/postgres_fdw/postgres_fdw--1.0.sql | 10 +++
2 files changed, 110 insertions(+)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ab3226287d..4f79897cc3 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -73,6 +74,11 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +100,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(int cacheid, uint32 hashvalue,
+ bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1325,3 +1333,95 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * Disconnects the cached connections.
+ *
+ * If server name is provided as input, it disconnects the associated cached
+ * connections. Otherwise all the cached connections are disconnected and the
+ * cache is destroyed.
+ *
+ * Returns false if the server name is provided but the cache is empty or
+ * no associated entry is found in the cache or cache is empty. In all other
+ * cases true is returned.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (ConnectionHash == NULL)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (server != NULL)
+ {
+ uint32 hashvalue;
+
+ hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ result = disconnect_cached_connections(FOREIGNSERVEROID,
+ hashvalue,
+ false);
+ }
+ }
+ else
+ result = disconnect_cached_connections(-1, 0, true);
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * If all is true, all the cached connections are disconnected and cache is
+ * destroyed. Otherwise, the entries with the given cacheid and hashvalue are
+ * disconnected.
+ *
+ * Returns true in the following cases: either the cache is destroyed now or
+ * at least a single entry with the given hashvalue exists. In all other cases
+ * false is returned.
+ */
+bool disconnect_cached_connections(int cacheid, uint32 hashvalue, bool all)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ if (ConnectionHash == NULL)
+ return false;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ if (all || (!all && cacheid == FOREIGNSERVEROID &&
+ entry->server_hashvalue == hashvalue))
+ {
+ if (entry->conn != NULL &&
+ !all && cacheid == FOREIGNSERVEROID &&
+ entry->server_hashvalue == hashvalue)
+ result = true;
+
+ if (entry->conn != NULL)
+ disconnect_pg_server(entry);
+ }
+ }
+
+ if (all)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ result = true;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql
index a0f0fc1bf4..9f70ed1c32 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0.sql
@@ -16,3 +16,13 @@ LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER postgres_fdw
HANDLER postgres_fdw_handler
VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
--
2.25.1
v1-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache-connections.patchapplication/octet-stream; name=v1-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache-connections.patchDownload
From 8c875725c04ec3b86a041d7ffac8d7d3091f3a29 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 23 Nov 2020 11:53:33 +0530
Subject: [PATCH v1] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 14 ++++++++++++--
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++++++++++++
contrib/postgres_fdw/postgres_fdw.h | 3 +++
3 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 4f79897cc3..eae5113b60 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -60,6 +60,8 @@ typedef struct ConnCacheEntry
bool invalidated; /* true if reconnect is pending */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Is this connection used in current xact?*/
+ bool used_in_current_xact;
} ConnCacheEntry;
/*
@@ -261,6 +263,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ entry->used_in_current_xact = true;
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -284,6 +288,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->used_in_current_xact = false;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -954,13 +959,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
* If the connection isn't in a good idle state, discard it to
* recover. Next GetConnection will open a new connection.
*/
- if (PQstatus(entry->conn) != CONNECTION_OK ||
+ if ((PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
- entry->changing_xact_state)
+ entry->changing_xact_state) ||
+ (entry->used_in_current_xact &&
+ !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}
+
+ if (entry->used_in_current_xact)
+ entry->used_in_current_xact = false;
}
/*
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9c5aaacc51..05c8817224 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -301,6 +301,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -505,6 +507,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
--
2.25.1
v1-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v1-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From 1d3304f680a1358f1d1f7a5151bb4b6e4446a486 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 23 Nov 2020 11:57:04 +0530
Subject: [PATCH v1] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 16 +++++++++++++++-
contrib/postgres_fdw/option.c | 9 ++++++++-
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eae5113b60..dfea4b3da5 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
uint32 mapping_hashvalue; /* hash value of user mapping OID */
/* Is this connection used in current xact?*/
bool used_in_current_xact;
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -123,6 +125,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -265,6 +269,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
entry->used_in_current_xact = true;
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -289,6 +302,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->used_in_current_xact = false;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -963,7 +977,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state) ||
(entry->used_in_current_xact &&
- !keep_connections))
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1a03e02263..0fe2eff878 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
--
2.25.1
v1-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patchapplication/octet-stream; name=v1-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patchDownload
From c522b63967f97d4c8fd54f6ebbd0db557d6b40d9 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 23 Nov 2020 11:59:32 +0530
Subject: [PATCH v1] postgre_fdw connection cache discard tests and
documentation
This patch adds the tests and documentation related to connection
cache discard feature.
---
.../postgres_fdw/expected/postgres_fdw.out | 152 +++++++++++++++++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 87 ++++++++++
doc/src/sgml/postgres-fdw.sgml | 117 ++++++++++++++
3 files changed, 355 insertions(+), 1 deletion(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..ded26388e6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9035,3 +9035,153 @@ ERROR: 08006
COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
+-- ===================================================================
+-- disconnect connections that are cached/kept by the local session
+-- ===================================================================
+-- Change application names of remote connections to special ones so that we
+-- can easily check for their existence.
+ALTER SERVER loopback OPTIONS (SET application_name 'fdw_disconnect_cached_conn_1');
+ALTER SERVER loopback2 OPTIONS (application_name 'fdw_disconnect_cached_conn_2');
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- Connection related to loopback server is closed by the local session at the
+-- end of xact as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+-- Connection related to loopback2 server is cached by the local session as the
+-- keep_connection is on.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_2
+(1 row)
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections that are made.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Connection related to loopback2 server are closed at the end of xact.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------
+(0 rows)
+
+-- Connections from hereafter are cached.
+SET postgres_fdw.keep_connections TO on;
+-- Connection related to loopback server is cached.
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- Connections related to loopback and loopback2 are cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection related to loopback server is disconnected. Connection related to
+-- loopback2 server still exists.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_2
+(1 row)
+
+-- Make loopback server connection again. Now, both loopback and loopback2
+-- server connections exist in the local session.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_1
+(1 row)
+
+-- Discard all the connections i.e. connections related to loopback and
+-- loopback2 server.
+SELECT postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both the connections should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------
+(0 rows)
+
+-- The server name provided doesn't exist, so false is returned.
+SELECT postgres_fdw_disconnect('unknownserver');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7581c5417b..a2b3e53bba 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2697,3 +2697,90 @@ COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
+
+-- ===================================================================
+-- disconnect connections that are cached/kept by the local session
+-- ===================================================================
+
+-- Change application names of remote connections to special ones so that we
+-- can easily check for their existence.
+ALTER SERVER loopback OPTIONS (SET application_name 'fdw_disconnect_cached_conn_1');
+ALTER SERVER loopback2 OPTIONS (application_name 'fdw_disconnect_cached_conn_2');
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- Connection related to loopback server is closed by the local session at the
+-- end of xact as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Connection related to loopback2 server is cached by the local session as the
+-- keep_connection is on.
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections that are made.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Connection related to loopback2 server are closed at the end of xact.
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Connections from hereafter are cached.
+SET postgres_fdw.keep_connections TO on;
+
+-- Connection related to loopback server is cached.
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- Connections related to loopback and loopback2 are cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection related to loopback server is disconnected. Connection related to
+-- loopback2 server still exists.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Make loopback server connection again. Now, both loopback and loopback2
+-- server connections exist in the local session.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Discard all the connections i.e. connections related to loopback and
+-- loopback2 server.
+SELECT postgres_fdw_disconnect();
+
+-- Both the connections should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- The server name provided doesn't exist, so false is returned.
+SELECT postgres_fdw_disconnect('unknownserver');
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..0c9f62e49a 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,97 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
+
+ </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the connection previously made to the foreign server and
+ returns <literal>true</literal>. If there is no associated connection exists
+ for the given foreign server, then <literal>false</literal> is returned.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the connections
+ previously made to the foreign servers and returns <literal>true</literal>.
+ If there are no previous connections kept by the local session, then
+ <literal>false</literal> is returned.
+ </para>
+
+</sect2>
+
+ <sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that when <varname>postgres_fdw.keep_connections</varname> is set to
+ off, <filename>postgres_fdw</filename> discards either the connections
+ that are made previously and will be used by the local session or the
+ connections that will be made newly. But the connections that are made
+ previously and kept, but not used after this parameter is set to off, are
+ not discarded. To discard them, use
+ <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+
</sect2>
<sect2>
@@ -490,6 +581,32 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following ways to remove the connections to the remote servers and
+ so the remote sessions:
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is
+ discarded at the end of the transaction.
+
+ A GUC, <varname>postgres_fdw.keep_connections</varname>, default being
+ <literal>on</literal>, when set to <literal>off</literal>, the local session
+ doesn't keep remote connections that are made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server name.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
Hi,
On 2020-11-23 09:48, Bharath Rupireddy wrote:
Here is how I'm making 4 separate patches:
1. new function and it's documentation.
2. GUC and it's documentation.
3. server level option and it's documentation.
4. test cases for all of the above patches.Hi, I'm attaching the patches here. Note that, though the code changes
for this feature are small, I divided them up as separate patches to
make review easy.v1-0001-postgres_fdw-function-to-discard-cached-connections.patch
This patch looks pretty straightforward for me, but there are some
things to be addressed IMO:
+ server = GetForeignServerByName(servername, true);
+
+ if (server != NULL)
+ {
Yes, you return a false if no server was found, but for me it worth
throwing an error in this case as, for example, dblink does in the
dblink_disconnect().
+ result = disconnect_cached_connections(FOREIGNSERVEROID,
+ hashvalue,
+ false);
+ if (all || (!all && cacheid == FOREIGNSERVEROID &&
+ entry->server_hashvalue == hashvalue))
+ {
+ if (entry->conn != NULL &&
+ !all && cacheid == FOREIGNSERVEROID &&
+ entry->server_hashvalue == hashvalue)
These conditions look bulky for me. First, you pass FOREIGNSERVEROID to
disconnect_cached_connections(), but actually it just duplicates 'all'
flag, since when it is 'FOREIGNSERVEROID', then 'all == false'; when it
is '-1', then 'all == true'. That is all, there are only two calls of
disconnect_cached_connections(). That way, it seems that we should keep
only 'all' flag at least for now, doesn't it?
Second, I think that we should just rewrite this if statement in order
to simplify it and make more readable, e.g.:
if ((all || entry->server_hashvalue == hashvalue) &&
entry->conn != NULL)
{
disconnect_pg_server(entry);
result = true;
}
+ if (all)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ result = true;
+ }
Also, I am still not sure that it is a good idea to destroy the whole
cache even in 'all' case, but maybe others will have a different
opinion.
v1-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache-connections.patch
+ entry->changing_xact_state) ||
+ (entry->used_in_current_xact &&
+ !keep_connections))
I am not sure, but I think, that instead of adding this additional flag
into ConnCacheEntry structure we can look on entry->xact_depth and use
local:
bool used_in_current_xact = entry->xact_depth > 0;
for exactly the same purpose. Since we set entry->xact_depth to zero at
the end of xact, then it was used if it is not zero. It is set to 1 by
begin_remote_xact() called by GetConnection(), so everything seems to be
fine.
Otherwise, both patches seem to be working as expected. I am going to
have a look on the last two patches a bit later.
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
Thanks for the review comments.
On Mon, Nov 23, 2020 at 9:57 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
v1-0001-postgres_fdw-function-to-discard-cached-connections.patch
This patch looks pretty straightforward for me, but there are some
things to be addressed IMO:+ server = GetForeignServerByName(servername, true); + + if (server != NULL) + {Yes, you return a false if no server was found, but for me it worth
throwing an error in this case as, for example, dblink does in the
dblink_disconnect().
dblink_disconnect() "Returns status, which is always OK (since any
error causes the function to throw an error instead of returning)."
This behaviour doesn't seem okay to me.
Since we throw true/false, I would prefer to throw a warning(with a
reason) while returning false over an error.
+ result = disconnect_cached_connections(FOREIGNSERVEROID, + hashvalue, + false);+ if (all || (!all && cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue)) + { + if (entry->conn != NULL && + !all && cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue)These conditions look bulky for me. First, you pass FOREIGNSERVEROID to
disconnect_cached_connections(), but actually it just duplicates 'all'
flag, since when it is 'FOREIGNSERVEROID', then 'all == false'; when it
is '-1', then 'all == true'. That is all, there are only two calls of
disconnect_cached_connections(). That way, it seems that we should keep
only 'all' flag at least for now, doesn't it?
I added cachid as an argument to disconnect_cached_connections() for
reusability. Say, someone wants to use it with a user mapping then
they can pass cacheid USERMAPPINGOID, hash value of user mapping. The
cacheid == USERMAPPINGOID && entry->mapping_hashvalue == hashvalue can
be added to disconnect_cached_connections().
Second, I think that we should just rewrite this if statement in order
to simplify it and make more readable, e.g.:if ((all || entry->server_hashvalue == hashvalue) &&
entry->conn != NULL)
{
disconnect_pg_server(entry);
result = true;
}
Yeah. I will add a cacheid check and change it to below.
if ((all || (cacheid == FOREIGNSERVEROID &&
entry->server_hashvalue == hashvalue)) &&
entry->conn != NULL)
{
disconnect_pg_server(entry);
result = true;
}
+ if (all) + { + hash_destroy(ConnectionHash); + ConnectionHash = NULL; + result = true; + }Also, I am still not sure that it is a good idea to destroy the whole
cache even in 'all' case, but maybe others will have a different
opinion.
I think we should. When we disconnect all the connections, then no
point in keeping the connection cache hash data structure. If required
it gets created at the next first foreign server usage in the same
session. And also, hash_destroy() frees up memory context unlike
hash_search with HASH_REMOVE, so we can save a bit of memory.
v1-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache-connections.patch
+ entry->changing_xact_state) || + (entry->used_in_current_xact && + !keep_connections))I am not sure, but I think, that instead of adding this additional flag
into ConnCacheEntry structure we can look on entry->xact_depth and use
local:bool used_in_current_xact = entry->xact_depth > 0;
for exactly the same purpose. Since we set entry->xact_depth to zero at
the end of xact, then it was used if it is not zero. It is set to 1 by
begin_remote_xact() called by GetConnection(), so everything seems to be
fine.
I missed this. Thanks, we can use the local variable as you suggested.
I will change it.
Otherwise, both patches seem to be working as expected. I am going to
have a look on the last two patches a bit later.
Thanks. I will work on the comments so far and post updated patches soon.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020-11-24 06:52, Bharath Rupireddy wrote:
Thanks for the review comments.
On Mon, Nov 23, 2020 at 9:57 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:v1-0001-postgres_fdw-function-to-discard-cached-connections.patch
This patch looks pretty straightforward for me, but there are some
things to be addressed IMO:+ server = GetForeignServerByName(servername, true); + + if (server != NULL) + {Yes, you return a false if no server was found, but for me it worth
throwing an error in this case as, for example, dblink does in the
dblink_disconnect().dblink_disconnect() "Returns status, which is always OK (since any
error causes the function to throw an error instead of returning)."
This behaviour doesn't seem okay to me.Since we throw true/false, I would prefer to throw a warning(with a
reason) while returning false over an error.
I thought about something a bit more sophisticated:
1) Return 'true' if there were open connections and we successfully
closed them.
2) Return 'false' in the no-op case, i.e. there were no open
connections.
3) Rise an error if something went wrong. And non-existing server case
belongs to this last category, IMO.
That looks like a semantically correct behavior, but let us wait for any
other opinion.
+ result = disconnect_cached_connections(FOREIGNSERVEROID, + hashvalue, + false);+ if (all || (!all && cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue)) + { + if (entry->conn != NULL && + !all && cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue)These conditions look bulky for me. First, you pass FOREIGNSERVEROID
to
disconnect_cached_connections(), but actually it just duplicates 'all'
flag, since when it is 'FOREIGNSERVEROID', then 'all == false'; when
it
is '-1', then 'all == true'. That is all, there are only two calls of
disconnect_cached_connections(). That way, it seems that we should
keep
only 'all' flag at least for now, doesn't it?I added cachid as an argument to disconnect_cached_connections() for
reusability. Say, someone wants to use it with a user mapping then
they can pass cacheid USERMAPPINGOID, hash value of user mapping. The
cacheid == USERMAPPINGOID && entry->mapping_hashvalue == hashvalue can
be added to disconnect_cached_connections().
Yeah, I have got your point and motivation to add this argument, but how
we can use it? To disconnect all connections belonging to some specific
user mapping? But any user mapping is hard bound to some foreign server,
AFAIK, so we can pass serverid-based hash in this case.
In the case of pgfdw_inval_callback() this argument makes sense, since
syscache callbacks work that way, but here I can hardly imagine a case
where we can use it. Thus, it still looks as a preliminary complication
for me, since we do not have plans to use it, do we? Anyway, everything
seems to be working fine, so it is up to you to keep this additional
argument.
v1-0003-postgres_fdw-server-level-option-keep_connection.patch
This patch adds a new server level option, keep_connection, default
being on, when set to off, the local session doesn't cache the
connections associated with the foreign server.
This patch looks good to me, except one note:
(entry->used_in_current_xact &&
- !keep_connections))
+ (!keep_connections || !entry->keep_connection)))
{
Following this logic:
1) If keep_connections == true, then per-server keep_connection has a
*higher* priority, so one can disable caching of a single foreign
server.
2) But if keep_connections == false, then it works like a global switch
off indifferently of per-server keep_connection's, i.e. they have a
*lower* priority.
It looks fine for me, at least I cannot propose anything better, but
maybe it should be documented in 0004?
v1-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patch
This patch adds the tests and documentation related to this feature.
I have not read all texts thoroughly, but what caught my eye:
+ A GUC, <varname>postgres_fdw.keep_connections</varname>, default
being
+ <literal>on</literal>, when set to <literal>off</literal>, the local
session
I think that GUC acronym is used widely only in the source code and
Postgres docs tend to do not use it at all, except from acronyms list
and a couple of 'GUC parameters' collocation usage. And it never used in
a singular form there, so I think that it should be rather:
A configuration parameter,
<varname>postgres_fdw.keep_connections</varname>, default being...
+ <para>
+ Note that when <varname>postgres_fdw.keep_connections</varname>
is set to
+ off, <filename>postgres_fdw</filename> discards either the
connections
+ that are made previously and will be used by the local session or
the
+ connections that will be made newly. But the connections that are
made
+ previously and kept, but not used after this parameter is set to
off, are
+ not discarded. To discard them, use
+ <function>postgres_fdw_disconnect</function> function.
+ </para>
The whole paragraph is really difficult to follow. It could be something
like that:
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname>
to
off does not discard any previously made and still open
connections immediately.
They will be closed only at the end of a future transaction, which
operated on them.
To close all connections immediately use
<function>postgres_fdw_disconnect</function> function.
</para>
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
On Wed, Nov 25, 2020 at 2:43 AM Alexey Kondratov <a.kondratov@postgrespro.ru>
wrote:
On 2020-11-24 06:52, Bharath Rupireddy wrote:
Thanks for the review comments.
On Mon, Nov 23, 2020 at 9:57 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:v1-0001-postgres_fdw-function-to-discard-cached-connections.patch
This patch looks pretty straightforward for me, but there are some
things to be addressed IMO:+ server = GetForeignServerByName(servername, true); + + if (server != NULL) + {Yes, you return a false if no server was found, but for me it worth
throwing an error in this case as, for example, dblink does in the
dblink_disconnect().dblink_disconnect() "Returns status, which is always OK (since any
error causes the function to throw an error instead of returning)."
This behaviour doesn't seem okay to me.Since we throw true/false, I would prefer to throw a warning(with a
reason) while returning false over an error.I thought about something a bit more sophisticated:
1) Return 'true' if there were open connections and we successfully
closed them.
2) Return 'false' in the no-op case, i.e. there were no open
connections.
3) Rise an error if something went wrong. And non-existing server case
belongs to this last category, IMO.That looks like a semantically correct behavior, but let us wait for any
other opinion.+ result = disconnect_cached_connections(FOREIGNSERVEROID, + hashvalue, + false);+ if (all || (!all && cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue)) + { + if (entry->conn != NULL && + !all && cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue)These conditions look bulky for me. First, you pass FOREIGNSERVEROID
to
disconnect_cached_connections(), but actually it just duplicates 'all'
flag, since when it is 'FOREIGNSERVEROID', then 'all == false'; when
it
is '-1', then 'all == true'. That is all, there are only two calls of
disconnect_cached_connections(). That way, it seems that we should
keep
only 'all' flag at least for now, doesn't it?I added cachid as an argument to disconnect_cached_connections() for
reusability. Say, someone wants to use it with a user mapping then
they can pass cacheid USERMAPPINGOID, hash value of user mapping. The
cacheid == USERMAPPINGOID && entry->mapping_hashvalue == hashvalue can
be added to disconnect_cached_connections().Yeah, I have got your point and motivation to add this argument, but how
we can use it? To disconnect all connections belonging to some specific
user mapping? But any user mapping is hard bound to some foreign server,
AFAIK, so we can pass serverid-based hash in this case.In the case of pgfdw_inval_callback() this argument makes sense, since
syscache callbacks work that way, but here I can hardly imagine a case
where we can use it. Thus, it still looks as a preliminary complication
for me, since we do not have plans to use it, do we? Anyway, everything
seems to be working fine, so it is up to you to keep this additional
argument.v1-0003-postgres_fdw-server-level-option-keep_connection.patch
This patch adds a new server level option, keep_connection, default
being on, when set to off, the local session doesn't cache the
connections associated with the foreign server.This patch looks good to me, except one note:
(entry->used_in_current_xact && - !keep_connections)) + (!keep_connections || !entry->keep_connection))) {Following this logic:
1) If keep_connections == true, then per-server keep_connection has a
*higher* priority, so one can disable caching of a single foreign
server.2) But if keep_connections == false, then it works like a global switch
off indifferently of per-server keep_connection's, i.e. they have a
*lower* priority.It looks fine for me, at least I cannot propose anything better, but
maybe it should be documented in 0004?v1-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patch
This patch adds the tests and documentation related to this feature.
I have not read all texts thoroughly, but what caught my eye:
+ A GUC, <varname>postgres_fdw.keep_connections</varname>, default being + <literal>on</literal>, when set to <literal>off</literal>, the local sessionI think that GUC acronym is used widely only in the source code and
Postgres docs tend to do not use it at all, except from acronyms list
and a couple of 'GUC parameters' collocation usage. And it never used in
a singular form there, so I think that it should be rather:A configuration parameter,
<varname>postgres_fdw.keep_connections</varname>, default being...
A quick thought here.
Would it make sense to add a hook in the DISCARD ALL implementation that
postgres_fdw can register for?
There's precedent here, since DISCARD ALL already has the same effect as
SELECT pg_advisory_unlock_all(); amongst other things.
On Wed, Nov 25, 2020 at 7:24 AM Craig Ringer
<craig.ringer@enterprisedb.com> wrote:
A quick thought here.
Would it make sense to add a hook in the DISCARD ALL implementation that postgres_fdw can register for?
There's precedent here, since DISCARD ALL already has the same effect as SELECT pg_advisory_unlock_all(); amongst other things.
IIUC, then it is like a core(server) function doing some work for the
postgres_fdw module. Earlier in the discussion, one point raised was
that it's better not to have core handling something related to
postgres_fdw. This is the reason we have come up with postgres_fdw
specific function and a GUC, which get defined when extension is
created. Similarly, dblink also has it's own bunch of functions one
among them is dblink_disconnect().
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020-11-25 06:17, Bharath Rupireddy wrote:
On Wed, Nov 25, 2020 at 7:24 AM Craig Ringer
<craig.ringer@enterprisedb.com> wrote:A quick thought here.
Would it make sense to add a hook in the DISCARD ALL implementation
that postgres_fdw can register for?There's precedent here, since DISCARD ALL already has the same effect
as SELECT pg_advisory_unlock_all(); amongst other things.IIUC, then it is like a core(server) function doing some work for the
postgres_fdw module. Earlier in the discussion, one point raised was
that it's better not to have core handling something related to
postgres_fdw. This is the reason we have come up with postgres_fdw
specific function and a GUC, which get defined when extension is
created. Similarly, dblink also has it's own bunch of functions one
among them is dblink_disconnect().
If I have got Craig correctly, he proposed that we already have a
DISCARD ALL statement, which is processed by DiscardAll(), and it
releases internal resources known from the core perspective. That way,
we can introduce a general purpose hook DiscardAll_hook(), so
postgres_fdw can get use of it to clean up its own resources
(connections in our context) if needed. In other words, it is not like a
core function doing some work for the postgres_fdw module, but rather
like a callback/hook, that postgres_fdw is able to register to do some
additional work.
It can be a good replacement for 0001, but won't it be already an
overkill to drop all local caches along with remote connections? I mean,
that it would be a nice to have hook from the extensibility perspective,
but postgres_fdw_disconnect() still makes sense, since it does a very
narrow and specific job.
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
On Wed, Nov 25, 2020 at 12:13 AM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
1) Return 'true' if there were open connections and we successfully
closed them.
2) Return 'false' in the no-op case, i.e. there were no open
connections.
3) Rise an error if something went wrong. And non-existing server case
belongs to this last category, IMO.
Done this way.
I am not sure, but I think, that instead of adding this additional flag
into ConnCacheEntry structure we can look on entry->xact_depth and use
local:bool used_in_current_xact = entry->xact_depth > 0;
for exactly the same purpose. Since we set entry->xact_depth to zero at
the end of xact, then it was used if it is not zero. It is set to 1 by
begin_remote_xact() called by GetConnection(), so everything seems to be
fine.
Done.
In the case of pgfdw_inval_callback() this argument makes sense, since
syscache callbacks work that way, but here I can hardly imagine a case
where we can use it. Thus, it still looks as a preliminary complication
for me, since we do not have plans to use it, do we? Anyway, everything
seems to be working fine, so it is up to you to keep this additional
argument.
Removed the cacheid variable.
Following this logic:
1) If keep_connections == true, then per-server keep_connection has a
*higher* priority, so one can disable caching of a single foreign
server.2) But if keep_connections == false, then it works like a global switch
off indifferently of per-server keep_connection's, i.e. they have a
*lower* priority.It looks fine for me, at least I cannot propose anything better, but
maybe it should be documented in 0004?
Done.
I think that GUC acronym is used widely only in the source code and
Postgres docs tend to do not use it at all, except from acronyms list
and a couple of 'GUC parameters' collocation usage. And it never used in
a singular form there, so I think that it should be rather:A configuration parameter,
<varname>postgres_fdw.keep_connections</varname>, default being...
Done.
The whole paragraph is really difficult to follow. It could be something
like that:<para>
Note that setting <varname>postgres_fdw.keep_connections</varname>
to
off does not discard any previously made and still open
connections immediately.
They will be closed only at the end of a future transaction, which
operated on them.To close all connections immediately use
<function>postgres_fdw_disconnect</function> function.
</para>
Done.
Attaching the v2 patch set. Please review it further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v2-0001-postgres_fdw-connection-cache-disconnect-function.patchapplication/octet-stream; name=v2-0001-postgres_fdw-connection-cache-disconnect-function.patchDownload
From 10c4dffefeecac50e114c3cc96ff43e87f77b964 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 27 Nov 2020 06:53:18 +0530
Subject: [PATCH v1] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
---
contrib/postgres_fdw/connection.c | 98 ++++++++++++++++++++++
contrib/postgres_fdw/postgres_fdw--1.0.sql | 10 +++
2 files changed, 108 insertions(+)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ab3226287d..7e7233650c 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -73,6 +74,11 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +100,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1325,3 +1332,94 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * Disconnects the cached connections.
+ *
+ * If server name is provided as input, it disconnects the associated cached
+ * connections. Otherwise all the cached connections are disconnected and the
+ * cache is destroyed.
+ *
+ * Returns false if the cache is empty or if the cache is non empty and server
+ * name is provided and it exists but it has no associated entry in the cache.
+ * An error is emitted when the given foreign server does not exist.
+ * In all other cases true is returned.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (server != NULL)
+ {
+ uint32 hashvalue;
+
+ hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ if (ConnectionHash != NULL)
+ result = disconnect_cached_connections(hashvalue, false);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+ }
+ else
+ {
+ if (ConnectionHash != NULL)
+ result = disconnect_cached_connections(0, true);
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * If all is true, all the cached connections are disconnected and cache is
+ * destroyed. Otherwise, the entries with the given hashvalue are disconnected.
+ *
+ * Returns true in the following cases: either the cache is destroyed now or
+ * at least a single entry with the given hashvalue exists. In all other cases
+ * false is returned.
+ */
+bool disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ /*
+ * We do not come here if the ConnectionHash is NULL. We handle it in the
+ * caller.
+ */
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn != NULL)
+ {
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ if (all)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ result = true;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql
index a0f0fc1bf4..9f70ed1c32 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0.sql
@@ -16,3 +16,13 @@ LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER postgres_fdw
HANDLER postgres_fdw_handler
VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
--
2.25.1
v2-0004-postgre_fdw-connection-cache-discard-tests-and-doc.patchapplication/octet-stream; name=v2-0004-postgre_fdw-connection-cache-discard-tests-and-doc.patchDownload
From 932d0c781ee60d8cc98cda35a23eba553df6e611 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 27 Nov 2020 07:25:13 +0530
Subject: [PATCH v2] postgres_fdw connection cache discard tests and
documentation
---
.../postgres_fdw/expected/postgres_fdw.out | 169 +++++++++++++++++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 96 ++++++++++
doc/src/sgml/postgres-fdw.sgml | 125 +++++++++++++
3 files changed, 389 insertions(+), 1 deletion(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..de95a6f886 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9035,3 +9035,170 @@ ERROR: 08006
COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
+-- ===================================================================
+-- disconnect connections that are cached/kept by the local session
+-- ===================================================================
+-- Change application names of remote connections to special ones so that we
+-- can easily check for their existence.
+ALTER SERVER loopback OPTIONS (SET application_name 'fdw_disconnect_cached_conn_1');
+ALTER SERVER loopback2 OPTIONS (application_name 'fdw_disconnect_cached_conn_2');
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+-- loopback2 server connection is cached by the local session as the
+-- keep_connection is on.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_2
+(1 row)
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- loopback2 server connection is closed at the end of xact.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------
+(0 rows)
+
+-- Connections from hereafter are cached.
+SET postgres_fdw.keep_connections TO on;
+-- loopback server connection is cached.
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- Connections are cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- loopback server connection is disconnected and true is returned. loopback2
+-- server connection still exists.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_2
+(1 row)
+
+-- Cache exists, but the loopback server connection is not present in it,
+-- so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Make loopback server connection again. Now, both loopback and loopback2
+-- server connections exist in the local session.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_1
+(1 row)
+
+-- Discard all the connections. True is returned.
+SELECT postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both the connections should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------
+(0 rows)
+
+-- Cache does not exist. Try to discard, false is returned.
+SELECT postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache does not exist, but the loopback server exists, so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- The server name provided doesn't exist, an error is expected.
+SELECT postgres_fdw_disconnect('unknownserver');
+ERROR: foreign server "unknownserver" does not exist
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7581c5417b..2b1472c3c2 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2697,3 +2697,99 @@ COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
+
+-- ===================================================================
+-- disconnect connections that are cached/kept by the local session
+-- ===================================================================
+
+-- Change application names of remote connections to special ones so that we
+-- can easily check for their existence.
+ALTER SERVER loopback OPTIONS (SET application_name 'fdw_disconnect_cached_conn_1');
+ALTER SERVER loopback2 OPTIONS (application_name 'fdw_disconnect_cached_conn_2');
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- loopback2 server connection is cached by the local session as the
+-- keep_connection is on.
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- loopback2 server connection is closed at the end of xact.
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Connections from hereafter are cached.
+SET postgres_fdw.keep_connections TO on;
+
+-- loopback server connection is cached.
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- Connections are cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- loopback server connection is disconnected and true is returned. loopback2
+-- server connection still exists.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Cache exists, but the loopback server connection is not present in it,
+-- so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- Make loopback server connection again. Now, both loopback and loopback2
+-- server connections exist in the local session.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Discard all the connections. True is returned.
+SELECT postgres_fdw_disconnect();
+
+-- Both the connections should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Cache does not exist. Try to discard, false is returned.
+SELECT postgres_fdw_disconnect();
+
+-- Cache does not exist, but the loopback server exists, so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- The server name provided doesn't exist, an error is expected.
+SELECT postgres_fdw_disconnect('unknownserver');
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..60b9d2bf7b 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,104 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
+
+ </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the connection previously made to the foreign server and
+ returns <literal>true</literal>. If there is no associated connection exists
+ for the given foreign server, then <literal>false</literal> is returned. If
+ no foreign server exists with the given name, then an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the connections
+ previously made to the foreign servers and returns <literal>true</literal>.
+ If there are no previous connections kept by the local session, then
+ <literal>false</literal> is returned.
+ </para>
+
+</sect2>
+
+ <sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+
</sect2>
<sect2>
@@ -490,6 +588,33 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following ways to remove the connections to the remote servers and
+ so the remote sessions:
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is
+ discarded at the end of the transaction.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
v2-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache.patchapplication/octet-stream; name=v2-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache.patchDownload
From 1b3571df2d1b1e8675aa20d59e2fe9aff11bbcf0 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 27 Nov 2020 06:02:54 +0530
Subject: [PATCH v2] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 17 +++++++++++++----
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++++++++++++
contrib/postgres_fdw/postgres_fdw.h | 3 +++
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 975d7c648e..85217188b5 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -807,6 +807,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -817,6 +818,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -950,15 +953,21 @@ pgfdw_xact_callback(XactEvent event, void *arg)
entry->xact_depth = 0;
/*
- * If the connection isn't in a good idle state, discard it to
- * recover. Next GetConnection will open a new connection.
+ * If the connection isn't in a good idle state, discard it to recover.
+ * Next GetConnection will open a new connection when required.
+ *
+ * If the keep_connections GUC is false and this connection is used in
+ * current xact, then also discard it.
*/
- if (PQstatus(entry->conn) != CONNECTION_OK ||
+ if ((PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
- entry->changing_xact_state)
+ entry->changing_xact_state) ||
+ (used_in_current_xact &&
+ !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index b6c72e1d1e..160f17972b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
--
2.25.1
v2-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v2-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From ec2b2d7d78a5ce9d75eb253ae3db45cc4a9d7a13 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 27 Nov 2020 06:18:42 +0530
Subject: [PATCH v2] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 22 +++++++++++++++++++---
contrib/postgres_fdw/option.c | 9 ++++++++-
2 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 85217188b5..c13e1a3cc8 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -60,6 +60,8 @@ typedef struct ConnCacheEntry
bool invalidated; /* true if reconnect is pending */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -120,6 +122,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -260,6 +264,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -283,6 +296,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,14 +970,16 @@ pgfdw_xact_callback(XactEvent event, void *arg)
* If the connection isn't in a good idle state, discard it to recover.
* Next GetConnection will open a new connection when required.
*
- * If the keep_connections GUC is false and this connection is used in
- * current xact, then also discard it.
+ * Also discard the connection if it is used in current xact and if the
+ * GUC is set to off or if the GUC is on but the server level option is
+ * set to off. Note that keep_connections GUC overrides the server
+ * level keep_connection option.
*/
if ((PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state) ||
(used_in_current_xact &&
- !keep_connections))
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1a03e02263..0fe2eff878 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
--
2.25.1
On 2020/11/27 11:12, Bharath Rupireddy wrote:
On Wed, Nov 25, 2020 at 12:13 AM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:1) Return 'true' if there were open connections and we successfully
closed them.
2) Return 'false' in the no-op case, i.e. there were no open
connections.
3) Rise an error if something went wrong. And non-existing server case
belongs to this last category, IMO.Done this way.
I am not sure, but I think, that instead of adding this additional flag
into ConnCacheEntry structure we can look on entry->xact_depth and use
local:bool used_in_current_xact = entry->xact_depth > 0;
for exactly the same purpose. Since we set entry->xact_depth to zero at
the end of xact, then it was used if it is not zero. It is set to 1 by
begin_remote_xact() called by GetConnection(), so everything seems to be
fine.Done.
In the case of pgfdw_inval_callback() this argument makes sense, since
syscache callbacks work that way, but here I can hardly imagine a case
where we can use it. Thus, it still looks as a preliminary complication
for me, since we do not have plans to use it, do we? Anyway, everything
seems to be working fine, so it is up to you to keep this additional
argument.Removed the cacheid variable.
Following this logic:
1) If keep_connections == true, then per-server keep_connection has a
*higher* priority, so one can disable caching of a single foreign
server.2) But if keep_connections == false, then it works like a global switch
off indifferently of per-server keep_connection's, i.e. they have a
*lower* priority.It looks fine for me, at least I cannot propose anything better, but
maybe it should be documented in 0004?Done.
I think that GUC acronym is used widely only in the source code and
Postgres docs tend to do not use it at all, except from acronyms list
and a couple of 'GUC parameters' collocation usage. And it never used in
a singular form there, so I think that it should be rather:A configuration parameter,
<varname>postgres_fdw.keep_connections</varname>, default being...Done.
The whole paragraph is really difficult to follow. It could be something
like that:<para>
Note that setting <varname>postgres_fdw.keep_connections</varname>
to
off does not discard any previously made and still open
connections immediately.
They will be closed only at the end of a future transaction, which
operated on them.To close all connections immediately use
<function>postgres_fdw_disconnect</function> function.
</para>Done.
Attaching the v2 patch set. Please review it further.
Regarding the 0001 patch, we should add the function that returns
the information of cached connections like dblink_get_connections(),
together with 0001 patch? Otherwise it's not easy for users to
see how many cached connections are and determine whether to
disconnect them or not. Sorry if this was already discussed before.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Dec 4, 2020 at 11:49 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching the v2 patch set. Please review it further.
Regarding the 0001 patch, we should add the function that returns
the information of cached connections like dblink_get_connections(),
together with 0001 patch? Otherwise it's not easy for users to
see how many cached connections are and determine whether to
disconnect them or not. Sorry if this was already discussed before.
Thanks for bringing this up. Exactly this is what I was thinking a few
days back. Say the new function postgres_fdw_get_connections() which
can return an array of server names whose connections exist in the
cache. Without this function, the user may not know how many
connections this backend has until he checks it manually on the remote
server.
Thoughts? If okay, I can code the function in the 0001 patch.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Dec 4, 2020 at 1:46 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Fri, Dec 4, 2020 at 11:49 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching the v2 patch set. Please review it further.
Regarding the 0001 patch, we should add the function that returns
the information of cached connections like dblink_get_connections(),
together with 0001 patch? Otherwise it's not easy for users to
see how many cached connections are and determine whether to
disconnect them or not. Sorry if this was already discussed before.Thanks for bringing this up. Exactly this is what I was thinking a few
days back. Say the new function postgres_fdw_get_connections() which
can return an array of server names whose connections exist in the
cache. Without this function, the user may not know how many
connections this backend has until he checks it manually on the remote
server.Thoughts? If okay, I can code the function in the 0001 patch.
Added a new function postgres_fdw_get_connections() into 0001 patch,
which returns a list of server names for which there exists an
existing open and active connection.
Attaching v3 patch set, please review it further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v3-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patchapplication/x-patch; name=v3-0004-postgres_fdw-connection-cache-discard-tests-and-documentation.patchDownload
From 0ededddba3ee80a968d51df8283525ff962440da Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 4 Dec 2020 16:22:06 +0530
Subject: [PATCH v3] postgres_fdw connection cache discard tests and
documentation
---
.../postgres_fdw/expected/postgres_fdw.out | 202 +++++++++++++++++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 118 ++++++++++
doc/src/sgml/postgres-fdw.sgml | 132 ++++++++++++
3 files changed, 451 insertions(+), 1 deletion(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..692da1c606 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9035,3 +9035,203 @@ ERROR: 08006
COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
+-- ===================================================================
+-- disconnect connections that are cached/kept by the local session
+-- ===================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Change application names of remote connections to special ones so that we
+-- can easily check for their existence.
+ALTER SERVER loopback OPTIONS (SET application_name 'fdw_disconnect_cached_conn_1');
+ALTER SERVER loopback2 OPTIONS (application_name 'fdw_disconnect_cached_conn_2');
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+-- loopback2 server connection is cached by the local session as the
+-- keep_connection is on.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_2
+(1 row)
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- loopback2 server connection is closed at the end of xact.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------
+(0 rows)
+
+-- Connections from hereafter are cached.
+SET postgres_fdw.keep_connections TO on;
+-- loopback server connection is cached.
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- Connections are cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return loopback and
+-- loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+ loopback
+ loopback2
+(2 rows)
+
+-- loopback server connection is disconnected and true is returned. loopback2
+-- server connection still exists.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_2
+(1 row)
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+ loopback2
+(1 row)
+
+-- Cache exists, but the loopback server connection is not present in it,
+-- so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Make loopback server connection again. Now, both loopback and loopback2
+-- server connections exist in the local session.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------------------
+ fdw_disconnect_cached_conn_1
+(1 row)
+
+-- Discard all the connections. True is returned.
+SELECT postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both the connections should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+ application_name
+------------------
+(0 rows)
+
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+ application_name
+------------------
+(0 rows)
+
+-- Cache does not exist. Try to discard, false is returned.
+SELECT postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- List all the existing cached connections. Should return NULL, as there are
+-- none exist.
+SELECT * FROM postgres_fdw_get_connections();
+ postgres_fdw_get_connections
+------------------------------
+
+(1 row)
+
+-- Cache does not exist, but the loopback server exists, so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- The server name provided doesn't exist, an error is expected.
+SELECT postgres_fdw_disconnect('unknownserver');
+ERROR: foreign server "unknownserver" does not exist
+-- Clean up
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7581c5417b..137edae4bb 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2697,3 +2697,121 @@ COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
+
+-- ===================================================================
+-- disconnect connections that are cached/kept by the local session
+-- ===================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Change application names of remote connections to special ones so that we
+-- can easily check for their existence.
+ALTER SERVER loopback OPTIONS (SET application_name 'fdw_disconnect_cached_conn_1');
+ALTER SERVER loopback2 OPTIONS (application_name 'fdw_disconnect_cached_conn_2');
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- loopback2 server connection is cached by the local session as the
+-- keep_connection is on.
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- loopback2 server connection is closed at the end of xact.
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Connections from hereafter are cached.
+SET postgres_fdw.keep_connections TO on;
+
+-- loopback server connection is cached.
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- Connections are cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- List all the existing cached connections. Should return loopback and
+-- loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- loopback server connection is disconnected and true is returned. loopback2
+-- server connection still exists.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- Connection should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the loopback server connection is not present in it,
+-- so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- Make loopback server connection again. Now, both loopback and loopback2
+-- server connections exist in the local session.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Connection should exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+
+-- Discard all the connections. True is returned.
+SELECT postgres_fdw_disconnect();
+
+-- Both the connections should not exist.
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_1';
+SELECT application_name FROM pg_stat_activity
+ WHERE application_name = 'fdw_disconnect_cached_conn_2';
+
+-- Cache does not exist. Try to discard, false is returned.
+SELECT postgres_fdw_disconnect();
+
+-- List all the existing cached connections. Should return NULL, as there are
+-- none exist.
+SELECT * FROM postgres_fdw_get_connections();
+
+-- Cache does not exist, but the loopback server exists, so false is returned.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- The server name provided doesn't exist, an error is expected.
+SELECT postgres_fdw_disconnect('unknownserver');
+
+-- Clean up
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..f3f4988454 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,111 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
+
+ </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array of the foreign server
+ names of all the open connections that are previously made to the foreign
+ servers. If there are no open connections, then <literal>NULL</literal> is
+ returned.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the open connection previously made to the foreign server and
+ returns <literal>true</literal>. If there is no associated connection exists
+ for the given foreign server, then <literal>false</literal> is returned. If
+ no foreign server exists with the given name, then an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the open connections that
+ are previously made to the foreign servers and returns <literal>true</literal>.
+ If there are no open connections, then <literal>false</literal> is returned.
+ </para>
+
+</sect2>
+
+ <sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+
</sect2>
<sect2>
@@ -490,6 +595,33 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following ways to remove the connections to the remote servers and
+ so the remote sessions:
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is
+ discarded at the end of the transaction.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
v3-0001-postgres_fdw-connection-cache-disconnect-function.patchapplication/x-patch; name=v3-0001-postgres_fdw-connection-cache-disconnect-function.patchDownload
From f890ebb605c8a2aa27dcf87e1b36cbb48594ce59 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 4 Dec 2020 16:24:57 +0530
Subject: [PATCH v3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/connection.c | 164 +++++++++++++++++++++
contrib/postgres_fdw/postgres_fdw--1.0.sql | 15 ++
2 files changed, 179 insertions(+)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ab3226287d..65695ebb67 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -14,6 +14,7 @@
#include "access/htup_details.h"
#include "access/xact.h"
+#include "catalog/pg_foreign_server.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "mb/pg_wchar.h"
@@ -22,6 +23,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -73,6 +75,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +102,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1325,3 +1334,158 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List all cached connections by corresponding foreign server names.
+ *
+ * Takes no paramaters as input. Returns an array of all foreign server names
+ * with which connections exist. Returns NULL, in case there are no cached
+ * connections at all.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+
+ if (ConnectionHash)
+ {
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ Form_pg_user_mapping umap;
+ HeapTuple umaptup;
+ Form_pg_foreign_server fsrv;
+ HeapTuple fsrvtup;
+
+ /* We only look for active and open remote connections. */
+ if (entry->invalidated || !entry->conn)
+ continue;
+
+ umaptup = SearchSysCache1(USERMAPPINGOID,
+ ObjectIdGetDatum(entry->key));
+
+ if (!HeapTupleIsValid(umaptup))
+ elog(ERROR, "cache lookup failed for user mapping with OID %u",
+ entry->key);
+
+ umap = (Form_pg_user_mapping) GETSTRUCT(umaptup);
+
+ fsrvtup = SearchSysCache1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(umap->umserver));
+
+ if (!HeapTupleIsValid(fsrvtup))
+ elog(ERROR, "cache lookup failed for foreign server with OID %u",
+ umap->umserver);
+
+ fsrv = (Form_pg_foreign_server) GETSTRUCT(fsrvtup);
+
+ /* stash away current value */
+ astate = accumArrayResult(astate,
+ CStringGetTextDatum(NameStr(fsrv->srvname)),
+ false, TEXTOID, CurrentMemoryContext);
+
+ ReleaseSysCache(umaptup);
+ ReleaseSysCache(fsrvtup);
+ }
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnects the cached connections.
+ *
+ * If server name is provided as input, it disconnects the associated cached
+ * connections. Otherwise all the cached connections are disconnected and the
+ * cache is destroyed.
+ *
+ * Returns false if the cache is empty or if the cache is non empty and server
+ * name is provided and it exists but it has no associated entry in the cache.
+ * An error is emitted when the given foreign server does not exist.
+ * In all other cases true is returned.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (server)
+ {
+ uint32 hashvalue;
+
+ hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ if (ConnectionHash)
+ result = disconnect_cached_connections(hashvalue, false);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+ }
+ else
+ {
+ if (ConnectionHash)
+ result = disconnect_cached_connections(0, true);
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * If all is true, all the cached connections are disconnected and cache is
+ * destroyed. Otherwise, the entries with the given hashvalue are disconnected.
+ *
+ * Returns true in the following cases: either the cache is destroyed now or
+ * at least a single entry with the given hashvalue exists. In all other cases
+ * false is returned.
+ */
+bool disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ /*
+ * We do not come here if the ConnectionHash is NULL. We handle it in the
+ * caller.
+ */
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ if (all)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ result = true;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql
index a0f0fc1bf4..edbb5911d2 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0.sql
@@ -16,3 +16,18 @@ LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER postgres_fdw
HANDLER postgres_fdw_handler
VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS text[]
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
--
2.25.1
v3-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache.patchapplication/x-patch; name=v3-0002-postgres_fdw-add-keep_connections-GUC-to-not-cache.patchDownload
From 1b3571df2d1b1e8675aa20d59e2fe9aff11bbcf0 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 27 Nov 2020 06:02:54 +0530
Subject: [PATCH v2] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 17 +++++++++++++----
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++++++++++++
contrib/postgres_fdw/postgres_fdw.h | 3 +++
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 975d7c648e..85217188b5 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -807,6 +807,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -817,6 +818,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -950,15 +953,21 @@ pgfdw_xact_callback(XactEvent event, void *arg)
entry->xact_depth = 0;
/*
- * If the connection isn't in a good idle state, discard it to
- * recover. Next GetConnection will open a new connection.
+ * If the connection isn't in a good idle state, discard it to recover.
+ * Next GetConnection will open a new connection when required.
+ *
+ * If the keep_connections GUC is false and this connection is used in
+ * current xact, then also discard it.
*/
- if (PQstatus(entry->conn) != CONNECTION_OK ||
+ if ((PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
- entry->changing_xact_state)
+ entry->changing_xact_state) ||
+ (used_in_current_xact &&
+ !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index b6c72e1d1e..160f17972b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
--
2.25.1
v3-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/x-patch; name=v3-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From ec2b2d7d78a5ce9d75eb253ae3db45cc4a9d7a13 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 27 Nov 2020 06:18:42 +0530
Subject: [PATCH v2] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 22 +++++++++++++++++++---
contrib/postgres_fdw/option.c | 9 ++++++++-
2 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 85217188b5..c13e1a3cc8 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -60,6 +60,8 @@ typedef struct ConnCacheEntry
bool invalidated; /* true if reconnect is pending */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -120,6 +122,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -260,6 +264,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -283,6 +296,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,14 +970,16 @@ pgfdw_xact_callback(XactEvent event, void *arg)
* If the connection isn't in a good idle state, discard it to recover.
* Next GetConnection will open a new connection when required.
*
- * If the keep_connections GUC is false and this connection is used in
- * current xact, then also discard it.
+ * Also discard the connection if it is used in current xact and if the
+ * GUC is set to off or if the GUC is on but the server level option is
+ * set to off. Note that keep_connections GUC overrides the server
+ * level keep_connection option.
*/
if ((PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state) ||
(used_in_current_xact &&
- !keep_connections))
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1a03e02263..0fe2eff878 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
--
2.25.1
On 2020/12/04 20:15, Bharath Rupireddy wrote:
On Fri, Dec 4, 2020 at 1:46 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Fri, Dec 4, 2020 at 11:49 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching the v2 patch set. Please review it further.
Regarding the 0001 patch, we should add the function that returns
the information of cached connections like dblink_get_connections(),
together with 0001 patch? Otherwise it's not easy for users to
see how many cached connections are and determine whether to
disconnect them or not. Sorry if this was already discussed before.Thanks for bringing this up. Exactly this is what I was thinking a few
days back. Say the new function postgres_fdw_get_connections() which
can return an array of server names whose connections exist in the
cache. Without this function, the user may not know how many
connections this backend has until he checks it manually on the remote
server.Thoughts? If okay, I can code the function in the 0001 patch.
Added a new function postgres_fdw_get_connections() into 0001 patch,
Thanks!
which returns a list of server names for which there exists an
existing open and active connection.Attaching v3 patch set, please review it further.
I started reviewing 0001 patch.
IMO the 0001 patch should be self-contained so that we can commit it at first. That is, I think that it's better to move the documents and tests for the functions 0001 patch adds from 0004 to 0001.
Since 0001 introduces new user-visible functions into postgres_fdw, the version of postgres_fdw should be increased?
The similar code to get the server name from cached connection entry exists also in pgfdw_reject_incomplete_xact_state_change(). I'm tempted to make the "common" function for that code and use it both in postgres_fdw_get_connections() and pgfdw_reject_incomplete_xact_state_change(), to simplify the code.
+ /* We only look for active and open remote connections. */
+ if (entry->invalidated || !entry->conn)
+ continue;
We should return even invalidated entry because it has still cached connection?
Also this makes me wonder if we should return both the server name and boolean flag indicating whether it's invalidated or not. If so, users can easily find the invalidated connection entry and disconnect it because there is no need to keep invalidated connection.
+ if (all)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ result = true;
+ }
Could you tell me why ConnectionHash needs to be destroyed?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Wed, Dec 9, 2020 at 4:49 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
I started reviewing 0001 patch.
Thanks!
IMO the 0001 patch should be self-contained so that we can commit it at first. That is, I think that it's better to move the documents and tests for the functions 0001 patch adds from 0004 to 0001.
+1. I will make each patch self-contained in the next version which I
plan to submit soon.
Since 0001 introduces new user-visible functions into postgres_fdw, the version of postgres_fdw should be increased?
Yeah looks like we should do that, dblink has done that when it
introduced new functions. In case the new functions are not required
for anyone, they can choose to go back to 1.0.
Should we make the new version as 1.1 or 2.0? I prefer to make it 1.1
as we are just adding few functionality over 1.0. I will change the
default_version from 1.0 to the 1.1 and add a new
postgres_fdw--1.1.sql file.
If okay, I will make changes to 0001 patch.
The similar code to get the server name from cached connection entry exists also in pgfdw_reject_incomplete_xact_state_change(). I'm tempted to make the "common" function for that code and use it both in postgres_fdw_get_connections() and pgfdw_reject_incomplete_xact_state_change(), to simplify the code.
+1. I will move the server name finding code to a new function, say
char *pgfdw_get_server_name(ConnCacheEntry *entry);
+ /* We only look for active and open remote connections. */ + if (entry->invalidated || !entry->conn) + continue;We should return even invalidated entry because it has still cached connection?
I checked this point earlier, for invalidated connections, the tuple
returned from the cache is also invalid and the following error will
be thrown. So, we can not get the server name for that user mapping.
Cache entries too would have been invalidated after the connection is
marked as invalid in pgfdw_inval_callback().
umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
elog(ERROR, "cache lookup failed for user mapping with OID %u",
entry->key);
Can we reload the sys cache entries of USERMAPPINGOID (if there is a
way) for invalid connections in our new function and then do a look
up? If not, another way could be storing the associated server name or
oid in the ConnCacheEntry. Currently we store user mapping oid(in
key), its hash value(in mapping_hashvalue) and foreign server oid's
hash value (in server_hashvalue). If we have the foreign server oid,
then we can just look up for the server name, but I'm not quite sure
whether we get the same issue i.e. invalid tuples when the entry gets
invalided (via pgfdw_inval_callback) due to some change in foreign
server options.
IMHO, we can simply choose to show all the active, valid connections. Thoughts?
Also this makes me wonder if we should return both the server name and boolean flag indicating whether it's invalidated or not. If so, users can easily find the invalidated connection entry and disconnect it because there is no need to keep invalidated connection.
Currently we are returning a list of foreing server names with whom
there exist active connections. If we somehow address the above
mentioned problem for invalid connections and choose to show them as
well, then how should our output look like? Is it something like we
prepare a list of pairs (servername, validflag)?
+ if (all) + { + hash_destroy(ConnectionHash); + ConnectionHash = NULL; + result = true; + }Could you tell me why ConnectionHash needs to be destroyed?
Say, in a session there are hundreds of different foreign server
connections made and if users want to disconnect all of them with the
new function and don't want any further foreign connections in that
session, they can do it. But then why keep the cache just lying around
and holding those many entries? Instead we can destroy the cache and
if necessary it will be allocated later on next foreign server
connections.
IMHO, it is better to destroy the cache in case of disconnect all,
hoping to save memory, thinking that (next time if required) the cache
allocation doesn't take much time. Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Dec 10, 2020 at 7:14 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
+ /* We only look for active and open remote connections. */ + if (entry->invalidated || !entry->conn) + continue;We should return even invalidated entry because it has still cached connection?
I checked this point earlier, for invalidated connections, the tuple
returned from the cache is also invalid and the following error will
be thrown. So, we can not get the server name for that user mapping.
Cache entries too would have been invalidated after the connection is
marked as invalid in pgfdw_inval_callback().umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
elog(ERROR, "cache lookup failed for user mapping with OID %u",
entry->key);
I further checked on returning invalidated connections in the output
of the function. Actually, the reason I'm seeing a null tuple from sys
cache (and hence the error "cache lookup failed for user mapping with
OID xxxx") for an invalidated connection is that the user mapping
(with OID entry->key that exists in the cache) is getting dropped, so
the sys cache returns null tuple. The use case is as follows:
1) Create a server, role, and user mapping of the role with the server
2) Run a foreign table query, so that the connection related to the
server gets cached
3) Issue DROP OWNED BY for the role created, since the user mapping is
dependent on that role, it gets dropped from the catalogue table and
an invalidation message will be pending to clear the sys cache
associated with that user mapping.
4) Now, if I do select * from postgres_fdw_get_connections() or for
that matter any query, at the beginning the txn
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
gets called and marks the cached entry as invalidated. Remember the
reason for this invalidation message is that the user mapping with the
OID entry->key is dropped from 3). Later in
postgres_fdw_get_connections(), when we search the sys cache with
entry->key for that invalidated connection, since the user mapping is
dropped from the system, null tuple is returned.
If we were to show invalidated connections in the output of
postgres_fdw_get_connections(), we can ignore the entry and continue
further if the user mapping sys cache search returns null tuple:
umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
continue;
Thoughts?
Also this makes me wonder if we should return both the server name and boolean flag indicating whether it's invalidated or not. If so, users can easily find the invalidated connection entry and disconnect it because there is no need to keep invalidated connection.
Currently we are returning a list of foreing server names with whom
there exist active connections. If we somehow address the above
mentioned problem for invalid connections and choose to show them as
well, then how should our output look like? Is it something like we
prepare a list of pairs (servername, validflag)?
If agreed on above point, we can output something like: (myserver1,
valid), (myserver2, valid), (myserver3, invalid), (myserver4, valid)
....
Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020/12/11 19:16, Bharath Rupireddy wrote:
On Thu, Dec 10, 2020 at 7:14 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:+ /* We only look for active and open remote connections. */ + if (entry->invalidated || !entry->conn) + continue;We should return even invalidated entry because it has still cached connection?
I checked this point earlier, for invalidated connections, the tuple
returned from the cache is also invalid and the following error will
be thrown. So, we can not get the server name for that user mapping.
Cache entries too would have been invalidated after the connection is
marked as invalid in pgfdw_inval_callback().umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
elog(ERROR, "cache lookup failed for user mapping with OID %u",
entry->key);I further checked on returning invalidated connections in the output
of the function. Actually, the reason I'm seeing a null tuple from sys
cache (and hence the error "cache lookup failed for user mapping with
OID xxxx") for an invalidated connection is that the user mapping
(with OID entry->key that exists in the cache) is getting dropped, so
the sys cache returns null tuple. The use case is as follows:1) Create a server, role, and user mapping of the role with the server
2) Run a foreign table query, so that the connection related to the
server gets cached
3) Issue DROP OWNED BY for the role created, since the user mapping is
dependent on that role, it gets dropped from the catalogue table and
an invalidation message will be pending to clear the sys cache
associated with that user mapping.
4) Now, if I do select * from postgres_fdw_get_connections() or for
that matter any query, at the beginning the txn
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
gets called and marks the cached entry as invalidated. Remember the
reason for this invalidation message is that the user mapping with the
OID entry->key is dropped from 3). Later in
postgres_fdw_get_connections(), when we search the sys cache with
entry->key for that invalidated connection, since the user mapping is
dropped from the system, null tuple is returned.
Thanks for the analysis! This means that the cached connection invalidated by drop of server or user mapping will not be closed even by the subsequent access to the foreign server and will remain until the backend exits. Right? If so, this seems like a connection-leak bug, at least for me.... Thought?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2020/12/10 10:44, Bharath Rupireddy wrote:
On Wed, Dec 9, 2020 at 4:49 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
I started reviewing 0001 patch.
Thanks!
IMO the 0001 patch should be self-contained so that we can commit it at first. That is, I think that it's better to move the documents and tests for the functions 0001 patch adds from 0004 to 0001.
+1. I will make each patch self-contained in the next version which I
plan to submit soon.Since 0001 introduces new user-visible functions into postgres_fdw, the version of postgres_fdw should be increased?
Yeah looks like we should do that, dblink has done that when it
introduced new functions. In case the new functions are not required
for anyone, they can choose to go back to 1.0.Should we make the new version as 1.1 or 2.0? I prefer to make it 1.1
as we are just adding few functionality over 1.0. I will change the
default_version from 1.0 to the 1.1 and add a new
postgres_fdw--1.1.sql file.
+1
If okay, I will make changes to 0001 patch.
The similar code to get the server name from cached connection entry exists also in pgfdw_reject_incomplete_xact_state_change(). I'm tempted to make the "common" function for that code and use it both in postgres_fdw_get_connections() and pgfdw_reject_incomplete_xact_state_change(), to simplify the code.
+1. I will move the server name finding code to a new function, say
char *pgfdw_get_server_name(ConnCacheEntry *entry);+ /* We only look for active and open remote connections. */ + if (entry->invalidated || !entry->conn) + continue;We should return even invalidated entry because it has still cached connection?
I checked this point earlier, for invalidated connections, the tuple
returned from the cache is also invalid and the following error will
be thrown. So, we can not get the server name for that user mapping.
Cache entries too would have been invalidated after the connection is
marked as invalid in pgfdw_inval_callback().umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
elog(ERROR, "cache lookup failed for user mapping with OID %u",
entry->key);Can we reload the sys cache entries of USERMAPPINGOID (if there is a
way) for invalid connections in our new function and then do a look
up? If not, another way could be storing the associated server name or
oid in the ConnCacheEntry. Currently we store user mapping oid(in
key), its hash value(in mapping_hashvalue) and foreign server oid's
hash value (in server_hashvalue). If we have the foreign server oid,
then we can just look up for the server name, but I'm not quite sure
whether we get the same issue i.e. invalid tuples when the entry gets
invalided (via pgfdw_inval_callback) due to some change in foreign
server options.IMHO, we can simply choose to show all the active, valid connections. Thoughts?
Also this makes me wonder if we should return both the server name and boolean flag indicating whether it's invalidated or not. If so, users can easily find the invalidated connection entry and disconnect it because there is no need to keep invalidated connection.
Currently we are returning a list of foreing server names with whom
there exist active connections. If we somehow address the above
mentioned problem for invalid connections and choose to show them as
well, then how should our output look like? Is it something like we
prepare a list of pairs (servername, validflag)?+ if (all) + { + hash_destroy(ConnectionHash); + ConnectionHash = NULL; + result = true; + }Could you tell me why ConnectionHash needs to be destroyed?
Say, in a session there are hundreds of different foreign server
connections made and if users want to disconnect all of them with the
new function and don't want any further foreign connections in that
session, they can do it. But then why keep the cache just lying around
and holding those many entries? Instead we can destroy the cache and
if necessary it will be allocated later on next foreign server
connections.IMHO, it is better to destroy the cache in case of disconnect all,
hoping to save memory, thinking that (next time if required) the cache
allocation doesn't take much time. Thoughts?
Ok, but why is ConnectionHash destroyed only when "all" is true? Even when "all" is false, for example, the following query can disconnect all the cached connections. Even in this case, i.e., whenever there are no cached connections, ConnectionHash should be destroyed?
SELECT postgres_fdw_disconnect(srvname) FROM pg_foreign_server ;
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Dec 11, 2020 at 11:01 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2020/12/11 19:16, Bharath Rupireddy wrote:
On Thu, Dec 10, 2020 at 7:14 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:+ /* We only look for active and open remote connections. */ + if (entry->invalidated || !entry->conn) + continue;We should return even invalidated entry because it has still cached connection?
I checked this point earlier, for invalidated connections, the tuple
returned from the cache is also invalid and the following error will
be thrown. So, we can not get the server name for that user mapping.
Cache entries too would have been invalidated after the connection is
marked as invalid in pgfdw_inval_callback().umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
elog(ERROR, "cache lookup failed for user mapping with OID %u",
entry->key);I further checked on returning invalidated connections in the output
of the function. Actually, the reason I'm seeing a null tuple from sys
cache (and hence the error "cache lookup failed for user mapping with
OID xxxx") for an invalidated connection is that the user mapping
(with OID entry->key that exists in the cache) is getting dropped, so
the sys cache returns null tuple. The use case is as follows:1) Create a server, role, and user mapping of the role with the server
2) Run a foreign table query, so that the connection related to the
server gets cached
3) Issue DROP OWNED BY for the role created, since the user mapping is
dependent on that role, it gets dropped from the catalogue table and
an invalidation message will be pending to clear the sys cache
associated with that user mapping.
4) Now, if I do select * from postgres_fdw_get_connections() or for
that matter any query, at the beginning the txn
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
gets called and marks the cached entry as invalidated. Remember the
reason for this invalidation message is that the user mapping with the
OID entry->key is dropped from 3). Later in
postgres_fdw_get_connections(), when we search the sys cache with
entry->key for that invalidated connection, since the user mapping is
dropped from the system, null tuple is returned.Thanks for the analysis! This means that the cached connection invalidated by drop of server or user mapping will not be closed even by the subsequent access to the foreign server and will remain until the backend exits. Right?
It will be first marked as invalidated via
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback(),
and on the next use of that connection invalidated connections are
disconnected and reconnected.
if (entry->conn != NULL && entry->invalidated && entry->xact_depth == 0)
{
elog(DEBUG3, "closing connection %p for option changes to take effect",
entry->conn);
disconnect_pg_server(entry);
}
If so, this seems like a connection-leak bug, at least for me.... Thought?
It's not a leak. The comment before pgfdw_inval_callback() [1]/* * Connection invalidation callback function * * After a change to a pg_foreign_server or pg_user_mapping catalog entry, * mark connections depending on that entry as needing to be remade. * We can't immediately destroy them, since they might be in the midst of * a transaction, but we'll remake them at the next opportunity. * * Although most cache invalidation callbacks blow away all the related stuff * regardless of the given hashvalue, connections are expensive enough that * it's worth trying to avoid that. * * NB: We could avoid unnecessary disconnection more strictly by examining * individual option values, but it seems too much effort for the gain. */ static void pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
explains why we can not immediately close/disconnect the connections
in pgfdw_inval_callback() after marking them as invalidated.
Here is the scenario how in the midst of a txn we get invalidation
messages(AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
happens):
1) select from foreign table with server1, usermapping1 in session1
2) begin a top txn in session1, run a few foreign queries that open up
sub txns internally. meanwhile alter/drop server1/usermapping1 in
session2, then at each start of sub txn also we get to process the
invalidation messages via
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback().
So, if we disconnect right after marking invalidated in
pgfdw_inval_callback, that's a problem since we are in a sub txn under
a top txn.
I don't think we can do something here and disconnect the connections
right after the invalidation happens. Thoughts?
[1]: /* * Connection invalidation callback function * * After a change to a pg_foreign_server or pg_user_mapping catalog entry, * mark connections depending on that entry as needing to be remade. * We can't immediately destroy them, since they might be in the midst of * a transaction, but we'll remake them at the next opportunity. * * Although most cache invalidation callbacks blow away all the related stuff * regardless of the given hashvalue, connections are expensive enough that * it's worth trying to avoid that. * * NB: We could avoid unnecessary disconnection more strictly by examining * individual option values, but it seems too much effort for the gain. */ static void pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
/*
* Connection invalidation callback function
*
* After a change to a pg_foreign_server or pg_user_mapping catalog entry,
* mark connections depending on that entry as needing to be remade.
* We can't immediately destroy them, since they might be in the midst of
* a transaction, but we'll remake them at the next opportunity.
*
* Although most cache invalidation callbacks blow away all the related stuff
* regardless of the given hashvalue, connections are expensive enough that
* it's worth trying to avoid that.
*
* NB: We could avoid unnecessary disconnection more strictly by examining
* individual option values, but it seems too much effort for the gain.
*/
static void
pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Dec 11, 2020 at 3:46 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
If we were to show invalidated connections in the output of
postgres_fdw_get_connections(), we can ignore the entry and continue
further if the user mapping sys cache search returns null tuple:umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
continue;
Any thoughts here?
Also this makes me wonder if we should return both the server name and boolean flag indicating whether it's invalidated or not. If so, users can easily find the invalidated connection entry and disconnect it because there is no need to keep invalidated connection.
Currently we are returning a list of foreing server names with whom
there exist active connections. If we somehow address the above
mentioned problem for invalid connections and choose to show them as
well, then how should our output look like? Is it something like we
prepare a list of pairs (servername, validflag)?If agreed on above point, we can output something like: (myserver1,
valid), (myserver2, valid), (myserver3, invalid), (myserver4, valid)
And here on the output text?
In case we agreed on the above output format, one funniest thing could
occur is that if some hypothetical person has "valid" or "invalid" as
their foreign server names, they will have difficulty in reading their
output. (valid, valid), (valid, invalid), (invalid, valid), (invalid,
invalid).
Or should it be something like pairs of (server_name, true/false)?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020/12/12 3:01, Bharath Rupireddy wrote:
On Fri, Dec 11, 2020 at 11:01 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2020/12/11 19:16, Bharath Rupireddy wrote:
On Thu, Dec 10, 2020 at 7:14 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:+ /* We only look for active and open remote connections. */ + if (entry->invalidated || !entry->conn) + continue;We should return even invalidated entry because it has still cached connection?
I checked this point earlier, for invalidated connections, the tuple
returned from the cache is also invalid and the following error will
be thrown. So, we can not get the server name for that user mapping.
Cache entries too would have been invalidated after the connection is
marked as invalid in pgfdw_inval_callback().umaptup = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
if (!HeapTupleIsValid(umaptup))
elog(ERROR, "cache lookup failed for user mapping with OID %u",
entry->key);I further checked on returning invalidated connections in the output
of the function. Actually, the reason I'm seeing a null tuple from sys
cache (and hence the error "cache lookup failed for user mapping with
OID xxxx") for an invalidated connection is that the user mapping
(with OID entry->key that exists in the cache) is getting dropped, so
the sys cache returns null tuple. The use case is as follows:1) Create a server, role, and user mapping of the role with the server
2) Run a foreign table query, so that the connection related to the
server gets cached
3) Issue DROP OWNED BY for the role created, since the user mapping is
dependent on that role, it gets dropped from the catalogue table and
an invalidation message will be pending to clear the sys cache
associated with that user mapping.
4) Now, if I do select * from postgres_fdw_get_connections() or for
that matter any query, at the beginning the txn
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
gets called and marks the cached entry as invalidated. Remember the
reason for this invalidation message is that the user mapping with the
OID entry->key is dropped from 3). Later in
postgres_fdw_get_connections(), when we search the sys cache with
entry->key for that invalidated connection, since the user mapping is
dropped from the system, null tuple is returned.Thanks for the analysis! This means that the cached connection invalidated by drop of server or user mapping will not be closed even by the subsequent access to the foreign server and will remain until the backend exits. Right?
It will be first marked as invalidated via
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback(),
and on the next use of that connection invalidated connections are
disconnected and reconnected.
I was thinking that in the case of drop of user mapping or server, hash_search(ConnnectionHash) in GetConnection() cannot find the cached connection entry invalidated by that drop. Because "user->umid" used as hash key is changed. So I was thinking that that invalidated connection will not be closed nor reconnected.
if (entry->conn != NULL && entry->invalidated && entry->xact_depth == 0)
{
elog(DEBUG3, "closing connection %p for option changes to take effect",
entry->conn);
disconnect_pg_server(entry);
}If so, this seems like a connection-leak bug, at least for me.... Thought?
It's not a leak. The comment before pgfdw_inval_callback() [1]
explains why we can not immediately close/disconnect the connections
in pgfdw_inval_callback() after marking them as invalidated.
*If* invalidated connection cannot be close immediately even in the case of drop of server or user mapping, we can defer it to the subsequent call to GetConnection(). That is, GetConnection() closes not only the target invalidated connection but also the other all invalidated connections. Of course, invalidated connections will remain until subsequent GetConnection() is called, though.
Here is the scenario how in the midst of a txn we get invalidation
messages(AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
happens):1) select from foreign table with server1, usermapping1 in session1
2) begin a top txn in session1, run a few foreign queries that open up
sub txns internally. meanwhile alter/drop server1/usermapping1 in
session2, then at each start of sub txn also we get to process the
invalidation messages via
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback().
So, if we disconnect right after marking invalidated in
pgfdw_inval_callback, that's a problem since we are in a sub txn under
a top txn.
Maybe. But what is the actual problem here?
OTOH, if cached connection should not be close in the middle of transaction, postgres_fdw_disconnect() also should be disallowed to be executed during transaction?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Sat, Dec 12, 2020 at 12:19 AM Fujii Masao <masao.fujii@oss.nttdata.com>
wrote:
I was thinking that in the case of drop of user mapping or server,
hash_search(ConnnectionHash) in GetConnection() cannot find the cached
connection entry invalidated by that drop. Because "user->umid" used as
hash key is changed. So I was thinking that that invalidated connection
will not be closed nor reconnected.
You are right in saying that the connection leaks.
Use case 1:
1) Run foreign query in session1 with server1, user mapping1
2) Drop user mapping1 in another session2, invalidation message gets logged
which will have to be processed by other sessions
3) Run foreign query again in session1, at the start of txn, the cached
entry gets invalidated via pgfdw_inval_callback(). Whatever may be the type
of foreign query (select, update, explain, delete, insert, analyze etc.),
upon next call to GetUserMapping() from postgres_fdw.c, the cache lookup
fails(with ERROR: user mapping not found for "XXXX") since the user
mapping1 has been dropped in session2 and the query will also fail before
reaching GetConnection() where the connections associated with invalidated
entries would have got disconnected.
So, the connection associated with invalidated entry would remain until the
local session exits which is a problem to solve.
Use case 2:
1) Run foreign query in session1 with server1, user mapping1
2) Try to drop foreign server1, then we would not be allowed to do so
because of dependency. If we use CASCADE, then the dependent user mapping1
and foreign tables get dropped too [1]postgres=# drop server loopback1 ; ERROR: cannot drop server loopback1 because other objects depend on it DETAIL: user mapping for bharath on server loopback1 depends on server loopback1 foreign table f1 depends on server loopback1 HINT: Use DROP ... CASCADE to drop the dependent objects too..
3) Run foreign query again in session1, at the start of txn, the cached
entry gets invalidated via pgfdw_inval_callback(), it fails because there
is no foreign table and user mapping1.
But, note that the connection remains open in session1, which is again a
problem to solve.
To solve the above connection leak problem, it looks like the right place
to close all the invalid connections is pgfdw_xact_callback(), once
registered, which gets called at the end of every txn in the current
session(by then all the sub txns also would have been finished). Note that
if there are too many invalidated entries, then one of the following txn
has to bear running this extra code, but that's okay than having leaked
connections. Thoughts? If okay, I can code a separate patch.
static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
* /* HERE WE CAN LOOK FOR ALL INVALIDATED ENTRIES AND DISCONNECT THEM*/*
/* Quick exit if no connections were touched in this transaction. */
if (!xact_got_connection)
return;
And we can also extend postgres_fdw_disconnect() something like.
postgres_fdw_disconnect(bool invalid_only) --> default for invalid_only
false. disconnects all connections. when invalid_only is set to true then
disconnects only invalid connections.
postgres_fdw_disconnect('server_name') --> disconnections connections
associated with the specified foreign server
Having said this, I'm not in favour of invalid_only flag, because if we
choose to change the code in pgfdw_xact_callback to solve connection leak
problem, we may not need this invalid_only flag at all, because at the end
of txn (even for the txns in which the queries fail with error,
pgfdw_xact_callback gets called), all the existing invalid connections get
disconnected. Thoughts?
[1]: postgres=# drop server loopback1 ; ERROR: cannot drop server loopback1 because other objects depend on it DETAIL: user mapping for bharath on server loopback1 depends on server loopback1 foreign table f1 depends on server loopback1 HINT: Use DROP ... CASCADE to drop the dependent objects too.
postgres=# drop server loopback1 ;
ERROR: cannot drop server loopback1 because other objects depend on it
DETAIL: user mapping for bharath on server loopback1 depends on server
loopback1
foreign table f1 depends on server loopback1
HINT: Use DROP ... CASCADE to drop the dependent objects too.
postgres=# drop server loopback1 CASCADE ;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to user mapping for bharath on server loopback1
drop cascades to foreign table f1
DROP SERVER
if (entry->conn != NULL && entry->invalidated && entry->xact_depth
== 0)
{
elog(DEBUG3, "closing connection %p for option changes to take
effect",
entry->conn);
disconnect_pg_server(entry);
}If so, this seems like a connection-leak bug, at least for me....
Thought?
It's not a leak. The comment before pgfdw_inval_callback() [1]
explains why we can not immediately close/disconnect the connections
in pgfdw_inval_callback() after marking them as invalidated.*If* invalidated connection cannot be close immediately even in the case
of drop of server or user mapping, we can defer it to the subsequent call
to GetConnection(). That is, GetConnection() closes not only the target
invalidated connection but also the other all invalidated connections. Of
course, invalidated connections will remain until subsequent
GetConnection() is called, though.
I think my detailed response to the above comment clarifies this.
Here is the scenario how in the midst of a txn we get invalidation
messages(AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
happens):
1) select from foreign table with server1, usermapping1 in session1
2) begin a top txn in session1, run a few foreign queries that open up
sub txns internally. meanwhile alter/drop server1/usermapping1 in
session2, then at each start of sub txn also we get to process the
invalidation messages via
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback().
So, if we disconnect right after marking invalidated in
pgfdw_inval_callback, that's a problem since we are in a sub txn under
a top txn.Maybe. But what is the actual problem here?
OTOH, if cached connection should not be close in the middle of
transaction, postgres_fdw_disconnect() also should be disallowed to be
executed during transaction?
+1. Yeah that makes sense. We can avoid closing the connection if
(entry->xact_depth > 0). I will modify it in
disconnect_cached_connections().
Could you tell me why ConnectionHash needs to be destroyed?
Say, in a session there are hundreds of different foreign server
connections made and if users want to disconnect all of them with the
new function and don't want any further foreign connections in that
session, they can do it. But then why keep the cache just lying around
and holding those many entries? Instead we can destroy the cache and
if necessary it will be allocated later on next foreign server
connections.IMHO, it is better to destroy the cache in case of disconnect all,
hoping to save memory, thinking that (next time if required) the cache
allocation doesn't take much time. Thoughts?Ok, but why is ConnectionHash destroyed only when "all" is true? Even
when "all" is false, for example, the following query can disconnect all
the cached connections. Even in this case, i.e., whenever there are no
cached connections, ConnectionHash should be destroyed?
SELECT postgres_fdw_disconnect(srvname) FROM pg_foreign_server ;
+1. I can check all the cache entries to see if there are any active
connections, in the same loop where I try to find the cache entry for the
given foreign server, if none exists, then I destroy the cache. Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020/12/12 15:05, Bharath Rupireddy wrote:
On Sat, Dec 12, 2020 at 12:19 AM Fujii Masao <masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com>> wrote:
I was thinking that in the case of drop of user mapping or server, hash_search(ConnnectionHash) in GetConnection() cannot find the cached connection entry invalidated by that drop. Because "user->umid" used as hash key is changed. So I was thinking that that invalidated connection will not be closed nor reconnected.
You are right in saying that the connection leaks.
Use case 1:
1) Run foreign query in session1 with server1, user mapping1
2) Drop user mapping1 in another session2, invalidation message gets logged which will have to be processed by other sessions
3) Run foreign query again in session1, at the start of txn, the cached entry gets invalidated via pgfdw_inval_callback(). Whatever may be the type of foreign query (select, update, explain, delete, insert, analyze etc.), upon next call to GetUserMapping() from postgres_fdw.c, the cache lookup fails(with ERROR: user mapping not found for "XXXX") since the user mapping1 has been dropped in session2 and the query will also fail before reaching GetConnection() where the connections associated with invalidated entries would have got disconnected.So, the connection associated with invalidated entry would remain until the local session exits which is a problem to solve.
Use case 2:
1) Run foreign query in session1 with server1, user mapping1
2) Try to drop foreign server1, then we would not be allowed to do so because of dependency. If we use CASCADE, then the dependent user mapping1 and foreign tables get dropped too [1].
3) Run foreign query again in session1, at the start of txn, the cached entry gets invalidated via pgfdw_inval_callback(), it fails because there is no foreign table and user mapping1.But, note that the connection remains open in session1, which is again a problem to solve.
To solve the above connection leak problem, it looks like the right place to close all the invalid connections is pgfdw_xact_callback(), once registered, which gets called at the end of every txn in the current session(by then all the sub txns also would have been finished). Note that if there are too many invalidated entries, then one of the following txn has to bear running this extra code, but that's okay than having leaked connections. Thoughts? If okay, I can code a separate patch.
Thanks for further analysis! Sounds good. Also +1 for making it as separate patch. Maybe only this patch needs to be back-patched.
static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
* /* HERE WE CAN LOOK FOR ALL INVALIDATED ENTRIES AND DISCONNECT THEM*/*
This may cause the connection to be closed before sending COMMIT TRANSACTION command to the foreign server, i.e., the connection is closed in the middle of the transaction. So as you explained before, we should avoid that? If this my understanding is right, probably the connection should be closed after COMMIT TRANSACTION command is sent to the foreign server. What about changing the following code in pgfdw_xact_callback() so that it closes the connection even when it's marked as invalidated?
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}
/* Quick exit if no connections were touched in this transaction. */
if (!xact_got_connection)
return;And we can also extend postgres_fdw_disconnect() something like.
postgres_fdw_disconnect(bool invalid_only) --> default for invalid_only false. disconnects all connections. when invalid_only is set to true then disconnects only invalid connections.
postgres_fdw_disconnect('server_name') --> disconnections connections associated with the specified foreign serverHaving said this, I'm not in favour of invalid_only flag, because if we choose to change the code in pgfdw_xact_callback to solve connection leak problem, we may not need this invalid_only flag at all, because at the end of txn (even for the txns in which the queries fail with error, pgfdw_xact_callback gets called), all the existing invalid connections get disconnected. Thoughts?
+1 not to have invalid_only flag. On the other hand, I think that postgres_fdw_get_connections() should return all the cached connections including invalidated ones. Otherwise, the number of connections observed via postgres_fdw_get_connections() may be different from the number of connections actually established, and which would be confusing to users. BTW, even after fixing the connection-leak issue, postgres_fdw_get_connections() may see invalidated cached connections when it's called during the transaction.
[1]
postgres=# drop server loopback1 ;
ERROR: cannot drop server loopback1 because other objects depend on it
DETAIL: user mapping for bharath on server loopback1 depends on server loopback1
foreign table f1 depends on server loopback1
HINT: Use DROP ... CASCADE to drop the dependent objects too.postgres=# drop server loopback1 CASCADE ;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to user mapping for bharath on server loopback1
drop cascades to foreign table f1
DROP SERVERif (entry->conn != NULL && entry->invalidated && entry->xact_depth == 0)
{
elog(DEBUG3, "closing connection %p for option changes to take effect",
entry->conn);
disconnect_pg_server(entry);
}If so, this seems like a connection-leak bug, at least for me.... Thought?
It's not a leak. The comment before pgfdw_inval_callback() [1]
explains why we can not immediately close/disconnect the connections
in pgfdw_inval_callback() after marking them as invalidated.*If* invalidated connection cannot be close immediately even in the case of drop of server or user mapping, we can defer it to the subsequent call to GetConnection(). That is, GetConnection() closes not only the target invalidated connection but also the other all invalidated connections. Of course, invalidated connections will remain until subsequent GetConnection() is called, though.
I think my detailed response to the above comment clarifies this.
Here is the scenario how in the midst of a txn we get invalidation
messages(AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback()
happens):1) select from foreign table with server1, usermapping1 in session1
2) begin a top txn in session1, run a few foreign queries that open up
sub txns internally. meanwhile alter/drop server1/usermapping1 in
session2, then at each start of sub txn also we get to process the
invalidation messages via
AtStart_Cache()-->AcceptInvalidationMessages()-->pgfdw_inval_callback().
So, if we disconnect right after marking invalidated in
pgfdw_inval_callback, that's a problem since we are in a sub txn under
a top txn.Maybe. But what is the actual problem here?
OTOH, if cached connection should not be close in the middle of transaction, postgres_fdw_disconnect() also should be disallowed to be executed during transaction?
+1. Yeah that makes sense. We can avoid closing the connection if (entry->xact_depth > 0). I will modify it in disconnect_cached_connections().
Could you tell me why ConnectionHash needs to be destroyed?
Say, in a session there are hundreds of different foreign server
connections made and if users want to disconnect all of them with the
new function and don't want any further foreign connections in that
session, they can do it. But then why keep the cache just lying around
and holding those many entries? Instead we can destroy the cache and
if necessary it will be allocated later on next foreign server
connections.IMHO, it is better to destroy the cache in case of disconnect all,
hoping to save memory, thinking that (next time if required) the cache
allocation doesn't take much time. Thoughts?Ok, but why is ConnectionHash destroyed only when "all" is true? Even when "all" is false, for example, the following query can disconnect all the cached connections. Even in this case, i.e., whenever there are no cached connections, ConnectionHash should be destroyed?
SELECT postgres_fdw_disconnect(srvname) FROM pg_foreign_server ;
+1. I can check all the cache entries to see if there are any active connections, in the same loop where I try to find the cache entry for the given foreign server, if none exists, then I destroy the cache. Thoughts?
+1
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Dec 14, 2020 at 9:38 AM Fujii Masao <masao.fujii@oss.nttdata.com>
wrote:
On 2020/12/12 15:05, Bharath Rupireddy wrote:
On Sat, Dec 12, 2020 at 12:19 AM Fujii Masao <
masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com>> wrote:
I was thinking that in the case of drop of user mapping or server,
hash_search(ConnnectionHash) in GetConnection() cannot find the cached
connection entry invalidated by that drop. Because "user->umid" used as
hash key is changed. So I was thinking that that invalidated connection
will not be closed nor reconnected.
You are right in saying that the connection leaks.
Use case 1:
1) Run foreign query in session1 with server1, user mapping1
2) Drop user mapping1 in another session2, invalidation message gets
logged which will have to be processed by other sessions
3) Run foreign query again in session1, at the start of txn, the cached
entry gets invalidated via pgfdw_inval_callback(). Whatever may be the type
of foreign query (select, update, explain, delete, insert, analyze etc.),
upon next call to GetUserMapping() from postgres_fdw.c, the cache lookup
fails(with ERROR: user mapping not found for "XXXX") since the user
mapping1 has been dropped in session2 and the query will also fail before
reaching GetConnection() where the connections associated with invalidated
entries would have got disconnected.
So, the connection associated with invalidated entry would remain until
the local session exits which is a problem to solve.
Use case 2:
1) Run foreign query in session1 with server1, user mapping1
2) Try to drop foreign server1, then we would not be allowed to do so
because of dependency. If we use CASCADE, then the dependent user mapping1
and foreign tables get dropped too [1]/messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com.
3) Run foreign query again in session1, at the start of txn, the cached
entry gets invalidated via pgfdw_inval_callback(), it fails because there
is no foreign table and user mapping1.
But, note that the connection remains open in session1, which is again
a problem to solve.
To solve the above connection leak problem, it looks like the right
place to close all the invalid connections is pgfdw_xact_callback(), once
registered, which gets called at the end of every txn in the current
session(by then all the sub txns also would have been finished). Note that
if there are too many invalidated entries, then one of the following txn
has to bear running this extra code, but that's okay than having leaked
connections. Thoughts? If okay, I can code a separate patch.
Thanks for further analysis! Sounds good. Also +1 for making it as
separate patch. Maybe only this patch needs to be back-patched.
Thanks. Yeah once agreed on the fix, +1 to back patch. Shall I start a
separate thread for connection leak issue and patch, so that others might
have different thoughts??
static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
* /* HERE WE CAN LOOK FOR ALL INVALIDATED ENTRIES AND DISCONNECT
THEM*/*
This may cause the connection to be closed before sending COMMIT
TRANSACTION command to the foreign server, i.e., the connection is closed
in the middle of the transaction. So as you explained before, we should
avoid that? If this my understanding is right, probably the connection
should be closed after COMMIT TRANSACTION command is sent to the foreign
server. What about changing the following code in pgfdw_xact_callback() so
that it closes the connection even when it's marked as invalidated?
You are right! I'm posting what I have in my mind for fixing this
connection leak problem.
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
/* tracks whether there exists at least one invalid connection in the
connection cache */
*static bool invalid_connections_exist = false;*
static void
pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
{
/* hashvalue == 0 means a cache reset, must clear all state */
if (hashvalue == 0 ||
(cacheid == FOREIGNSERVEROID &&
entry->server_hashvalue == hashvalue) ||
(cacheid == USERMAPPINGOID &&
entry->mapping_hashvalue == hashvalue))
{
entry->invalidated = true;
* invalid_connections_exist = true;*
}
static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
/* Quick exit if no connections were touched in this transaction or
there are no invalid connections in the cache. */
if (!xact_got_connection *&& !invalid_connections_exist)*
return;
/*
* If the connection isn't in a good idle state, discard it to
* recover. Next GetConnection will open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
* entry->invalidated)*
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}
/*
* Regardless of the event type, we can now mark ourselves as out of the
* transaction. (Note: if we are here during PRE_COMMIT or PRE_PREPARE,
* this saves a useless scan of the hashtable during COMMIT or PREPARE.)
*/
xact_got_connection = false;
/* We are done with closing all the invalidated connections so reset. */
*invalid_connections_exist = false;*
}
And we can also extend postgres_fdw_disconnect() something like.
postgres_fdw_disconnect(bool invalid_only) --> default for invalid_only
false. disconnects all connections. when invalid_only is set to true then
disconnects only invalid connections.
postgres_fdw_disconnect('server_name') --> disconnections connections
associated with the specified foreign server
Having said this, I'm not in favour of invalid_only flag, because if we
choose to change the code in pgfdw_xact_callback to solve connection leak
problem, we may not need this invalid_only flag at all, because at the end
of txn (even for the txns in which the queries fail with error,
pgfdw_xact_callback gets called), all the existing invalid connections get
disconnected. Thoughts?
+1 not to have invalid_only flag. On the other hand, I think that
postgres_fdw_get_connections() should return all the cached connections
including invalidated ones. Otherwise, the number of connections observed
via postgres_fdw_get_connections() may be different from the number of
connections actually established, and which would be confusing to users.
If postgres_fdw_get_connections() has to return invalidated connections, I
have few things mentioned in [1]/messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com to be clarified. Thoughts? Please have a
look at the below comment before we decide to show up the invalid entries
or not.
[1]: /messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com
/messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com
BTW, even after fixing the connection-leak issue,
postgres_fdw_get_connections() may see invalidated cached connections when
it's called during the transaction.
We will not output if the invalidated entry has no active connection[2]+Datum +postgres_fdw_get_connections(PG_FUNCTION_ARGS) +{ + ArrayBuildState *astate = NULL; + + if (ConnectionHash) + { + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + Form_pg_user_mapping umap; + HeapTuple umaptup; + Form_pg_foreign_server fsrv; + HeapTuple fsrvtup; + + /* We only look for active and open remote connections. */ + if (!entry->conn) + continue;, so
if we fix the connection leak issue with the above discussed fix i.e
closing all the invalidated connections at the end of next xact, there are
less chances that we will output invalidated entries in the
postgres_fdw_get_connections() output. Only case we may show up invalidated
connections(which have active connections entry->conn) in the
postgres_fdw_get_connections() output is as follows:
1) say we have few cached active connections exists in session 1
2) drop the user mapping (in another session) associated with any of the
cached connections to make that entry invalid
3) run select * from postgres_fdw_get_connections(); in session 1. At the
start of the xact, the invalidation message gets processed and the
corresponding entry gets marked as invalid. If we allow invalid connections
(that have entry->conn) to show up in the output, then we show them in the
result of the query. At the end of xact, we close these invalid
connections, in this case, user might think that he still have invalid
connections active.
If the query ran in 3) is not postgres_fdw_get_connections() and something
else, then postgres_fdw_get_connections() will never get to show invalid
connections as they would have closed the connections.
IMO, better not choose the invalid connections to show up in the
postgres_fdw_get_connections() output, if we fix the connection leak issue
with the above discussed fix i.e closing all the invalidated connections at
the end of next xact
[2]
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+
+ if (ConnectionHash)
+ {
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ Form_pg_user_mapping umap;
+ HeapTuple umaptup;
+ Form_pg_foreign_server fsrv;
+ HeapTuple fsrvtup;
+
+ /* We only look for active and open remote connections. */
+ if (!entry->conn)
+ continue;
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020/12/14 14:36, Bharath Rupireddy wrote:
On Mon, Dec 14, 2020 at 9:38 AM Fujii Masao <masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com>> wrote:
On 2020/12/12 15:05, Bharath Rupireddy wrote:
On Sat, Dec 12, 2020 at 12:19 AM Fujii Masao <masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com> <mailto:masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com>>> wrote:
> I was thinking that in the case of drop of user mapping or server, hash_search(ConnnectionHash) in GetConnection() cannot find the cached connection entry invalidated by that drop. Because "user->umid" used as hash key is changed. So I was thinking that that invalidated connection will not be closed nor reconnected.
>You are right in saying that the connection leaks.
Use case 1:
1) Run foreign query in session1 with server1, user mapping1
2) Drop user mapping1 in another session2, invalidation message gets logged which will have to be processed by other sessions
3) Run foreign query again in session1, at the start of txn, the cached entry gets invalidated via pgfdw_inval_callback(). Whatever may be the type of foreign query (select, update, explain, delete, insert, analyze etc.), upon next call to GetUserMapping() from postgres_fdw.c, the cache lookup fails(with ERROR: user mapping not found for "XXXX") since the user mapping1 has been dropped in session2 and the query will also fail before reaching GetConnection() where the connections associated with invalidated entries would have got disconnected.So, the connection associated with invalidated entry would remain until the local session exits which is a problem to solve.
Use case 2:
1) Run foreign query in session1 with server1, user mapping1
2) Try to drop foreign server1, then we would not be allowed to do so because of dependency. If we use CASCADE, then the dependent user mapping1 and foreign tables get dropped too [1].
3) Run foreign query again in session1, at the start of txn, the cached entry gets invalidated via pgfdw_inval_callback(), it fails because there is no foreign table and user mapping1.But, note that the connection remains open in session1, which is again a problem to solve.
To solve the above connection leak problem, it looks like the right place to close all the invalid connections is pgfdw_xact_callback(), once registered, which gets called at the end of every txn in the current session(by then all the sub txns also would have been finished). Note that if there are too many invalidated entries, then one of the following txn has to bear running this extra code, but that's okay than having leaked connections. Thoughts? If okay, I can code a separate patch.
Thanks for further analysis! Sounds good. Also +1 for making it as separate patch. Maybe only this patch needs to be back-patched.
Thanks. Yeah once agreed on the fix, +1 to back patch. Shall I start a separate thread for connection leak issue and patch, so that others might have different thoughts??
Yes, of course!
static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
* /* HERE WE CAN LOOK FOR ALL INVALIDATED ENTRIES AND DISCONNECT THEM*/*This may cause the connection to be closed before sending COMMIT TRANSACTION command to the foreign server, i.e., the connection is closed in the middle of the transaction. So as you explained before, we should avoid that? If this my understanding is right, probably the connection should be closed after COMMIT TRANSACTION command is sent to the foreign server. What about changing the following code in pgfdw_xact_callback() so that it closes the connection even when it's marked as invalidated?
You are right! I'm posting what I have in my mind for fixing this connection leak problem.
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
/* tracks whether there exists at least one invalid connection in the connection cache */
*static bool invalid_connections_exist = false;*static void
pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
{/* hashvalue == 0 means a cache reset, must clear all state */
if (hashvalue == 0 ||
(cacheid == FOREIGNSERVEROID &&
entry->server_hashvalue == hashvalue) ||
(cacheid == USERMAPPINGOID &&
entry->mapping_hashvalue == hashvalue))
{
entry->invalidated = true;
* invalid_connections_exist = true;*
}static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;/* Quick exit if no connections were touched in this transaction or there are no invalid connections in the cache. */
if (!xact_got_connection *&& !invalid_connections_exist)*
return;/*
* If the connection isn't in a good idle state, discard it to
* recover. Next GetConnection will open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
* entry->invalidated)*
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}/*
* Regardless of the event type, we can now mark ourselves as out of the
* transaction. (Note: if we are here during PRE_COMMIT or PRE_PREPARE,
* this saves a useless scan of the hashtable during COMMIT or PREPARE.)
*/
xact_got_connection = false;/* We are done with closing all the invalidated connections so reset. */
*invalid_connections_exist = false;*
}And we can also extend postgres_fdw_disconnect() something like.
postgres_fdw_disconnect(bool invalid_only) --> default for invalid_only false. disconnects all connections. when invalid_only is set to true then disconnects only invalid connections.
postgres_fdw_disconnect('server_name') --> disconnections connections associated with the specified foreign serverHaving said this, I'm not in favour of invalid_only flag, because if we choose to change the code in pgfdw_xact_callback to solve connection leak problem, we may not need this invalid_only flag at all, because at the end of txn (even for the txns in which the queries fail with error, pgfdw_xact_callback gets called), all the existing invalid connections get disconnected. Thoughts?
+1 not to have invalid_only flag. On the other hand, I think that postgres_fdw_get_connections() should return all the cached connections including invalidated ones. Otherwise, the number of connections observed via postgres_fdw_get_connections() may be different from the number of connections actually established, and which would be confusing to users.
If postgres_fdw_get_connections() has to return invalidated connections, I have few things mentioned in [1] to be clarified. Thoughts? Please have a look at the below comment before we decide to show up the invalid entries or not.
[1] - /messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com </messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com>
I was thinking to display the records having the columns for server name and boolean flag indicating whether it's invalidated or not. But I'm not sure if this is the best design for now. Probably we should revisit this after determining how to fix the connection-leak issue.
BTW, even after fixing the connection-leak issue, postgres_fdw_get_connections() may see invalidated cached connections when it's called during the transaction.
We will not output if the invalidated entry has no active connection[2], so if we fix the connection leak issue with the above discussed fix i.e closing all the invalidated connections at the end of next xact, there are less chances that we will output invalidated entries in the postgres_fdw_get_connections() output. Only case we may show up invalidated connections(which have active connections entry->conn) in the postgres_fdw_get_connections() output is as follows:
1) say we have few cached active connections exists in session 1
2) drop the user mapping (in another session) associated with any of the cached connections to make that entry invalid
3) run select * from postgres_fdw_get_connections(); in session 1. At the start of the xact, the invalidation message gets processed and the corresponding entry gets marked as invalid. If we allow invalid connections (that have entry->conn) to show up in the output, then we show them in the result of the query. At the end of xact, we close these invalid connections, in this case, user might think that he still have invalid connections active.
What about the case where the transaction started at the above 1) at session 1, and postgres_fdw_get_connections() in the above 3) is called within that transaction at session 1? In this case, postgres_fdw_get_connections() can return even invalidated connections?
If the query ran in 3) is not postgres_fdw_get_connections() and something else, then postgres_fdw_get_connections() will never get to show invalid connections as they would have closed the connections.
IMO, better not choose the invalid connections to show up in the postgres_fdw_get_connections() output, if we fix the connection leak issue with the above discussed fix i.e closing all the invalidated connections at the end of next xact
[2] +Datum +postgres_fdw_get_connections(PG_FUNCTION_ARGS) +{ + ArrayBuildState *astate = NULL; + + if (ConnectionHash) + { + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + Form_pg_user_mapping umap; + HeapTuple umaptup; + Form_pg_foreign_server fsrv; + HeapTuple fsrvtup; + + /* We only look for active and open remote connections. */ + if (!entry->conn) + continue;With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com>
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Dec 14, 2020 at 8:03 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
We will not output if the invalidated entry has no active connection[2], so if we fix the connection leak issue with the above discussed fix i.e closing all the invalidated connections at the end of next xact, there are less chances that we will output invalidated entries in the postgres_fdw_get_connections() output. Only case we may show up invalidated connections(which have active connections entry->conn) in the postgres_fdw_get_connections() output is as follows:
1) say we have few cached active connections exists in session 1
2) drop the user mapping (in another session) associated with any of the cached connections to make that entry invalid
3) run select * from postgres_fdw_get_connections(); in session 1. At the start of the xact, the invalidation message gets processed and the corresponding entry gets marked as invalid. If we allow invalid connections (that have entry->conn) to show up in the output, then we show them in the result of the query. At the end of xact, we close these invalid connections, in this case, user might think that he still have invalid connections active.What about the case where the transaction started at the above 1) at session 1, and postgres_fdw_get_connections() in the above 3) is called within that transaction at session 1? In this case, postgres_fdw_get_connections() can return even invalidated connections?
In that case, since the user mapping would have been dropped in
another session and we are in the middle of a txn in session 1, the
entries would not get marked as invalid until the invalidation message
gets processed by the session 1 which may happen if the session 1
opens a sub txn, if not then for postgres_fdw_get_connections() the
entries will still be active as they would not have been marked as
invalid yet and postgres_fdw_get_connections() would return them in
the output.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Dec 14, 2020, 9:47 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Mon, Dec 14, 2020 at 8:03 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
We will not output if the invalidated entry has no active connection[2], so if we fix the connection leak issue with the above discussed fix i.e closing all the invalidated connections at the end of next xact, there are less chances that we will output invalidated entries in the postgres_fdw_get_connections() output. Only case we may show up invalidated connections(which have active connections entry->conn) in the postgres_fdw_get_connections() output is as follows:
1) say we have few cached active connections exists in session 1
2) drop the user mapping (in another session) associated with any of the cached connections to make that entry invalid
3) run select * from postgres_fdw_get_connections(); in session 1. At the start of the xact, the invalidation message gets processed and the corresponding entry gets marked as invalid. If we allow invalid connections (that have entry->conn) to show up in the output, then we show them in the result of the query. At the end of xact, we close these invalid connections, in this case, user might think that he still have invalid connections active.What about the case where the transaction started at the above 1) at session 1, and postgres_fdw_get_connections() in the above 3) is called within that transaction at session 1? In this case, postgres_fdw_get_connections() can return even invalidated connections?
In that case, since the user mapping would have been dropped in
another session and we are in the middle of a txn in session 1, the
entries would not get marked as invalid until the invalidation message
gets processed by the session 1 which may happen if the session 1
opens a sub txn, if not then for postgres_fdw_get_connections() the
entries will still be active as they would not have been marked as
invalid yet and postgres_fdw_get_connections() would return them in
the output.
One more point for the above scenario: if the user mapping is dropped
in another session, then cache lookup for that entry in the
postgres_fdw_get_connections() returns a null tuple which I plan to
not throw an error, but just to skip in that case and continue. But if
the user mapping is not dropped in another session but altered, then
postgres_fdw_get_connections() still can show that in the output.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020/12/15 1:40, Bharath Rupireddy wrote:
On Mon, Dec 14, 2020, 9:47 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Mon, Dec 14, 2020 at 8:03 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
We will not output if the invalidated entry has no active connection[2], so if we fix the connection leak issue with the above discussed fix i.e closing all the invalidated connections at the end of next xact, there are less chances that we will output invalidated entries in the postgres_fdw_get_connections() output. Only case we may show up invalidated connections(which have active connections entry->conn) in the postgres_fdw_get_connections() output is as follows:
1) say we have few cached active connections exists in session 1
2) drop the user mapping (in another session) associated with any of the cached connections to make that entry invalid
3) run select * from postgres_fdw_get_connections(); in session 1. At the start of the xact, the invalidation message gets processed and the corresponding entry gets marked as invalid. If we allow invalid connections (that have entry->conn) to show up in the output, then we show them in the result of the query. At the end of xact, we close these invalid connections, in this case, user might think that he still have invalid connections active.What about the case where the transaction started at the above 1) at session 1, and postgres_fdw_get_connections() in the above 3) is called within that transaction at session 1? In this case, postgres_fdw_get_connections() can return even invalidated connections?
In that case, since the user mapping would have been dropped in
another session and we are in the middle of a txn in session 1, the
entries would not get marked as invalid until the invalidation message
gets processed by the session 1 which may happen if the session 1
Yes, and this can happen by other commands, for example, CREATE TABLE.
opens a sub txn, if not then for postgres_fdw_get_connections() the
entries will still be active as they would not have been marked as
invalid yet and postgres_fdw_get_connections() would return them in
the output.One more point for the above scenario: if the user mapping is dropped
in another session, then cache lookup for that entry in the
postgres_fdw_get_connections() returns a null tuple which I plan to
not throw an error, but just to skip in that case and continue. But if
the user mapping is not dropped in another session but altered, then
postgres_fdw_get_connections() still can show that in the output.
Yes, so *if* we really want to return even connection invalidated by drop of
user mapping, the cached connection entry may need to store not only
user mapping id but also server id so that we can get the server name without
user mapping entry.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Dec 14, 2020 at 11:00 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
One more point for the above scenario: if the user mapping is dropped
in another session, then cache lookup for that entry in the
postgres_fdw_get_connections() returns a null tuple which I plan to
not throw an error, but just to skip in that case and continue. But if
the user mapping is not dropped in another session but altered, then
postgres_fdw_get_connections() still can show that in the output.Yes, so *if* we really want to return even connection invalidated by drop of
user mapping, the cached connection entry may need to store not only
user mapping id but also server id so that we can get the server name without
user mapping entry.
We can do that, but what happens if the foreign server itself get
dropped with cascade option in another session, use case is as
follows:
1) Run a foreign query in session 1 with server 1, user mapping 1
2) Try to drop foreign server 1, then we would not be allowed to do so
because of dependency, if we use CASCADE, then the dependent user
mapping 1 and foreign tables get dropped too.
3) Run the postgres_fdw_get_connections(), at the start of txn, the
cached entry gets invalidated via pgfdw_inval_callback() and we try to
use the stored server id of the invalid entry (for which the foreign
server would have been dropped) and lookup in sys catalogues, so again
a null tuple is returned.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Dec 14, 2020 at 8:03 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2020/12/14 14:36, Bharath Rupireddy wrote:
On Mon, Dec 14, 2020 at 9:38 AM Fujii Masao <masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com>> wrote:
On 2020/12/12 15:05, Bharath Rupireddy wrote:
On Sat, Dec 12, 2020 at 12:19 AM Fujii Masao <masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com> <mailto:masao.fujii@oss.nttdata.com <mailto:masao.fujii@oss.nttdata.com>>> wrote:
I was thinking that in the case of drop of user mapping or server, hash_search(ConnnectionHash) in GetConnection() cannot find the cached connection entry invalidated by that drop. Because "user->umid" used as hash key is changed. So I was thinking that that invalidated connection will not be closed nor reconnected.
You are right in saying that the connection leaks.
Use case 1:
1) Run foreign query in session1 with server1, user mapping1
2) Drop user mapping1 in another session2, invalidation message gets logged which will have to be processed by other sessions
3) Run foreign query again in session1, at the start of txn, the cached entry gets invalidated via pgfdw_inval_callback(). Whatever may be the type of foreign query (select, update, explain, delete, insert, analyze etc.), upon next call to GetUserMapping() from postgres_fdw.c, the cache lookup fails(with ERROR: user mapping not found for "XXXX") since the user mapping1 has been dropped in session2 and the query will also fail before reaching GetConnection() where the connections associated with invalidated entries would have got disconnected.So, the connection associated with invalidated entry would remain until the local session exits which is a problem to solve.
Use case 2:
1) Run foreign query in session1 with server1, user mapping1
2) Try to drop foreign server1, then we would not be allowed to do so because of dependency. If we use CASCADE, then the dependent user mapping1 and foreign tables get dropped too [1].
3) Run foreign query again in session1, at the start of txn, the cached entry gets invalidated via pgfdw_inval_callback(), it fails because there is no foreign table and user mapping1.But, note that the connection remains open in session1, which is again a problem to solve.
To solve the above connection leak problem, it looks like the right place to close all the invalid connections is pgfdw_xact_callback(), once registered, which gets called at the end of every txn in the current session(by then all the sub txns also would have been finished). Note that if there are too many invalidated entries, then one of the following txn has to bear running this extra code, but that's okay than having leaked connections. Thoughts? If okay, I can code a separate patch.
Thanks for further analysis! Sounds good. Also +1 for making it as separate patch. Maybe only this patch needs to be back-patched.
Thanks. Yeah once agreed on the fix, +1 to back patch. Shall I start a separate thread for connection leak issue and patch, so that others might have different thoughts??
Yes, of course!
Thanks. I posted the patch in a separate thread[1]/messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com for fixing the
connection leak problem.
[1]: /messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Dec 14, 2020 at 11:13 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Mon, Dec 14, 2020 at 11:00 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:One more point for the above scenario: if the user mapping is dropped
in another session, then cache lookup for that entry in the
postgres_fdw_get_connections() returns a null tuple which I plan to
not throw an error, but just to skip in that case and continue. But if
the user mapping is not dropped in another session but altered, then
postgres_fdw_get_connections() still can show that in the output.Yes, so *if* we really want to return even connection invalidated by drop of
user mapping, the cached connection entry may need to store not only
user mapping id but also server id so that we can get the server name without
user mapping entry.We can do that, but what happens if the foreign server itself get
dropped with cascade option in another session, use case is as
follows:1) Run a foreign query in session 1 with server 1, user mapping 1
2) Try to drop foreign server 1, then we would not be allowed to do so
because of dependency, if we use CASCADE, then the dependent user
mapping 1 and foreign tables get dropped too.
3) Run the postgres_fdw_get_connections(), at the start of txn, the
cached entry gets invalidated via pgfdw_inval_callback() and we try to
use the stored server id of the invalid entry (for which the foreign
server would have been dropped) and lookup in sys catalogues, so again
a null tuple is returned.
Hi,
Any further thoughts on this would be really helpful.
Discussion here is on the point - whether to show up the invalidated
connections in the output of the new postgres_fdw_get_connections()
function? If we were to show, then because of the solution we proposed
for the connection leak problem in [1]/messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com, will the invalidated entries
be shown every time?
[1]: /messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Hi
Discussion here is on the point - whether to show up the invalidated
connections in the output of the new postgres_fdw_get_connections()
function? If we were to show, then because of the solution we proposed for
the connection leak problem in [1], will the invalidated entries be shown
every time?
IMO, we introduced the function postgres_fdw_get_connections to decide
whether there are too many connections exists and we should disconnect them.
If User decide to disconnect, we have two cases:
1. user decide to disconnect one of them,
I think it’s ok for user to disconnect invalidated connection, so we'd better list the invalidated connections.
2. User decide to disconnect all of them. In this case,
It seems postgres_fdw_disconnect will disconnect both invalidated and not connections,
And we should let user realize what connections they are disconnecting, so we should list the invalidated connections.
Based on the above two cases, Personlly, I think we can list the invalidated connections.
-----
I took a look into the patch, and have a little issue:
+bool disconnect_cached_connections(uint32 hashvalue, bool all)
+ if (all)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ result = true;
+ }
If disconnect_cached_connections is called to disconnect all the connections,
should we reset the 'xact_got_connection' flag ?
[1] -
/messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLv
A%2B4yeBThRfxMz7Oxbk1aHcpQ%40mail.gmail.com
The patch about connection leak looks good to me.
And I have a same issue about the new 'have_invalid_connections' flag,
If we disconnect all the connections, should we reset the flag ?
Best regards,
houzj
On Thu, Dec 17, 2020 at 5:38 PM Hou, Zhijie <houzj.fnst@cn.fujitsu.com> wrote:
Discussion here is on the point - whether to show up the invalidated
connections in the output of the new postgres_fdw_get_connections()
function? If we were to show, then because of the solution we proposed for
the connection leak problem in [1], will the invalidated entries be shown
every time?IMO, we introduced the function postgres_fdw_get_connections to decide
whether there are too many connections exists and we should disconnect them.If User decide to disconnect, we have two cases:
1. user decide to disconnect one of them,
I think it’s ok for user to disconnect invalidated connection, so we'd better list the invalidated connections.2. User decide to disconnect all of them. In this case,
It seems postgres_fdw_disconnect will disconnect both invalidated and not connections,
And we should let user realize what connections they are disconnecting, so we should list the invalidated connections.Based on the above two cases, Personlly, I think we can list the invalidated connections.
I will do that. So, the output will have a list of pairs like
(server_name, true/false), true/false is for valid/invalid connection.
-----
I took a look into the patch, and have a little issue:+bool disconnect_cached_connections(uint32 hashvalue, bool all) + if (all) + { + hash_destroy(ConnectionHash); + ConnectionHash = NULL; + result = true; + }If disconnect_cached_connections is called to disconnect all the connections,
should we reset the 'xact_got_connection' flag ?
I think we must allow postgres_fdw_disconnect() to disconnect the
particular/all connections only when the corresponding entries have no
open txns or connections are not being used in that txn, otherwise
not. We may end up closing/disconnecting the connection that's still
being in use because entry->xact_dept can even go more than 1 for sub
txns. See use case [1]BEGIN; SELECT 1 FROM ft1 LIMIT 1; --> server 1 entry->xact_depth is 1 SAVEPOINT s; SELECT 1 FROM ft1 LIMIT 1; --> entry->xact_depth becomes 2 SELECT postgres_fdw_disconnect()/postgres_fdw_disconnect('server 1'); --> I think we should not close the connection as it's txn is still open. COMMIT;.
+ if ((all || entry->server_hashvalue == hashvalue) &&
entry->xact_depth == 0 &&
+ entry->conn)
+ {
+ disconnect_pg_server(entry);
+ result = true;
+ }
Thoughts?
And to reset the 'xact_got_connection' flag: I think we should reset
it only when we close all the connections i.e. when all the
connections are at entry->xact_depth = 0, otherwise not. Same for
have_invalid_connections flag as well.
[1]: BEGIN; SELECT 1 FROM ft1 LIMIT 1; --> server 1 entry->xact_depth is 1 SAVEPOINT s; SELECT 1 FROM ft1 LIMIT 1; --> entry->xact_depth becomes 2 SELECT postgres_fdw_disconnect()/postgres_fdw_disconnect('server 1'); --> I think we should not close the connection as it's txn is still open. COMMIT;
BEGIN;
SELECT 1 FROM ft1 LIMIT 1; --> server 1 entry->xact_depth is 1
SAVEPOINT s;
SELECT 1 FROM ft1 LIMIT 1; --> entry->xact_depth becomes 2
SELECT postgres_fdw_disconnect()/postgres_fdw_disconnect('server 1');
--> I think we should not close the connection as it's txn is still
open.
COMMIT;
[1] -
/messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLv
A%2B4yeBThRfxMz7Oxbk1aHcpQ%40mail.gmail.comThe patch about connection leak looks good to me.
And I have a same issue about the new 'have_invalid_connections' flag,
If we disconnect all the connections, should we reset the flag ?
Yes as mentioned in the above comment.
Thanks for reviewing the connection leak patch. It will be good if the
review comments for the connection leak flag is provided separately in
that thread. I added it to commitfest -
https://commitfest.postgresql.org/31/2882/.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020-12-17 18:02, Bharath Rupireddy wrote:
On Thu, Dec 17, 2020 at 5:38 PM Hou, Zhijie <houzj.fnst@cn.fujitsu.com>
wrote:I took a look into the patch, and have a little issue:
+bool disconnect_cached_connections(uint32 hashvalue, bool all) + if (all) + { + hash_destroy(ConnectionHash); + ConnectionHash = NULL; + result = true; + }If disconnect_cached_connections is called to disconnect all the
connections,
should we reset the 'xact_got_connection' flag ?I think we must allow postgres_fdw_disconnect() to disconnect the
particular/all connections only when the corresponding entries have no
open txns or connections are not being used in that txn, otherwise
not. We may end up closing/disconnecting the connection that's still
being in use because entry->xact_dept can even go more than 1 for sub
txns. See use case [1].+ if ((all || entry->server_hashvalue == hashvalue) && entry->xact_depth == 0 && + entry->conn) + { + disconnect_pg_server(entry); + result = true; + }Thoughts?
I think that you are right. Actually, I was thinking about much more
simple solution to this problem --- just restrict
postgres_fdw_disconnect() to run only *outside* of explicit transaction
block. This should protect everyone from closing its underlying
connections, but seems to be a bit more restrictive than you propose.
Just thought, that if we start closing fdw connections in the open xact
block:
1) Close a couple of them.
2) Found one with xact_depth > 0 and error out.
3) End up in the mixed state: some of connections were closed, but some
them not, and it cannot be rolled back with the xact.
In other words, I have some doubts about allowing to call a
non-transactional by its nature function in the transaction block.
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
On Thu, Dec 17, 2020 at 10:32 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
On 2020-12-17 18:02, Bharath Rupireddy wrote:
On Thu, Dec 17, 2020 at 5:38 PM Hou, Zhijie <houzj.fnst@cn.fujitsu.com>
wrote:I took a look into the patch, and have a little issue:
+bool disconnect_cached_connections(uint32 hashvalue, bool all) + if (all) + { + hash_destroy(ConnectionHash); + ConnectionHash = NULL; + result = true; + }If disconnect_cached_connections is called to disconnect all the
connections,
should we reset the 'xact_got_connection' flag ?I think we must allow postgres_fdw_disconnect() to disconnect the
particular/all connections only when the corresponding entries have no
open txns or connections are not being used in that txn, otherwise
not. We may end up closing/disconnecting the connection that's still
being in use because entry->xact_dept can even go more than 1 for sub
txns. See use case [1].+ if ((all || entry->server_hashvalue == hashvalue) && entry->xact_depth == 0 && + entry->conn) + { + disconnect_pg_server(entry); + result = true; + }Thoughts?
I think that you are right. Actually, I was thinking about much more
simple solution to this problem --- just restrict
postgres_fdw_disconnect() to run only *outside* of explicit transaction
block. This should protect everyone from closing its underlying
connections, but seems to be a bit more restrictive than you propose.
Agree that it's restrictive from a usability point of view. I think
having entry->xact_depth == 0 should be enough to protect from closing
any connections that are currently in use.
Say the user has called postgres_fdw_disconnect('myserver1'), if it's
currently in use in that xact, then we can return false or even go
further and issue a warning along with false. Also if
postgres_fdw_disconnect() is called for closing all connections and
any one of the connections are currently in use in the xact, then also
we can return: true and a warning if atleast one connection is closed
or false and a warning if all the connections are in use.
The warning message can be something like - for the first case -
"could not close the server connection as it is in use" and for the
second case - "could not close some of the connections as they are in
use".
Thoughts?
Just thought, that if we start closing fdw connections in the open xact
block:1) Close a couple of them.
2) Found one with xact_depth > 0 and error out.
3) End up in the mixed state: some of connections were closed, but some
them not, and it cannot be rolled back with the xact.
We don't error out, but we may issue a warning (if agreed on the above
reponse) and return false, but definitely not an error.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Hi,
I'm posting a v4-0001 patch for the new functions
postgres_fdw_get_connections() and postgres_fdw_disconnect(). In this
patch, I tried to address the review comments provided upthread.
At a high level, the changes include:
1) Storing the foreign server id in the cache entry which will help to
fetch the server name associated with it easily.
2) postgres_fdw_get_connections now returns an open connection server
name and true or false to indicate whether it's valid or not.
3) postgres_fdw_get_connections can issue a warning when the cache
look up for server name returns null i.e. the foreign server is
dropped. Please see the comments before postgres_fdw_get_connections
in which situations this is possible.
4) postgres_fdw_disconnect('myserver') disconnects the open connection
only when it's not being used in the current xact. If it's used, then
false is returned and a warning is issued.
5) postgres_fdw_disconnect() disconnects all the connections only when
they are not being used in the current xact. If at least one
connection that's being used exists, then it issues a warning and
returns true if at least one open connection gets closed otherwise
false. If there are no connections made yet or connection cache is
empty, then also false is returned.
6) postgres_fdw_disconnect can discard the entire cache if there is no
active connection.
Thoughts?
Below things are still pending which I plan to post new patches after
the v4-0001 is reviewed:
1) changing the version of postgres_fdw--1.0.sql to postgres_fdw--1.1.sql
2) 0002 and 0003 patches having keep_connections GUC and
keep_connection server level option.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v4-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/x-patch; name=v4-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From 82b82b901e8f0ca84e1b21454a3b68f4fd8f0016 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 30 Dec 2020 11:07:35 +0530
Subject: [PATCH v4] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/connection.c | 319 +++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 415 +++++++++++++++++-
contrib/postgres_fdw/postgres_fdw--1.0.sql | 15 +
contrib/postgres_fdw/sql/postgres_fdw.sql | 166 +++++++
doc/src/sgml/postgres-fdw.sgml | 57 ++-
5 files changed, 953 insertions(+), 19 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index d841cec39b..496ef2bae2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,6 +58,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -73,6 +76,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +103,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +284,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +801,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1004,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1119,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1171,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1181,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1366,279 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List the foreign server connections.
+ *
+ * This function takes no input parameter and returns an array with elements
+ * as pairs of foreign server name and true/false to show whether or not the
+ * connection is valid. The array elements look like (srv1, true),
+ * (srv2, false), (srv3, true) ... (srvn, true). True if the connection is
+ * valid. False if the connection is invalidated in pgfdw_inval_callback. NULL
+ * is returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ StringInfoData buf;
+ uint16 no_server_conn_cnt = 0;
+
+ if (!ConnectionHash)
+ PG_RETURN_NULL();
+
+ initStringInfo(&buf);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
+
+ /* stash away the prepared string into array value */
+ astate = accumArrayResult(astate, CStringGetTextDatum(buf.data),
+ false, TEXTOID, CurrentMemoryContext);
+ resetStringInfo(&buf);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections get closed either in the next use or at the end of the current transaction.")
+ : errdetail("Such connection gets closed either in the next use or at the end of the current transaction.")));
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (server)
+ {
+ uint32 hashvalue;
+
+ hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ result = disconnect_cached_connections(hashvalue, false,
+ &is_in_use);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..ef9f6d7bbd 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,378 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+ (loopback, true)
+(2 rows)
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+(1 row)
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, false)
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection gets closed either in the next use or at the end of the current transaction.
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections get closed either in the next use or at the end of the current transaction.
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql
index a0f0fc1bf4..edbb5911d2 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0.sql
@@ -16,3 +16,18 @@ LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER postgres_fdw
HANDLER postgres_fdw_handler
VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS text[]
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..2267aee630 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,148 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+COMMIT;
+
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..405923f2aa 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,7 +477,47 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
- </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
+ The foreign server connections can get invalidated due to alter statements
+ on foreign server or user mapping. If there are no open connections, then
+ <literal>NULL</literal> is returned. This function issues a warning in case
+ for any connection, the associated foreign server has been dropped and the
+ server name can not be fetched from the system catalogues.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still being
+ used in the current transaction, it is not discarded, instead a warning is
+ issued and <literal>false</literal> is returned. <literal>false</literal> is
+ returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the unused open
+ connections that are previously made to the foreign servers and returns
+ <literal>true</literal>. If there is any open connection that is still being
+ used in the current transaction, then a warning is issued. <literal>false</literal>
+ is returned when no open connection is discarded or there are no open
+ connections at all.
+ </para>
+
+</sect2>
<sect2>
<title>Connection Management</title>
@@ -490,6 +530,21 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
On 2020-12-30 09:10, Bharath Rupireddy wrote:
Hi,
I'm posting a v4-0001 patch for the new functions
postgres_fdw_get_connections() and postgres_fdw_disconnect(). In this
patch, I tried to address the review comments provided upthread.Thoughts?
I still have some doubts that it is worth of allowing to call
postgres_fdw_disconnect() in the explicit transaction block, since it
adds a lot of things to care and check for. But otherwise current logic
looks solid.
+ errdetail("Such connections get closed either in the next use or
at the end of the current transaction.")
+ : errdetail("Such connection gets closed either in the next use or
at the end of the current transaction.")));
Does it really have a chance to get closed on the next use? If foreign
server is dropped then user mapping should be dropped as well (either
with CASCADE or manually), but we do need user mapping for a local cache
lookup. That way, if I understand all the discussion up-thread
correctly, we can only close such connections at the end of xact, do we?
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
I think that this will be corrected later by pg_indent, but still. In
this comment section following points 1) and 2) have a different
combination of tabs/spaces.
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
Attachments:
v4-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/x-patch; name=v4-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From 82b82b901e8f0ca84e1b21454a3b68f4fd8f0016 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 30 Dec 2020 11:07:35 +0530
Subject: [PATCH v4] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/connection.c | 319 +++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 415 +++++++++++++++++-
contrib/postgres_fdw/postgres_fdw--1.0.sql | 15 +
contrib/postgres_fdw/sql/postgres_fdw.sql | 166 +++++++
doc/src/sgml/postgres-fdw.sgml | 57 ++-
5 files changed, 953 insertions(+), 19 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index d841cec39b..496ef2bae2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,6 +58,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -73,6 +76,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +103,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +284,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +801,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1004,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1119,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1171,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1181,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1366,279 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List the foreign server connections.
+ *
+ * This function takes no input parameter and returns an array with elements
+ * as pairs of foreign server name and true/false to show whether or not the
+ * connection is valid. The array elements look like (srv1, true),
+ * (srv2, false), (srv3, true) ... (srvn, true). True if the connection is
+ * valid. False if the connection is invalidated in pgfdw_inval_callback. NULL
+ * is returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ StringInfoData buf;
+ uint16 no_server_conn_cnt = 0;
+
+ if (!ConnectionHash)
+ PG_RETURN_NULL();
+
+ initStringInfo(&buf);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
+
+ /* stash away the prepared string into array value */
+ astate = accumArrayResult(astate, CStringGetTextDatum(buf.data),
+ false, TEXTOID, CurrentMemoryContext);
+ resetStringInfo(&buf);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections get closed either in the next use or at the end of the current transaction.")
+ : errdetail("Such connection gets closed either in the next use or at the end of the current transaction.")));
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (server)
+ {
+ uint32 hashvalue;
+
+ hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ result = disconnect_cached_connections(hashvalue, false,
+ &is_in_use);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..ef9f6d7bbd 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,378 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+ (loopback, true)
+(2 rows)
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+(1 row)
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, false)
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection gets closed either in the next use or at the end of the current transaction.
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections get closed either in the next use or at the end of the current transaction.
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql
index a0f0fc1bf4..edbb5911d2 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0.sql
@@ -16,3 +16,18 @@ LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER postgres_fdw
HANDLER postgres_fdw_handler
VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS text[]
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..2267aee630 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,148 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+COMMIT;
+
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..405923f2aa 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,7 +477,47 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
- </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
+ The foreign server connections can get invalidated due to alter statements
+ on foreign server or user mapping. If there are no open connections, then
+ <literal>NULL</literal> is returned. This function issues a warning in case
+ for any connection, the associated foreign server has been dropped and the
+ server name can not be fetched from the system catalogues.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still being
+ used in the current transaction, it is not discarded, instead a warning is
+ issued and <literal>false</literal> is returned. <literal>false</literal> is
+ returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the unused open
+ connections that are previously made to the foreign servers and returns
+ <literal>true</literal>. If there is any open connection that is still being
+ used in the current transaction, then a warning is issued. <literal>false</literal>
+ is returned when no open connection is discarded or there are no open
+ connections at all.
+ </para>
+
+</sect2>
<sect2>
<title>Connection Management</title>
@@ -490,6 +530,21 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
On Wed, Dec 30, 2020 at 5:20 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
On 2020-12-30 09:10, Bharath Rupireddy wrote:
Hi,
I'm posting a v4-0001 patch for the new functions
postgres_fdw_get_connections() and postgres_fdw_disconnect(). In this
patch, I tried to address the review comments provided upthread.Thoughts?
I still have some doubts that it is worth of allowing to call
postgres_fdw_disconnect() in the explicit transaction block, since it
adds a lot of things to care and check for. But otherwise current logic
looks solid.+ errdetail("Such connections get closed either in the next use or at the end of the current transaction.") + : errdetail("Such connection gets closed either in the next use or at the end of the current transaction.")));Does it really have a chance to get closed on the next use? If foreign
server is dropped then user mapping should be dropped as well (either
with CASCADE or manually), but we do need user mapping for a local cache
lookup. That way, if I understand all the discussion up-thread
correctly, we can only close such connections at the end of xact, do we?
The next use of such a connection in the following query whose foreign
server would have been dropped fails because of the cascading that can
happen to drop the user mapping and the foreign table as well. During
the start of the next query such connection will be marked as
invalidated because xact_depth of that connection is > 1 and when the
fail happens, txn gets aborted due to which pgfdw_xact_callback gets
called and in that the connection gets closed. To make it more clear,
please have a look at the scenarios [1]case 1: BEGIN; SELECT 1 FROM f1 LIMIT 1; --> xact_depth becomes 1 DROP SERVER loopback1 CASCADE; --> drop cascades to the user mapping and the foreign table and the connection gets invalidated in pgfdw_inval_callback because xact_depth is 1 SELECT 1 FROM f1 LIMIT 1; --> since the failure occurs for this query and txn is aborted, the connection gets closed in pgfdw_xact_callback. SELECT * FROM postgres_fdw_get_connections(); --> txn was aborted SELECT * FROM postgres_fdw_disconnect(); --> txn was aborted COMMIT;.
I still feel the detailed message "Such connections get closed either
in the next use or at the end of the current transaction" is
appropriate. Please have a closer look at the possible use cases [1]case 1: BEGIN; SELECT 1 FROM f1 LIMIT 1; --> xact_depth becomes 1 DROP SERVER loopback1 CASCADE; --> drop cascades to the user mapping and the foreign table and the connection gets invalidated in pgfdw_inval_callback because xact_depth is 1 SELECT 1 FROM f1 LIMIT 1; --> since the failure occurs for this query and txn is aborted, the connection gets closed in pgfdw_xact_callback. SELECT * FROM postgres_fdw_get_connections(); --> txn was aborted SELECT * FROM postgres_fdw_disconnect(); --> txn was aborted COMMIT;.
And IMO, anyone dropping a foreign server inside an explicit txn block
in which the foreign server was used is extremely rare, so still
showing this message and allowing postgres_fdw_disconnect() in
explicit txn block is useful. For all other cases the
postgres_fdw_disconnect behaves as expected.
Thoughts?
[1]: case 1: BEGIN; SELECT 1 FROM f1 LIMIT 1; --> xact_depth becomes 1 DROP SERVER loopback1 CASCADE; --> drop cascades to the user mapping and the foreign table and the connection gets invalidated in pgfdw_inval_callback because xact_depth is 1 SELECT 1 FROM f1 LIMIT 1; --> since the failure occurs for this query and txn is aborted, the connection gets closed in pgfdw_xact_callback. SELECT * FROM postgres_fdw_get_connections(); --> txn was aborted SELECT * FROM postgres_fdw_disconnect(); --> txn was aborted COMMIT;
case 1:
BEGIN;
SELECT 1 FROM f1 LIMIT 1; --> xact_depth becomes 1
DROP SERVER loopback1 CASCADE; --> drop cascades to the user mapping
and the foreign table and the connection gets invalidated in
pgfdw_inval_callback because xact_depth is 1
SELECT 1 FROM f1 LIMIT 1; --> since the failure occurs for this query
and txn is aborted, the connection gets closed in pgfdw_xact_callback.
SELECT * FROM postgres_fdw_get_connections(); --> txn was aborted
SELECT * FROM postgres_fdw_disconnect(); --> txn was aborted
COMMIT;
case 2:
BEGIN;
SELECT 1 FROM f1 LIMIT 1; --> xact_depth becomes 1
DROP SERVER loopback1 CASCADE; --> drop cascades to the user mapping
and the foreign table and the connection gets invalidated in
pgfdw_inval_callback because xact_depth is 1
SELECT * FROM postgres_fdw_get_connections(); --> shows the above
warning because foreign server name can not be fetched
SELECT * FROM postgres_fdw_disconnect(); --> the connection can not be
closed here as well because xact_depth is 1, then it issues a warning
"cannot close any connection because they are still in use"
COMMIT; --> finally the connection gets closed here in pgfdw_xact_callback.
case 3:
SELECT 1 FROM f1 LIMIT 1;
BEGIN;
DROP SERVER loopback1 CASCADE; --> drop cascades to the user mapping
and the foreign table and the connection gets closed in
pgfdw_inval_callback because xact_depth is 0
SELECT 1 FROM f1 LIMIT 1; --> since the failure occurs for this query
and the connection was closed previously then the txn gets aborted
SELECT * FROM postgres_fdw_get_connections(); --> txn was aborted
SELECT * FROM postgres_fdw_disconnect(); --> txn was aborted
COMMIT;
+ * This function returns false if the cache doesn't exist. + * When the cache exists:I think that this will be corrected later by pg_indent, but still. In
this comment section following points 1) and 2) have a different
combination of tabs/spaces.
I can change that in the next version.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2020-12-30 17:59, Bharath Rupireddy wrote:
On Wed, Dec 30, 2020 at 5:20 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:On 2020-12-30 09:10, Bharath Rupireddy wrote:
I still have some doubts that it is worth of allowing to call
postgres_fdw_disconnect() in the explicit transaction block, since it
adds a lot of things to care and check for. But otherwise current
logic
looks solid.+ errdetail("Such connections get closed either in the next use or at the end of the current transaction.") + : errdetail("Such connection gets closed either in the next use or at the end of the current transaction.")));Does it really have a chance to get closed on the next use? If foreign
server is dropped then user mapping should be dropped as well (either
with CASCADE or manually), but we do need user mapping for a local
cache
lookup. That way, if I understand all the discussion up-thread
correctly, we can only close such connections at the end of xact, do
we?The next use of such a connection in the following query whose foreign
server would have been dropped fails because of the cascading that can
happen to drop the user mapping and the foreign table as well. During
the start of the next query such connection will be marked as
invalidated because xact_depth of that connection is > 1 and when the
fail happens, txn gets aborted due to which pgfdw_xact_callback gets
called and in that the connection gets closed. To make it more clear,
please have a look at the scenarios [1].
In my understanding 'connection gets closed either in the next use'
means that connection will be closed next time someone will try to use
it, i.e. GetConnection() will be called and it closes this connection
because of a bad state. However, if foreign server is dropped
GetConnection() cannot lookup the connection because it needs a user
mapping oid as a key.
I had a look on your scenarios. IIUC, under **next use** you mean a
select attempt from a table belonging to the same foreign server, which
leads to a transaction abort and connection gets closed in the xact
callback. Sorry, maybe I am missing something, but this just confirms
that such connections only get closed in the xact callback (taking into
account your recently committed patch [1]/messages/by-id/8b2aa1aa-c638-12a8-cb56-ea0f0a5019cf@oss.nttdata.com), so 'next use' looks
misleading.
[1]: /messages/by-id/8b2aa1aa-c638-12a8-cb56-ea0f0a5019cf@oss.nttdata.com
/messages/by-id/8b2aa1aa-c638-12a8-cb56-ea0f0a5019cf@oss.nttdata.com
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
On Wed, Dec 30, 2020 at 11:11 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:
On 2020-12-30 17:59, Bharath Rupireddy wrote:
On Wed, Dec 30, 2020 at 5:20 PM Alexey Kondratov
<a.kondratov@postgrespro.ru> wrote:On 2020-12-30 09:10, Bharath Rupireddy wrote:
I still have some doubts that it is worth of allowing to call
postgres_fdw_disconnect() in the explicit transaction block, since it
adds a lot of things to care and check for. But otherwise current
logic
looks solid.+ errdetail("Such connections get closed either in the next use or at the end of the current transaction.") + : errdetail("Such connection gets closed either in the next use or at the end of the current transaction.")));Does it really have a chance to get closed on the next use? If foreign
server is dropped then user mapping should be dropped as well (either
with CASCADE or manually), but we do need user mapping for a local
cache
lookup. That way, if I understand all the discussion up-thread
correctly, we can only close such connections at the end of xact, do
we?The next use of such a connection in the following query whose foreign
server would have been dropped fails because of the cascading that can
happen to drop the user mapping and the foreign table as well. During
the start of the next query such connection will be marked as
invalidated because xact_depth of that connection is > 1 and when the
fail happens, txn gets aborted due to which pgfdw_xact_callback gets
called and in that the connection gets closed. To make it more clear,
please have a look at the scenarios [1].In my understanding 'connection gets closed either in the next use'
means that connection will be closed next time someone will try to use
it, i.e. GetConnection() will be called and it closes this connection
because of a bad state. However, if foreign server is dropped
GetConnection() cannot lookup the connection because it needs a user
mapping oid as a key.
Right. We don't reach GetConnection(). The look up in either
GetForeignTable() or GetUserMapping() or GetForeignServer() fails (and
so the query) depending one which one gets called first.
I had a look on your scenarios. IIUC, under **next use** you mean a
select attempt from a table belonging to the same foreign server, which
leads to a transaction abort and connection gets closed in the xact
callback. Sorry, maybe I am missing something, but this just confirms
that such connections only get closed in the xact callback (taking into
account your recently committed patch [1]), so 'next use' looks
misleading.[1]
/messages/by-id/8b2aa1aa-c638-12a8-cb56-ea0f0a5019cf@oss.nttdata.com
Right. I meant the "next use" as the select attempt on a foreign table
with that foreign server. If no select query is run, then at the end
of the current txn that connection gets closed. Yes internally such
connection gets closed in pgfdw_xact_callback.
If the errdetail("Such connections get closed either in the next use
or at the end of the current transaction.") looks confusing, how about
1) errdetail("Such connection gets discarded while closing the remote
transaction.")/errdetail("Such connections get discarded while closing
the remote transaction.")
2) errdetail("Such connection is discarded at the end of remote
transaction.")/errdetail("Such connections are discarded at the end of
remote transaction.")
I prefer 2) Thoughts?
Because we already print a message in pgfdw_xact_callback -
elog(DEBUG3, "closing remote transaction on connection %p"
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Dec 31, 2020 at 8:29 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Right. I meant the "next use" as the select attempt on a foreign table
with that foreign server. If no select query is run, then at the end
of the current txn that connection gets closed. Yes internally such
connection gets closed in pgfdw_xact_callback.If the errdetail("Such connections get closed either in the next use
or at the end of the current transaction.") looks confusing, how about1) errdetail("Such connection gets discarded while closing the remote
transaction.")/errdetail("Such connections get discarded while closing
the remote transaction.")
2) errdetail("Such connection is discarded at the end of remote
transaction.")/errdetail("Such connections are discarded at the end of
remote transaction.")I prefer 2) Thoughts?
Because we already print a message in pgfdw_xact_callback -
elog(DEBUG3, "closing remote transaction on connection %p"
I changed the message to "Such connection is discarded at the end of
remote transaction.".
I'm attaching v5 patch set i.e. all the patches 0001 ( for new
functions), 0002 ( for GUC) and 0003 (for server level option). I have
also made the changes for increasing the version of
postgres_fdw--1.0.sql from 1.0 to 1.1.
I have no open points from my end. Please consider the v5 patch set
for further review.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v5-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/x-patch; name=v5-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From 413a8b34f8e1e1faf03aecad926a4b2a7a724aaa Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 1 Jan 2021 14:09:54 +0530
Subject: [PATCH v5 1/3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 319 +++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 415 +++++++++++++++++-
...dw--1.0.sql => postgres_fdw--1.0--1.1.sql} | 6 +-
contrib/postgres_fdw/postgres_fdw--1.1.sql | 33 ++
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 166 +++++++
doc/src/sgml/postgres-fdw.sgml | 57 ++-
8 files changed, 976 insertions(+), 24 deletions(-)
rename contrib/postgres_fdw/{postgres_fdw--1.0.sql => postgres_fdw--1.0--1.1.sql} (60%)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..52d3dac0bd 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index d841cec39b..b9f6050f4b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,6 +58,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -73,6 +76,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +103,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +284,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +801,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1004,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1119,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1171,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1181,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1366,279 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List the foreign server connections.
+ *
+ * This function takes no input parameter and returns an array with elements
+ * as pairs of foreign server name and true/false to show whether or not the
+ * connection is valid. The array elements look like (srv1, true),
+ * (srv2, false), (srv3, true) ... (srvn, true). True if the connection is
+ * valid. False if the connection is invalidated in pgfdw_inval_callback. NULL
+ * is returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ StringInfoData buf;
+ uint16 no_server_conn_cnt = 0;
+
+ if (!ConnectionHash)
+ PG_RETURN_NULL();
+
+ initStringInfo(&buf);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
+
+ /* stash away the prepared string into array value */
+ astate = accumArrayResult(astate, CStringGetTextDatum(buf.data),
+ false, TEXTOID, CurrentMemoryContext);
+ resetStringInfo(&buf);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections are discarded at the end of remote transaction.")
+ : errdetail("Such connection is discarded at the end of remote transaction.")));
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (server)
+ {
+ uint32 hashvalue;
+
+ hashvalue =
+ GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ result = disconnect_cached_connections(hashvalue, false,
+ &is_in_use);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..ba29bc6131 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,378 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+ (loopback, true)
+(2 rows)
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+(1 row)
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, false)
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection is discarded at the end of remote transaction.
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections are discarded at the end of remote transaction.
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
similarity index 60%
rename from contrib/postgres_fdw/postgres_fdw--1.0.sql
rename to contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index a0f0fc1bf4..fa8d12d3e3 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -1,7 +1,7 @@
-/* contrib/postgres_fdw/postgres_fdw--1.0.sql */
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
CREATE FUNCTION postgres_fdw_handler()
RETURNS fdw_handler
diff --git a/contrib/postgres_fdw/postgres_fdw--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.1.sql
new file mode 100644
index 0000000000..8a1453a495
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.1.sql
@@ -0,0 +1,33 @@
+/* contrib/postgres_fdw/postgres_fdw--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_handler()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION postgres_fdw_validator(text[], oid)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER postgres_fdw
+ HANDLER postgres_fdw_handler
+ VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS text[]
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..2267aee630 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,148 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+COMMIT;
+
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..405923f2aa 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,7 +477,47 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
- </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
+ The foreign server connections can get invalidated due to alter statements
+ on foreign server or user mapping. If there are no open connections, then
+ <literal>NULL</literal> is returned. This function issues a warning in case
+ for any connection, the associated foreign server has been dropped and the
+ server name can not be fetched from the system catalogues.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still being
+ used in the current transaction, it is not discarded, instead a warning is
+ issued and <literal>false</literal> is returned. <literal>false</literal> is
+ returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the unused open
+ connections that are previously made to the foreign servers and returns
+ <literal>true</literal>. If there is any open connection that is still being
+ used in the current transaction, then a warning is issued. <literal>false</literal>
+ is returned when no open connection is discarded or there are no open
+ connections at all.
+ </para>
+
+</sect2>
<sect2>
<title>Connection Management</title>
@@ -490,6 +530,21 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
v5-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchapplication/x-patch; name=v5-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchDownload
From 13eccc4c4c4b2ac4548608f37b3518160e8f3283 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 1 Jan 2021 14:33:41 +0530
Subject: [PATCH v5 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 32 +++++++++++-
doc/src/sgml/postgres-fdw.sgml | 46 ++++++++++++++++-
6 files changed, 154 insertions(+), 5 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index b9f6050f4b..323242c1d6 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -816,6 +816,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -826,6 +827,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -960,16 +963,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ba29bc6131..e08d9f38be 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9448,5 +9448,55 @@ DETAIL: Such connections are discarded at the end of remote transaction.
(0 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index b6c72e1d1e..160f17972b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 2267aee630..b37aa569d6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2875,5 +2875,35 @@ DROP SERVER temploopback2 CASCADE;
SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
-DROP FUNCTION fdw_unnest;
+DROP FUNCTION fdw_unnest;
\ No newline at end of file
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 405923f2aa..15def846f5 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -519,6 +519,44 @@ OPTIONS (ADD password_required 'false');
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -537,12 +575,18 @@ OPTIONS (ADD password_required 'false');
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v5-0003-postgres_fdw-server-level-option-keep_connection-.patchapplication/x-patch; name=v5-0003-postgres_fdw-server-level-option-keep_connection-.patchDownload
From c0dff6ca64126a29ef1e395dbe8cc2176f465b68 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 1 Jan 2021 15:02:28 +0530
Subject: [PATCH v5 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 42 ++++++++++++++++++
5 files changed, 149 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 323242c1d6..acb97d40cf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -963,15 +977,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1132,7 +1149,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e08d9f38be..100a342c8b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9498,5 +9498,47 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1a03e02263..0fe2eff878 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index b37aa569d6..44e076b4f4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2905,5 +2905,31 @@ SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
DROP FUNCTION fdw_unnest;
\ No newline at end of file
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 15def846f5..a62460704f 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -478,6 +478,35 @@ OPTIONS (ADD password_required 'false');
</sect3>
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
+
<sect2>
<title>Functions</title>
@@ -542,6 +571,14 @@ OPTIONS (ADD password_required 'false');
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -588,6 +625,11 @@ OPTIONS (ADD password_required 'false');
servers. Each connection is discarded at the end of transaction in which it
is used.
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is
+ discarded at the end of the transaction.
</para>
</sect2>
--
2.25.1
Hi, Bharath:
Happy new year.
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
Is it better to use 'invalidated' than 'false' in the string ?
For the first if block of postgres_fdw_disconnect():
+ * Check if the connection associated with the given foreign server
is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
since is_in_use is only set in the if (server) block, I think the above
warning can be moved into that block.
Cheers
On Fri, Jan 1, 2021 at 2:04 AM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
Show quoted text
On Thu, Dec 31, 2020 at 8:29 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:Right. I meant the "next use" as the select attempt on a foreign table
with that foreign server. If no select query is run, then at the end
of the current txn that connection gets closed. Yes internally such
connection gets closed in pgfdw_xact_callback.If the errdetail("Such connections get closed either in the next use
or at the end of the current transaction.") looks confusing, how about1) errdetail("Such connection gets discarded while closing the remote
transaction.")/errdetail("Such connections get discarded while closing
the remote transaction.")
2) errdetail("Such connection is discarded at the end of remote
transaction.")/errdetail("Such connections are discarded at the end of
remote transaction.")I prefer 2) Thoughts?
Because we already print a message in pgfdw_xact_callback -
elog(DEBUG3, "closing remote transaction on connection %p"I changed the message to "Such connection is discarded at the end of
remote transaction.".I'm attaching v5 patch set i.e. all the patches 0001 ( for new
functions), 0002 ( for GUC) and 0003 (for server level option). I have
also made the changes for increasing the version of
postgres_fdw--1.0.sql from 1.0 to 1.1.I have no open points from my end. Please consider the v5 patch set
for further review.With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Thanks for taking a look at the patches.
On Fri, Jan 1, 2021 at 9:35 PM Zhihong Yu <zyu@yugabyte.com> wrote:
Happy new year.
+ appendStringInfo(&buf, "(%s, %s)", server->servername, + entry->invalidated ? "false" : "true");Is it better to use 'invalidated' than 'false' in the string ?
This point was earlier discussed in [1]/messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com and [2]/messages/by-id/6da38393-6ae5-4d87-2690-11c932123403@oss.nttdata.com, but the agreement was
on having true/false [2]/messages/by-id/6da38393-6ae5-4d87-2690-11c932123403@oss.nttdata.com because of a simple reason specified in [1]/messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com,
that is when some users have foreign server names as invalid or valid,
then the output is difficult to interpret which one is what. With
having true/false, it's easier. IMO, let's keep the true/false as is,
since it's also suggested in [2]/messages/by-id/6da38393-6ae5-4d87-2690-11c932123403@oss.nttdata.com.
[1]: /messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com
[2]: /messages/by-id/6da38393-6ae5-4d87-2690-11c932123403@oss.nttdata.com
For the first if block of postgres_fdw_disconnect():
+ * Check if the connection associated with the given foreign server is + * in use i.e. entry->xact_depth > 0. Since we can not close it, so + * error out. + */ + if (is_in_use) + ereport(WARNING,since is_in_use is only set in the if (server) block, I think the above warning can be moved into that block.
Modified that a bit. Since we error out when no server object is
found, then no need of keeping the code in else part. We could save on
some indentation
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist",
servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false, &is_in_use);
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it
is still in use")));
Attaching v6 patch set. Please have a look.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v6-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/octet-stream; name=v6-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From b8f12b2e0d2292b924150776382520674bbf8185 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 2 Jan 2021 10:46:46 +0530
Subject: [PATCH v6 1/3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 313 ++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 415 +++++++++++++++++-
contrib/postgres_fdw/postgres_fdw--1.0.sql | 18 -
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 166 +++++++
doc/src/sgml/postgres-fdw.sgml | 57 ++-
7 files changed, 934 insertions(+), 39 deletions(-)
delete mode 100644 contrib/postgres_fdw/postgres_fdw--1.0.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..52d3dac0bd 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index d841cec39b..62dba1d481 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,6 +58,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -73,6 +76,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +103,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +284,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +801,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1004,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1119,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1171,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1181,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1366,273 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List the foreign server connections.
+ *
+ * This function takes no input parameter and returns an array with elements
+ * as pairs of foreign server name and true/false to show whether or not the
+ * connection is valid. The array elements look like (srv1, true),
+ * (srv2, false), (srv3, true) ... (srvn, true). True if the connection is
+ * valid. False if the connection is invalidated in pgfdw_inval_callback. NULL
+ * is returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ StringInfoData buf;
+ uint16 no_server_conn_cnt = 0;
+
+ if (!ConnectionHash)
+ PG_RETURN_NULL();
+
+ initStringInfo(&buf);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
+
+ /* stash away the prepared string into array value */
+ astate = accumArrayResult(astate, CStringGetTextDatum(buf.data),
+ false, TEXTOID, CurrentMemoryContext);
+ resetStringInfo(&buf);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections are discarded at the end of remote transaction.")
+ : errdetail("Such connection is discarded at the end of remote transaction.")));
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false, &is_in_use);
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..ba29bc6131 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,378 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+ (loopback, true)
+(2 rows)
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+(1 row)
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, false)
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection is discarded at the end of remote transaction.
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections are discarded at the end of remote transaction.
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql
deleted file mode 100644
index a0f0fc1bf4..0000000000
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ /dev/null
@@ -1,18 +0,0 @@
-/* contrib/postgres_fdw/postgres_fdw--1.0.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
-
-CREATE FUNCTION postgres_fdw_handler()
-RETURNS fdw_handler
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT;
-
-CREATE FUNCTION postgres_fdw_validator(text[], oid)
-RETURNS void
-AS 'MODULE_PATHNAME'
-LANGUAGE C STRICT;
-
-CREATE FOREIGN DATA WRAPPER postgres_fdw
- HANDLER postgres_fdw_handler
- VALIDATOR postgres_fdw_validator;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..2267aee630 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,148 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+COMMIT;
+
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..405923f2aa 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,7 +477,47 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
- </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
+ The foreign server connections can get invalidated due to alter statements
+ on foreign server or user mapping. If there are no open connections, then
+ <literal>NULL</literal> is returned. This function issues a warning in case
+ for any connection, the associated foreign server has been dropped and the
+ server name can not be fetched from the system catalogues.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still being
+ used in the current transaction, it is not discarded, instead a warning is
+ issued and <literal>false</literal> is returned. <literal>false</literal> is
+ returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the unused open
+ connections that are previously made to the foreign servers and returns
+ <literal>true</literal>. If there is any open connection that is still being
+ used in the current transaction, then a warning is issued. <literal>false</literal>
+ is returned when no open connection is discarded or there are no open
+ connections at all.
+ </para>
+
+</sect2>
<sect2>
<title>Connection Management</title>
@@ -490,6 +530,21 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
v6-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchapplication/octet-stream; name=v6-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchDownload
From d24e172833fb84d90af78dfc5c147b7e6cecec24 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 2 Jan 2021 10:48:09 +0530
Subject: [PATCH v6 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 32 +++++++++++-
doc/src/sgml/postgres-fdw.sgml | 46 ++++++++++++++++-
6 files changed, 154 insertions(+), 5 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 62dba1d481..5c400f1bfd 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -816,6 +816,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -826,6 +827,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -960,16 +963,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ba29bc6131..e08d9f38be 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9448,5 +9448,55 @@ DETAIL: Such connections are discarded at the end of remote transaction.
(0 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index b6c72e1d1e..160f17972b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 2267aee630..b37aa569d6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2875,5 +2875,35 @@ DROP SERVER temploopback2 CASCADE;
SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
-DROP FUNCTION fdw_unnest;
+DROP FUNCTION fdw_unnest;
\ No newline at end of file
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 405923f2aa..15def846f5 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -519,6 +519,44 @@ OPTIONS (ADD password_required 'false');
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -537,12 +575,18 @@ OPTIONS (ADD password_required 'false');
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v6-0003-postgres_fdw-server-level-option-keep_connection-.patchapplication/octet-stream; name=v6-0003-postgres_fdw-server-level-option-keep_connection-.patchDownload
From 764fda70d473162415ac2cf2751459a415ab0e84 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 2 Jan 2021 10:49:19 +0530
Subject: [PATCH v6 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 42 ++++++++++++++++++
5 files changed, 149 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 5c400f1bfd..dd793102ae 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -963,15 +977,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1132,7 +1149,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e08d9f38be..100a342c8b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9498,5 +9498,47 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1a03e02263..0fe2eff878 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index b37aa569d6..44e076b4f4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2905,5 +2905,31 @@ SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
DROP FUNCTION fdw_unnest;
\ No newline at end of file
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 15def846f5..a62460704f 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -478,6 +478,35 @@ OPTIONS (ADD password_required 'false');
</sect3>
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
+
<sect2>
<title>Functions</title>
@@ -542,6 +571,14 @@ OPTIONS (ADD password_required 'false');
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -588,6 +625,11 @@ OPTIONS (ADD password_required 'false');
servers. Each connection is discarded at the end of transaction in which it
is used.
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is
+ discarded at the end of the transaction.
</para>
</sect2>
--
2.25.1
On Sat, Jan 2, 2021 at 10:53 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Thanks for taking a look at the patches.
On Fri, Jan 1, 2021 at 9:35 PM Zhihong Yu <zyu@yugabyte.com> wrote:
Happy new year.
+ appendStringInfo(&buf, "(%s, %s)", server->servername, + entry->invalidated ? "false" : "true");Is it better to use 'invalidated' than 'false' in the string ?
This point was earlier discussed in [1] and [2], but the agreement was
on having true/false [2] because of a simple reason specified in [1],
that is when some users have foreign server names as invalid or valid,
then the output is difficult to interpret which one is what. With
having true/false, it's easier. IMO, let's keep the true/false as is,
since it's also suggested in [2].[1] - /messages/by-id/CALj2ACUv=ArQXs0U9PM3YXKCeSzJ1KxRokDY0g_0aGy--kDScA@mail.gmail.com
[2] - /messages/by-id/6da38393-6ae5-4d87-2690-11c932123403@oss.nttdata.comFor the first if block of postgres_fdw_disconnect():
+ * Check if the connection associated with the given foreign server is + * in use i.e. entry->xact_depth > 0. Since we can not close it, so + * error out. + */ + if (is_in_use) + ereport(WARNING,since is_in_use is only set in the if (server) block, I think the above warning can be moved into that block.
Modified that a bit. Since we error out when no server object is
found, then no need of keeping the code in else part. We could save on
some indentation+ if (!server) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST), + errmsg("foreign server \"%s\" does not exist", servername))); + + hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID, + ObjectIdGetDatum(server->serverid)); + result = disconnect_cached_connections(hashvalue, false, &is_in_use); + + /* + * Check if the connection associated with the given foreign server is + * in use i.e. entry->xact_depth > 0. Since we can not close it, so + * error out. + */ + if (is_in_use) + ereport(WARNING, + (errmsg("cannot close the connection because it is still in use")));Attaching v6 patch set. Please have a look.
I'm sorry for the mess. I missed adding the new files into the v6-0001
patch. Please ignore the v6 patch set and consder the v7 patch set for
further review. Note that 0002 and 0003 patches have no difference
from v5 patch set.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v7-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/octet-stream; name=v7-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From 6cbee3a521cd9bab78d16f5730db0fea5d32a12c Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 2 Jan 2021 11:10:54 +0530
Subject: [PATCH v7 1/3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 313 ++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 415 +++++++++++++++++-
...dw--1.0.sql => postgres_fdw--1.0--1.1.sql} | 6 +-
contrib/postgres_fdw/postgres_fdw--1.1.sql | 33 ++
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 166 +++++++
doc/src/sgml/postgres-fdw.sgml | 57 ++-
8 files changed, 970 insertions(+), 24 deletions(-)
rename contrib/postgres_fdw/{postgres_fdw--1.0.sql => postgres_fdw--1.0--1.1.sql} (60%)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..52d3dac0bd 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index d841cec39b..62dba1d481 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,6 +58,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -73,6 +76,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +103,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +284,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +801,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1004,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1119,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1171,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1181,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1366,273 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List the foreign server connections.
+ *
+ * This function takes no input parameter and returns an array with elements
+ * as pairs of foreign server name and true/false to show whether or not the
+ * connection is valid. The array elements look like (srv1, true),
+ * (srv2, false), (srv3, true) ... (srvn, true). True if the connection is
+ * valid. False if the connection is invalidated in pgfdw_inval_callback. NULL
+ * is returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ StringInfoData buf;
+ uint16 no_server_conn_cnt = 0;
+
+ if (!ConnectionHash)
+ PG_RETURN_NULL();
+
+ initStringInfo(&buf);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
+
+ /* stash away the prepared string into array value */
+ astate = accumArrayResult(astate, CStringGetTextDatum(buf.data),
+ false, TEXTOID, CurrentMemoryContext);
+ resetStringInfo(&buf);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections are discarded at the end of remote transaction.")
+ : errdetail("Such connection is discarded at the end of remote transaction.")));
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false, &is_in_use);
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..ba29bc6131 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,378 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+ (loopback, true)
+(2 rows)
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-------------------
+ (loopback2, true)
+(1 row)
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, false)
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection is discarded at the end of remote transaction.
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections are discarded at the end of remote transaction.
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
similarity index 60%
rename from contrib/postgres_fdw/postgres_fdw--1.0.sql
rename to contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index a0f0fc1bf4..fa8d12d3e3 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -1,7 +1,7 @@
-/* contrib/postgres_fdw/postgres_fdw--1.0.sql */
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
CREATE FUNCTION postgres_fdw_handler()
RETURNS fdw_handler
diff --git a/contrib/postgres_fdw/postgres_fdw--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.1.sql
new file mode 100644
index 0000000000..8a1453a495
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.1.sql
@@ -0,0 +1,33 @@
+/* contrib/postgres_fdw/postgres_fdw--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_handler()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION postgres_fdw_validator(text[], oid)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER postgres_fdw
+ HANDLER postgres_fdw_handler
+ VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS text[]
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..2267aee630 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,148 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Ensure to have loopback and loopback2 connections cached.
+SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft6 LIMIT 1;
+
+-- List all the existing cached connections. Should return loopback, loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- loopback connection is disconnected. loopback2 connection still exists.
+SELECT postgres_fdw_disconnect('loopback'); /* TRUE */
+
+-- List all the existing cached connections. Should return loopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the loopback connection is not present in it.
+SELECT postgres_fdw_disconnect('loopback'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and loopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+COMMIT;
+
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..405923f2aa 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,7 +477,47 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
- </sect2>
+
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
+ The foreign server connections can get invalidated due to alter statements
+ on foreign server or user mapping. If there are no open connections, then
+ <literal>NULL</literal> is returned. This function issues a warning in case
+ for any connection, the associated foreign server has been dropped and the
+ server name can not be fetched from the system catalogues.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still being
+ used in the current transaction, it is not discarded, instead a warning is
+ issued and <literal>false</literal> is returned. <literal>false</literal> is
+ returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the unused open
+ connections that are previously made to the foreign servers and returns
+ <literal>true</literal>. If there is any open connection that is still being
+ used in the current transaction, then a warning is issued. <literal>false</literal>
+ is returned when no open connection is discarded or there are no open
+ connections at all.
+ </para>
+
+</sect2>
<sect2>
<title>Connection Management</title>
@@ -490,6 +530,21 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+
+ </para>
</sect2>
<sect2>
--
2.25.1
v7-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchapplication/octet-stream; name=v7-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchDownload
From 13eccc4c4c4b2ac4548608f37b3518160e8f3283 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 1 Jan 2021 14:33:41 +0530
Subject: [PATCH v7 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 32 +++++++++++-
doc/src/sgml/postgres-fdw.sgml | 46 ++++++++++++++++-
6 files changed, 154 insertions(+), 5 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index b9f6050f4b..323242c1d6 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -816,6 +816,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -826,6 +827,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -960,16 +963,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ba29bc6131..e08d9f38be 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9448,5 +9448,55 @@ DETAIL: Such connections are discarded at the end of remote transaction.
(0 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index b6c72e1d1e..160f17972b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..7f1bdb96d6 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 2267aee630..b37aa569d6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2875,5 +2875,35 @@ DROP SERVER temploopback2 CASCADE;
SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
-DROP FUNCTION fdw_unnest;
+DROP FUNCTION fdw_unnest;
\ No newline at end of file
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 405923f2aa..15def846f5 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -519,6 +519,44 @@ OPTIONS (ADD password_required 'false');
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -537,12 +575,18 @@ OPTIONS (ADD password_required 'false');
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v7-0003-postgres_fdw-server-level-option-keep_connection-.patchapplication/octet-stream; name=v7-0003-postgres_fdw-server-level-option-keep_connection-.patchDownload
From c0dff6ca64126a29ef1e395dbe8cc2176f465b68 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 1 Jan 2021 15:02:28 +0530
Subject: [PATCH v7 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 42 ++++++++++++++++++
5 files changed, 149 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 323242c1d6..acb97d40cf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -963,15 +977,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1132,7 +1149,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e08d9f38be..100a342c8b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9498,5 +9498,47 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1a03e02263..0fe2eff878 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index b37aa569d6..44e076b4f4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2905,5 +2905,31 @@ SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
DROP FUNCTION fdw_unnest;
\ No newline at end of file
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 15def846f5..a62460704f 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -478,6 +478,35 @@ OPTIONS (ADD password_required 'false');
</sect3>
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
+
<sect2>
<title>Functions</title>
@@ -542,6 +571,14 @@ OPTIONS (ADD password_required 'false');
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -588,6 +625,11 @@ OPTIONS (ADD password_required 'false');
servers. Each connection is discarded at the end of transaction in which it
is used.
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is
+ discarded at the end of the transaction.
</para>
</sect2>
--
2.25.1
On Sat, Jan 2, 2021 at 11:19 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I'm sorry for the mess. I missed adding the new files into the v6-0001
patch. Please ignore the v6 patch set and consder the v7 patch set for
further review. Note that 0002 and 0003 patches have no difference
from v5 patch set.
It seems like cf bot was failing on v7 patches. On Linux, it fails
while building documentation in 0001 patch, I corrected that. On
FreeBSD, it fails in one of the test cases I added, since it was
unstable, I corrected it now.
Attaching v8 patch set. Hopefully, cf bot will be happy with v8.
Please consider the v8 patch set for further review.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v8-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/x-patch; name=v8-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From f784809cd81ed18ecbd1b3c7e87818a00b671fa0 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Tue, 5 Jan 2021 12:36:16 +0530
Subject: [PATCH v8 1/3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 313 ++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 424 +++++++++++++++++-
...dw--1.0.sql => postgres_fdw--1.0--1.1.sql} | 6 +-
contrib/postgres_fdw/postgres_fdw--1.1.sql | 33 ++
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 171 +++++++
doc/src/sgml/postgres-fdw.sgml | 54 +++
8 files changed, 982 insertions(+), 23 deletions(-)
rename contrib/postgres_fdw/{postgres_fdw--1.0.sql => postgres_fdw--1.0--1.1.sql} (60%)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..52d3dac0bd 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 266f66cc62..64e1d71514 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,6 +22,7 @@
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,6 +58,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -73,6 +76,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +103,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +284,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +801,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1004,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1119,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1171,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1181,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1366,273 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List the foreign server connections.
+ *
+ * This function takes no input parameter and returns an array with elements
+ * as pairs of foreign server name and true/false to show whether or not the
+ * connection is valid. The array elements look like (srv1, true),
+ * (srv2, false), (srv3, true) ... (srvn, true). True if the connection is
+ * valid. False if the connection is invalidated in pgfdw_inval_callback. NULL
+ * is returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ArrayBuildState *astate = NULL;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ StringInfoData buf;
+ uint16 no_server_conn_cnt = 0;
+
+ if (!ConnectionHash)
+ PG_RETURN_NULL();
+
+ initStringInfo(&buf);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ appendStringInfo(&buf, "(%s, %s)", server->servername,
+ entry->invalidated ? "false" : "true");
+
+ /* stash away the prepared string into array value */
+ astate = accumArrayResult(astate, CStringGetTextDatum(buf.data),
+ false, TEXTOID, CurrentMemoryContext);
+ resetStringInfo(&buf);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections are discarded at the end of remote transaction.")
+ : errdetail("Such connection is discarded at the end of remote transaction.")));
+ }
+
+ if (astate)
+ PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
+ CurrentMemoryContext));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ char *servername = NULL;
+ ForeignServer *server = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false, &is_in_use);
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ bool result = false;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..ff7b6afa63 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,387 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should return temploopback1,
+-- temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should return temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, true)
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------------
+ (temploopback1, false)
+ (temploopback2, false)
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+-----------------------
+ (temploopback1, true)
+ (temploopback2, true)
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection is discarded at the end of remote transaction.
+ fdw_unnest
+-----------------------
+ (temploopback2, true)
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections are discarded at the end of remote transaction.
+ fdw_unnest
+------------
+(0 rows)
+
+COMMIT;
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
similarity index 60%
rename from contrib/postgres_fdw/postgres_fdw--1.0.sql
rename to contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index a0f0fc1bf4..fa8d12d3e3 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -1,7 +1,7 @@
-/* contrib/postgres_fdw/postgres_fdw--1.0.sql */
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
CREATE FUNCTION postgres_fdw_handler()
RETURNS fdw_handler
diff --git a/contrib/postgres_fdw/postgres_fdw--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.1.sql
new file mode 100644
index 0000000000..8a1453a495
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.1.sql
@@ -0,0 +1,33 @@
+/* contrib/postgres_fdw/postgres_fdw--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_handler()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION postgres_fdw_validator(text[], oid)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER postgres_fdw
+ HANDLER postgres_fdw_handler
+ VALIDATOR postgres_fdw_validator;
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS text[]
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..c2c5c10739 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,153 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- postgres_fdw_get_connections returns an array with elements in a
+-- machine-dependent ordering, so we must resort to unnesting and sorting for a
+-- stable result.
+CREATE FUNCTION fdw_unnest(anyarray) RETURNS SETOF anyelement
+LANGUAGE SQL STRICT IMMUTABLE AS $$
+SELECT $1[i] FROM generate_series(array_lower($1,1), array_upper($1,1)) AS i
+$$;
+
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+
+-- List all the existing cached connections. Should return temploopback1,
+-- temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+
+-- List all the existing cached connections. Should return temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should return nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
+COMMIT;
+
+-- Clean up.
+DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..914d5a2ee6 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -479,6 +479,46 @@ OPTIONS (ADD password_required 'false');
</sect3>
</sect2>
+<sect2>
+ <title>Functions</title>
+
+ <para>
+ <function>postgres_fdw_get_connections</function> ( ) which takes no input.
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
+ The foreign server connections can get invalidated due to alter statements
+ on foreign server or user mapping. If there are no open connections, then
+ <literal>NULL</literal> is returned. This function issues a warning in case
+ for any connection, the associated foreign server has been dropped and the
+ server name can not be fetched from the system catalogues.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( <parameter>servername</parameter> <type>text</type> )
+ which takes foreign server name as input. When called in the local session,
+ it discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still being
+ used in the current transaction, it is not discarded, instead a warning is
+ issued and <literal>false</literal> is returned. <literal>false</literal> is
+ returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted.
+ </para>
+
+ <para>
+ <function>postgres_fdw_disconnect</function> ( ) which takes no input.
+ When called in the local session, it discards all the unused open
+ connections that are previously made to the foreign servers and returns
+ <literal>true</literal>. If there is any open connection that is still being
+ used in the current transaction, then a warning is issued. <literal>false</literal>
+ is returned when no open connection is discarded or there are no open
+ connections at all.
+ </para>
+</sect2>
+
<sect2>
<title>Connection Management</title>
@@ -490,6 +530,20 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v8-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchapplication/x-patch; name=v8-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchDownload
From c2a59499af7285763b65ff4e91e57903d51f83bf Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Tue, 5 Jan 2021 12:40:32 +0530
Subject: [PATCH v8 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 30 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 44 +++++++++++++++-
6 files changed, 151 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 64e1d71514..e49109e85a 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -816,6 +816,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -826,6 +827,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -960,16 +963,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ff7b6afa63..bc4cf71ff3 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9457,5 +9457,55 @@ DETAIL: Such connections are discarded at the end of remote transaction.
(0 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index c2c5c10739..98468db610 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2880,5 +2880,35 @@ DROP SERVER temploopback2 CASCADE;
SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1; /* WARNING */
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 914d5a2ee6..b704a7e4ac 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -519,6 +519,42 @@ OPTIONS (ADD password_required 'false');
</para>
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -537,12 +573,18 @@ OPTIONS (ADD password_required 'false');
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v8-0003-postgres_fdw-server-level-option-keep_connection-.patchapplication/x-patch; name=v8-0003-postgres_fdw-server-level-option-keep_connection-.patchDownload
From b00651e0fd33594389dfc642a14324544720d77c Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Tue, 5 Jan 2021 12:49:38 +0530
Subject: [PATCH v8 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 150 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index e49109e85a..e8ed9f5b85 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -963,15 +977,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1132,7 +1149,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index bc4cf71ff3..3062e59331 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9507,5 +9507,47 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+ fdw_unnest
+------------------
+ (loopback, true)
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 98468db610..1c8c8d0f8b 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2910,5 +2910,31 @@ SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM fdw_unnest(postgres_fdw_get_connections()) ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
-- Clean up.
DROP FUNCTION fdw_unnest;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index b704a7e4ac..5859b9a517 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -542,6 +571,14 @@ OPTIONS (ADD password_required 'false');
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -585,6 +622,12 @@ OPTIONS (ADD password_required 'false');
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/05 16:56, Bharath Rupireddy wrote:
On Sat, Jan 2, 2021 at 11:19 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I'm sorry for the mess. I missed adding the new files into the v6-0001
patch. Please ignore the v6 patch set and consder the v7 patch set for
further review. Note that 0002 and 0003 patches have no difference
from v5 patch set.It seems like cf bot was failing on v7 patches. On Linux, it fails
while building documentation in 0001 patch, I corrected that. On
FreeBSD, it fails in one of the test cases I added, since it was
unstable, I corrected it now.Attaching v8 patch set. Hopefully, cf bot will be happy with v8.
Please consider the v8 patch set for further review.
Thanks for the patch!
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sql
Shouldn't we leave 1.0.sql as it is and create 1.0--1.1.sql so that
we can run the followings?
CREATE EXTENSION postgres_fdw VERSION "1.0";
ALTER EXTENSION postgres_fdw UPDATE TO "1.1";
+<sect2>
+ <title>Functions</title>
The document format for functions should be consistent with
that in other contrib module like pgstattuple?
+ When called in the local session, it returns an array with each element as a
+ pair of the foreign server names of all the open connections that are
+ previously made to the foreign servers and <literal>true</literal> or
+ <literal>false</literal> to show whether or not the connection is valid.
We thought that the information about whether the connection is valid or
not was useful to, for example, identify and close explicitly the long-living
invalid connections because they were useless. But thanks to the recent
bug fix for connection leak issue, that information would be no longer
so helpful for us? False is returned only when the connection is used in
this local transaction but it's marked as invalidated. In this case that
connection cannot be explicitly closed because it's used in this transaction.
It will be closed at the end of transaction. Thought?
I guess that you made postgres_fdw_get_connections() return the array
because the similar function dblink_get_connections() does that. But
isn't it more convenient to make that return the set of records?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Thu, Jan 7, 2021 at 9:49 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/05 16:56, Bharath Rupireddy wrote:
Attaching v8 patch set. Hopefully, cf bot will be happy with v8.
Please consider the v8 patch set for further review.
-DATA = postgres_fdw--1.0.sql +DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sqlShouldn't we leave 1.0.sql as it is and create 1.0--1.1.sql so that
we can run the followings?CREATE EXTENSION postgres_fdw VERSION "1.0";
ALTER EXTENSION postgres_fdw UPDATE TO "1.1";
Yes we can. In that case, to use the new functions users have to
update postgres_fdw to 1.1, in that case, do we need to mention in the
documentation that to make use of the new functions, update
postgres_fdw to version 1.1?
With the above change, the contents of postgres_fdw--1.0.sql remain as
is and in postgres_fdw--1.0--1.1.sql we will have only the new
function statements:
/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this
file. \quit
CREATE FUNCTION postgres_fdw_get_connections ()
RETURNS text[]
AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
LANGUAGE C STRICT PARALLEL RESTRICTED;
CREATE FUNCTION postgres_fdw_disconnect ()
RETURNS bool
AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
LANGUAGE C STRICT PARALLEL RESTRICTED;
CREATE FUNCTION postgres_fdw_disconnect (text)
RETURNS bool
AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+<sect2>
+ <title>Functions</title>The document format for functions should be consistent with
that in other contrib module like pgstattuple?
pgstattuple has so many columns to show up in output because of that
they have a table listing all the output columns and their types. The
new functions introduced here have only one or none input and an
output. I think, we don't need a table listing the input and output
names and types.
IMO, we can have something similar to what pg_visibility_map has for
functions, and also an example for each function showing how it can be
used. Thoughts?
+ When called in the local session, it returns an array with each element as a + pair of the foreign server names of all the open connections that are + previously made to the foreign servers and <literal>true</literal> or + <literal>false</literal> to show whether or not the connection is valid.We thought that the information about whether the connection is valid or
not was useful to, for example, identify and close explicitly the long-living
invalid connections because they were useless. But thanks to the recent
bug fix for connection leak issue, that information would be no longer
so helpful for us? False is returned only when the connection is used in
this local transaction but it's marked as invalidated. In this case that
connection cannot be explicitly closed because it's used in this transaction.
It will be closed at the end of transaction. Thought?
Yes, connection's validity can be false only when the connection gets
invalidated and postgres_fdw_get_connections is called within an
explicit txn i.e. begin; commit;. In implicit txn, since the
invalidated connections get closed either during invalidation callback
or at the end of txn, postgres_fdw_get_connections will always show
valid connections. Having said that, I still feel we need the
true/false for valid/invalid in the output of the
postgres_fdw_get_connections, otherwise we might miss giving
connection validity information to the user in a very narrow use case
of explicit txn. If required, we can issue a warning whenever we see
an invalid connection saying "invalid connections connections are
discarded at the end of remote transaction". Thoughts?
I guess that you made postgres_fdw_get_connections() return the array
because the similar function dblink_get_connections() does that. But
isn't it more convenient to make that return the set of records?
Yes, for postgres_fdw_get_connections we can return a set of records
of (server_name, valid). To do so, I can refer to dblink_get_pkey.
Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/07 17:21, Bharath Rupireddy wrote:
On Thu, Jan 7, 2021 at 9:49 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/05 16:56, Bharath Rupireddy wrote:
Attaching v8 patch set. Hopefully, cf bot will be happy with v8.
Please consider the v8 patch set for further review.
-DATA = postgres_fdw--1.0.sql +DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sqlShouldn't we leave 1.0.sql as it is and create 1.0--1.1.sql so that
we can run the followings?CREATE EXTENSION postgres_fdw VERSION "1.0";
ALTER EXTENSION postgres_fdw UPDATE TO "1.1";Yes we can. In that case, to use the new functions users have to
update postgres_fdw to 1.1, in that case, do we need to mention in the
documentation that to make use of the new functions, update
postgres_fdw to version 1.1?
But since postgres_fdw.control indicates that the default version is 1.1,
"CREATE EXTENSION postgres_fdw" installs v1.1. So basically the users
don't need to update postgres_fdw from v1.0 to v1.1. Only the users of
v1.0 need to update that to v1.1 to use new functions. No?
With the above change, the contents of postgres_fdw--1.0.sql remain as
is and in postgres_fdw--1.0--1.1.sql we will have only the new
function statements:
Yes.
/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this
file. \quitCREATE FUNCTION postgres_fdw_get_connections ()
RETURNS text[]
AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
LANGUAGE C STRICT PARALLEL RESTRICTED;CREATE FUNCTION postgres_fdw_disconnect ()
RETURNS bool
AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
LANGUAGE C STRICT PARALLEL RESTRICTED;CREATE FUNCTION postgres_fdw_disconnect (text)
RETURNS bool
AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
LANGUAGE C STRICT PARALLEL RESTRICTED;+<sect2>
+ <title>Functions</title>The document format for functions should be consistent with
that in other contrib module like pgstattuple?pgstattuple has so many columns to show up in output because of that
they have a table listing all the output columns and their types. The
new functions introduced here have only one or none input and an
output. I think, we don't need a table listing the input and output
names and types.IMO, we can have something similar to what pg_visibility_map has for
functions, and also an example for each function showing how it can be
used. Thoughts?
Sounds good.
+ When called in the local session, it returns an array with each element as a + pair of the foreign server names of all the open connections that are + previously made to the foreign servers and <literal>true</literal> or + <literal>false</literal> to show whether or not the connection is valid.We thought that the information about whether the connection is valid or
not was useful to, for example, identify and close explicitly the long-living
invalid connections because they were useless. But thanks to the recent
bug fix for connection leak issue, that information would be no longer
so helpful for us? False is returned only when the connection is used in
this local transaction but it's marked as invalidated. In this case that
connection cannot be explicitly closed because it's used in this transaction.
It will be closed at the end of transaction. Thought?Yes, connection's validity can be false only when the connection gets
invalidated and postgres_fdw_get_connections is called within an
explicit txn i.e. begin; commit;. In implicit txn, since the
invalidated connections get closed either during invalidation callback
or at the end of txn, postgres_fdw_get_connections will always show
valid connections. Having said that, I still feel we need the
true/false for valid/invalid in the output of the
postgres_fdw_get_connections, otherwise we might miss giving
connection validity information to the user in a very narrow use case
of explicit txn.
Understood. I withdraw my suggestion and am fine to display
valid/invalid information.
If required, we can issue a warning whenever we see
an invalid connection saying "invalid connections connections are
discarded at the end of remote transaction". Thoughts?
IMO it's overkill to emit such warinng message because that
situation is normal one. OTOH, it seems worth documenting that.
I guess that you made postgres_fdw_get_connections() return the array
because the similar function dblink_get_connections() does that. But
isn't it more convenient to make that return the set of records?Yes, for postgres_fdw_get_connections we can return a set of records
of (server_name, valid). To do so, I can refer to dblink_get_pkey.
Thoughts?
Yes.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 8, 2021 at 7:29 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/07 17:21, Bharath Rupireddy wrote:
On Thu, Jan 7, 2021 at 9:49 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/05 16:56, Bharath Rupireddy wrote:
Attaching v8 patch set. Hopefully, cf bot will be happy with v8.
Please consider the v8 patch set for further review.
-DATA = postgres_fdw--1.0.sql +DATA = postgres_fdw--1.1.sql postgres_fdw--1.0--1.1.sqlShouldn't we leave 1.0.sql as it is and create 1.0--1.1.sql so that
we can run the followings?CREATE EXTENSION postgres_fdw VERSION "1.0";
ALTER EXTENSION postgres_fdw UPDATE TO "1.1";Yes we can. In that case, to use the new functions users have to
update postgres_fdw to 1.1, in that case, do we need to mention in the
documentation that to make use of the new functions, update
postgres_fdw to version 1.1?But since postgres_fdw.control indicates that the default version is 1.1,
"CREATE EXTENSION postgres_fdw" installs v1.1. So basically the users
don't need to update postgres_fdw from v1.0 to v1.1. Only the users of
v1.0 need to update that to v1.1 to use new functions. No?
It works this way:
scenario 1:
1) create extension postgres_fdw; --> this is run before our feature
i.e default_version 1.0
2) after the feature i..e default_version 1.1, users can run alter
extension postgres_fdw update to "1.1"; which gets the new functions
from postgres_fdw--1.0--1.1.sql.
scenario 2:
1) create extension postgres_fdw; --> this is run after our feature
i.e default_version 1.1, then the new functions will be installed with
create extension itself, no need to run alter update to get the
functions,
I will make the changes and post a new patch set soon.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jan 8, 2021 at 9:55 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I will make the changes and post a new patch set soon.
Attaching v9 patch set that has addressed the review comments on the
disconnect function returning setof records, documentation changes,
and postgres_fdw--1.0-1.1.sql changes.
Please consider the v9 patch set for further review.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v9-0001-postgres_fdw-function-to-discard-cached-connectio.patchapplication/octet-stream; name=v9-0001-postgres_fdw-function-to-discard-cached-connectio.patchDownload
From 06ddf1e3546379dfa96d2f31a69d30592f870756 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 8 Jan 2021 19:19:58 +0530
Subject: [PATCH v9 1/3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 428 +++++++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 415 ++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 20 +
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 160 +++++++
doc/src/sgml/postgres-fdw.sgml | 90 ++++
7 files changed, 1097 insertions(+), 20 deletions(-)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..c1b0cad453 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 266f66cc62..8f5df678b4 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -16,12 +16,14 @@
#include "access/xact.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -57,10 +59,21 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ /* Server oid to get the associated foreign server name. */
+ Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
+/*
+ * Output arguments for postgres_fdw_get_connections.
+ */
+typedef struct GetConnOutputArgs
+{
+ char *server_name; /* server name of the active connection */
+ bool valid; /* is connection valid? */
+} GetConnOutputArgs;
+
/*
* Connection cache (initialized on first use)
*/
@@ -73,6 +86,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -94,6 +113,9 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static List *get_connections();
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -273,6 +295,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -789,6 +812,13 @@ pgfdw_xact_callback(XactEvent event, void *arg)
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote transactions, and
* close them.
@@ -985,6 +1015,13 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
if (!xact_got_connection)
return;
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/*
* Scan all connection cache entries to find open remote subtransactions
* of the current level, and close them.
@@ -1093,6 +1130,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1138,8 +1182,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1192,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
@@ -1341,3 +1377,377 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * Workhorse to get the list of active connections.
+ *
+ * Walks through connection cache, prepares a list of GetConnOutputArgs i.e
+ * server name of active connection and true/false indicating whether it is
+ * valid or not.
+ */
+static List *
+get_connections()
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ uint16 no_server_conn_cnt = 0;
+ List *output_args_list = NULL;
+
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+ GetConnOutputArgs *output_args;
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, true);
+
+ /*
+ * The foreign server may have been dropped in the current explicit
+ * transaction. It's not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so the drop query in another session
+ * blocks until the current explicit transaction finishes.
+ *
+ * Even though the server is dropped in the current explicit
+ * transaction, the cache can have the associated active connection
+ * entry. Say we call such connections as dangling. Since we can not
+ * fetch the server name from system catalogues for dangling
+ * connections, instead we issue a warning.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But storing the server name in each cache entry requires 64bytes of
+ * memory, which is huge, considering the fact that there can exists
+ * many cached connections and the use case i.e. dropping the foreign
+ * server within the explicit current transaction seems rare. So, we
+ * chose to issue a warning instead.
+ *
+ * Such dangling connections get closed either in the next use or at
+ * the end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ if (entry->conn)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this entry would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->xact_depth > 0 && entry->invalidated);
+
+ no_server_conn_cnt++;
+ }
+
+ continue;
+ }
+
+ output_args = (GetConnOutputArgs *) palloc0(sizeof(GetConnOutputArgs));
+ output_args->server_name = server->servername;
+ output_args->valid = !entry->invalidated;
+ output_args_list = lappend(output_args_list, output_args);
+ }
+
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections are discarded at the end of remote transaction.")
+ : errdetail("Such connection is discarded at the end of remote transaction.")));
+ }
+
+ return output_args_list;
+}
+
+/*
+ * List the active foreign server connections.
+ *
+ * This function takes no input parameter and returns setof record made of the
+ * following values:
+ * - server_name - server name of the active connection.
+ * - valid - true/false representing whether the connection is valid or not.
+ * Note that connections can get invalidated in pgfdw_inval_callback.
+ *
+ * No records are returned when there are no cached connections at all.
+ *
+ * This function issues a warning in case for any connection the associated
+ * foreign server has been dropped which means that the server name can not be
+ * read from the system catalogues.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *fctx;
+ List *output_args_list;
+
+ /*
+ * This funtion gets called multiple times, returning one output record
+ * each time, until there is no record to output.
+ *
+ * During the first call, we initialize the function context, get the list
+ * of active connections using get_connections and store this in the
+ * function's memory context so that it can live multiple calls.
+ */
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tuple_desc;
+ MemoryContext old_context;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ /* If cache doesn't exist, we return no records. */
+ if (!ConnectionHash)
+ SRF_RETURN_DONE(fctx);
+
+ /*
+ * Use function's memory context so that the required information can
+ * be carried across multiple calls. Note that we don't need to pfree
+ * the memory allocated using pfree in the function's memory context,
+ * because SRF_RETURN_DONE deletes the memory context eventually.
+ */
+ old_context = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+ output_args_list = get_connections();
+
+ /* No active connections exist, so we return no records. */
+ if (!output_args_list)
+ {
+ /* Before returning, switch to the original memory context. */
+ MemoryContextSwitchTo(old_context);
+ SRF_RETURN_DONE(fctx);
+ }
+
+ /*
+ * We have these many records i.e. number of active connections to
+ * output.
+ */
+ fctx->max_calls = list_length(output_args_list);
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tuple_desc = BlessTupleDesc(tuple_desc);
+ fctx->attinmeta = TupleDescGetAttInMetadata(tuple_desc);
+ fctx->user_fctx = output_args_list;
+
+ /*
+ * We are done taking the required information into the function's memory
+ * context, so switch to the original memory context.
+ */
+ MemoryContextSwitchTo(old_context);
+ }
+
+ fctx = SRF_PERCALL_SETUP();
+ output_args_list = fctx->user_fctx;
+
+ /* If we have records to output, then prepare the result and return. */
+ if (fctx->call_cntr < fctx->max_calls)
+ {
+ GetConnOutputArgs *output_args;
+ Datum values[2];
+ bool nulls[2];
+ HeapTuple tuple;
+ Datum result;
+
+ /* We don't output the connections with null foreign server names. */
+ MemSet(nulls, 0, sizeof(nulls));
+
+ output_args = (GetConnOutputArgs *) linitial(output_args_list);
+ values[0] = CStringGetTextDatum(output_args->server_name);
+ values[1] = BoolGetDatum(output_args->valid);
+
+ tuple = heap_form_tuple(fctx->attinmeta->tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ /* Delete and move to the next connection in the list. */
+ output_args_list = list_delete_first(output_args_list);
+ fctx->user_fctx = output_args_list;
+
+ /*
+ * Actually we are good without freeing up the output_args memory,
+ * because SRF_RETURN_DONE will eventually deletes the function's
+ * memory context in which the output_args has been allocated. But
+ * there are cases where we may have huge number of active connections,
+ * because of which the memory consumption may go up, so this is a
+ * chance to free up the memory, let's do it.
+ */
+ pfree(output_args);
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+
+ /* We are done. */
+ SRF_RETURN_DONE(fctx);
+}
+
+/*
+ * Disconnect the cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false, &is_in_use);
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all the active connections i.e. if
+ * there is no single active connection, which we can know while scanning
+ * the cached entries in the above loop. Destroying the cache is better
+ * than to keep it in the memory with all inactive entries in it to save
+ * some memory. Cache can get initialized on the subsequent queries to
+ * foreign server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..c737de10ff 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,378 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should output temploopback1,
+-- temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | f
+ temploopback2 | t
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | f
+ temploopback2 | f
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; /* WARNING */
+WARNING: found an active connection for which the foreign server would have been dropped
+DETAIL: Such connection is discarded at the end of remote transaction.
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; /* WARNING */
+WARNING: found some active connections for which the foreign servers would have been dropped
+DETAIL: Such connections are discarded at the end of remote transaction.
+ server_name | valid
+-------------+-------
+(0 rows)
+
+COMMIT;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
new file mode 100644
index 0000000000..b616316999
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -0,0 +1,20 @@
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
+ OUT valid boolean)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..576fdde0c9 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,142 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+
+-- List all the existing cached connections. Should output temploopback1,
+-- temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+
+-- List all the existing cached connections. Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; /* WARNING */
+DROP SERVER temploopback2 CASCADE;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; /* WARNING */
+COMMIT;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..1da9599f59 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -479,6 +479,82 @@ OPTIONS (ADD password_required 'false');
</sect3>
</sect2>
+<sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <listitem>
+ <para>
+ When called in the local session, it returns set of records made of the
+ foreign server names of all the open connections that are previously made
+ to the foreign servers and <literal>true</literal> or <literal>false</literal>
+ to show whether or not the connection is valid. The foreign server
+ connections can get invalidated due to alter statements on foreign server
+ or user mapping such connections get closed at the end of transaction. If
+ there are no open connections, then no record is returned. This function
+ issues a warning in case for any connection, the associated foreign
+ server has been dropped and the server name can not be fetched from the
+ system catalogues. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback1 | t
+ loopback2 | f
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with the foreign server name as input,
+ it discards the unused open connection previously made to the foreign
+ server and returns <literal>true</literal>. If the open connection is
+ still being used in the current transaction, it is not discarded, instead
+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal>
+ is returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect() returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with no input argument, it discards all
+ the unused open connections that are previously made to the foreign
+ servers and returns <literal>true</literal>. If there is any open
+ connection that is still being used in the current transaction, then a
+ warning is issued. <literal>false</literal> is returned when no open
+ connection is discarded or there are no open connections at all. Example
+ usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+</sect2>
+
<sect2>
<title>Connection Management</title>
@@ -490,6 +566,20 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v9-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchapplication/octet-stream; name=v9-0002-postgres_fdw-add-keep_connections-GUC-to-not-cach.patchDownload
From bfe92eea0036acd451d08d619a24f83656c4b052 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 8 Jan 2021 19:57:13 +0530
Subject: [PATCH v9 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 30 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 44 +++++++++++++++-
6 files changed, 151 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 5793c7b0ee..307bec9cee 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -827,6 +827,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -837,6 +838,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -971,16 +974,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c737de10ff..b1f29e5f6b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9450,3 +9450,53 @@ DETAIL: Such connections are discarded at the end of remote transaction.
(0 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 576fdde0c9..114caeedc2 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2871,3 +2871,33 @@ DROP SERVER temploopback2 CASCADE;
-- Should output nothing.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; /* WARNING */
COMMIT;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 1da9599f59..49344efff2 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -555,6 +555,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -573,12 +609,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v9-0003-postgres_fdw-server-level-option-keep_connection-.patchapplication/octet-stream; name=v9-0003-postgres_fdw-server-level-option-keep_connection-.patchDownload
From 0d16335f49cf51c17700d37c22d39330bd8f941d Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 8 Jan 2021 20:01:06 +0530
Subject: [PATCH v9 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 150 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 307bec9cee..f1f848759d 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -63,6 +63,8 @@ typedef struct ConnCacheEntry
Oid serverid;
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -135,6 +137,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -272,6 +276,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -296,6 +309,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -974,15 +988,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1143,7 +1160,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b1f29e5f6b..45aef933c2 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9500,3 +9500,45 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 114caeedc2..8488c06d43 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2901,3 +2901,29 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 49344efff2..f554351dd0 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -578,6 +607,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -621,6 +658,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/09 10:12, Bharath Rupireddy wrote:
On Fri, Jan 8, 2021 at 9:55 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I will make the changes and post a new patch set soon.
Attaching v9 patch set that has addressed the review comments on the
disconnect function returning setof records, documentation changes,
and postgres_fdw--1.0-1.1.sql changes.Please consider the v9 patch set for further review.
Thanks for updating the patch! I reviewed only 0001 patch.
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
This code is not necessary at least in pgfdw_xact_callback() and
pgfdw_subxact_callback()? Because those functions check
"if (!xact_got_connection)" before checking the above condition.
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
What about applying only the change about serverid, as a separate patch at
first? This change itself is helpful to get rid of error "cache lookup failed"
in pgfdw_reject_incomplete_xact_state_change(). Patch attached.
+ server = GetForeignServerExtended(entry->serverid, true);
Since the type of second argument in GetForeignServerExtended() is bits16,
it's invalid to specify "true" there?
+ if (no_server_conn_cnt > 0)
+ {
+ ereport(WARNING,
+ (errmsg_plural("found an active connection for which the foreign server would have been dropped",
+ "found some active connections for which the foreign servers would have been dropped",
+ no_server_conn_cnt),
+ no_server_conn_cnt > 1 ?
+ errdetail("Such connections are discarded at the end of remote transaction.")
+ : errdetail("Such connection is discarded at the end of remote transaction.")));
At least for me, I like returning such connections with "NULL" in server_name
column and "false" in valid column, rather than emitting a warning. Because
which would enable us to count the number of actual foreign connections
easily by using SQL, for example.
+ * During the first call, we initialize the function context, get the list
+ * of active connections using get_connections and store this in the
+ * function's memory context so that it can live multiple calls.
+ */
+ if (SRF_IS_FIRSTCALL())
I guess that you used value-per-call mode to make the function return
a set result since you refered to dblink_get_pkey(). But isn't it better to
use materialize mode like dblink_get_notify() does rather than
value-per-call because this function returns not so many records? ISTM
that we can simplify postgres_fdw_get_connections() by using materialize mode.
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
If GetConnection() is called after ConnectionHash is destroyed,
it initialize the hashtable and registers some callback functions again
even though the same function have already been registered. This causes
same function to be registered as a callback more than once. This is
a bug.
+CREATE FUNCTION postgres_fdw_disconnect ()
Do we really want postgres_fdw_disconnect() with no argument?
IMO postgres_fdw_disconnect() with the server name specified is enough.
But I'd like to hear the opinion about that.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attachments:
serverid_v1.patchtext/plain; charset=UTF-8; name=serverid_v1.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 266f66cc62..eaedfea9f2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -57,6 +57,7 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
} ConnCacheEntry;
@@ -273,6 +274,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->have_error = false;
entry->changing_xact_state = false;
entry->invalidated = false;
+ entry->serverid = server->serverid;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -1138,8 +1140,6 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
static void
pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
{
- HeapTuple tup;
- Form_pg_user_mapping umform;
ForeignServer *server;
/* nothing to do for inactive entries and entries of sane state */
@@ -1150,13 +1150,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
disconnect_pg_server(entry);
/* find server name to be shown in the message below */
- tup = SearchSysCache1(USERMAPPINGOID,
- ObjectIdGetDatum(entry->key));
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
- umform = (Form_pg_user_mapping) GETSTRUCT(tup);
- server = GetForeignServer(umform->umserver);
- ReleaseSysCache(tup);
+ server = GetForeignServer(entry->serverid);
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_EXCEPTION),
On Thu, Jan 14, 2021 at 3:52 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
- if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for user mapping %u", entry->key); - umform = (Form_pg_user_mapping) GETSTRUCT(tup); - server = GetForeignServer(umform->umserver); - ReleaseSysCache(tup); + server = GetForeignServer(entry->serverid);What about applying only the change about serverid, as a separate patch at
first? This change itself is helpful to get rid of error "cache lookup failed"
in pgfdw_reject_incomplete_xact_state_change(). Patch attached.
Right, we can get rid of the "cache lookup failed for user mapping"
error and also storing server oid in the cache entry is helpful for
the new functions we are going to introduce.
serverid_v1.patch looks good to me. Both make check and make
check-world passes on my system.
I will respond to other comments soon.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/14 20:36, Bharath Rupireddy wrote:
On Thu, Jan 14, 2021 at 3:52 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
- if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for user mapping %u", entry->key); - umform = (Form_pg_user_mapping) GETSTRUCT(tup); - server = GetForeignServer(umform->umserver); - ReleaseSysCache(tup); + server = GetForeignServer(entry->serverid);What about applying only the change about serverid, as a separate patch at
first? This change itself is helpful to get rid of error "cache lookup failed"
in pgfdw_reject_incomplete_xact_state_change(). Patch attached.Right, we can get rid of the "cache lookup failed for user mapping"
error and also storing server oid in the cache entry is helpful for
the new functions we are going to introduce.serverid_v1.patch looks good to me. Both make check and make
check-world passes on my system.
Thanks for the check! I pushed the patch.
I will respond to other comments soon.
Thanks!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Thu, Jan 14, 2021 at 3:52 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/09 10:12, Bharath Rupireddy wrote:
On Fri, Jan 8, 2021 at 9:55 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I will make the changes and post a new patch set soon.
Attaching v9 patch set that has addressed the review comments on the
disconnect function returning setof records, documentation changes,
and postgres_fdw--1.0-1.1.sql changes.Please consider the v9 patch set for further review.
Thanks for updating the patch! I reviewed only 0001 patch.
+ /* + * Quick exit if the cache has been destroyed in + * disconnect_cached_connections. + */ + if (!ConnectionHash) + return;This code is not necessary at least in pgfdw_xact_callback() and
pgfdw_subxact_callback()? Because those functions check
"if (!xact_got_connection)" before checking the above condition.
Yes, if xact_got_connection is true, then ConnectionHash wouldn't have
been cleaned up in disconnect_cached_connections. +1 to remove that in
pgfdw_xact_callback and pgfdw_subxact_callback. But we need that check
in pgfdw_inval_callback, because we may reach there after
ConnectionHash is destroyed and set to NULL in
disconnect_cached_connections.
+ server = GetForeignServerExtended(entry->serverid, true);
Since the type of second argument in GetForeignServerExtended() is bits16,
it's invalid to specify "true" there?
Yeah. I will change it to be something like below:
bits16 flags = FSV_MISSING_OK;
server = GetForeignServerExtended(entry->serverid, flags);
+ if (no_server_conn_cnt > 0) + { + ereport(WARNING, + (errmsg_plural("found an active connection for which the foreign server would have been dropped", + "found some active connections for which the foreign servers would have been dropped", + no_server_conn_cnt), + no_server_conn_cnt > 1 ? + errdetail("Such connections are discarded at the end of remote transaction.") + : errdetail("Such connection is discarded at the end of remote transaction.")));At least for me, I like returning such connections with "NULL" in server_name
column and "false" in valid column, rather than emitting a warning. Because
which would enable us to count the number of actual foreign connections
easily by using SQL, for example.
+1. I was also of the similar opinion about this initially. I will change this.
+ * During the first call, we initialize the function context, get the list + * of active connections using get_connections and store this in the + * function's memory context so that it can live multiple calls. + */ + if (SRF_IS_FIRSTCALL())I guess that you used value-per-call mode to make the function return
a set result since you refered to dblink_get_pkey(). But isn't it better to
use materialize mode like dblink_get_notify() does rather than
value-per-call because this function returns not so many records? ISTM
that we can simplify postgres_fdw_get_connections() by using materialize mode.
Yeah. +1 I will change it to use materialize mode.
+ hash_destroy(ConnectionHash); + ConnectionHash = NULL;If GetConnection() is called after ConnectionHash is destroyed,
it initialize the hashtable and registers some callback functions again
even though the same function have already been registered. This causes
same function to be registered as a callback more than once. This is
a bug.
Yeah, we will register the same callbacks many times. I'm thinking to
have something like below:
static bool conn_cache_destroyed = false;
if (!active_conn_exists)
{
hash_destroy(ConnectionHash);
ConnectionHash = NULL;
conn_cache_destroyed = true;
}
/*
* Register callback functions that manage connection cleanup. This
* should be done just once in each backend. We don't register the
* callbacks again, if the connection cache is destroyed at least once
* in the backend.
*/
if (!conn_cache_destroyed)
{
RegisterXactCallback(pgfdw_xact_callback, NULL);
RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
pgfdw_inval_callback, (Datum) 0);
CacheRegisterSyscacheCallback(USERMAPPINGOID,
pgfdw_inval_callback, (Datum) 0);
}
Thoughts?
+CREATE FUNCTION postgres_fdw_disconnect ()
Do we really want postgres_fdw_disconnect() with no argument?
IMO postgres_fdw_disconnect() with the server name specified is enough.
But I'd like to hear the opinion about that.
IMO, we should have that. Though a bit impractical use case, if we
have many connections which are not being used and want to disconnect
them at once, this function will be useful.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Sat, Jan 16, 2021 at 10:36 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Please consider the v9 patch set for further review.
Thanks for updating the patch! I reviewed only 0001 patch.
I addressed the review comments and attached v10 patch set. Please
consider it for further review.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v10-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/x-patch; name=v10-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From 3cf7d7d4d7241460997530ed01c2a53508257786 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sun, 17 Jan 2021 12:30:22 +0530
Subject: [PATCH v10 1/3] postgres_fdw function to discard cached connections
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name. When called without any argument,
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 330 +++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 414 +++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 20 +
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 160 +++++++
doc/src/sgml/postgres-fdw.sgml | 90 ++++
7 files changed, 999 insertions(+), 19 deletions(-)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..c1b0cad453 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eaedfea9f2..b6da4899ea 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -16,12 +16,14 @@
#include "access/xact.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -66,6 +68,7 @@ typedef struct ConnCacheEntry
* Connection cache (initialized on first use)
*/
static HTAB *ConnectionHash = NULL;
+static bool conn_cache_destroyed = false;
/* for assigning cursor numbers and prepared statement numbers */
static unsigned int cursor_number = 0;
@@ -74,6 +77,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -95,6 +104,8 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all,
+ bool *is_in_use);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -127,15 +138,20 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
HASH_ELEM | HASH_BLOBS);
/*
- * Register some callback functions that manage connection cleanup.
- * This should be done just once in each backend.
+ * Register callback functions that manage connection cleanup. This
+ * should be done just once in each backend. We don't register the
+ * callbacks again, if the connection cache is destroyed at least once
+ * in the backend.
*/
- RegisterXactCallback(pgfdw_xact_callback, NULL);
- RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
- CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
- pgfdw_inval_callback, (Datum) 0);
- CacheRegisterSyscacheCallback(USERMAPPINGOID,
- pgfdw_inval_callback, (Datum) 0);
+ if (!conn_cache_destroyed)
+ {
+ RegisterXactCallback(pgfdw_xact_callback, NULL);
+ RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
+ CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+ pgfdw_inval_callback, (Datum) 0);
+ CacheRegisterSyscacheCallback(USERMAPPINGOID,
+ pgfdw_inval_callback, (Datum) 0);
+ }
}
/* Set flag that we did GetConnection during the current transaction */
@@ -1095,6 +1111,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1335,3 +1358,294 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List active foreign server connections.
+ *
+ * This function takes no input parameter and returns setof record made of
+ * following values:
+ * - server_name - server name of active connection. In case the foreign server
+ * is dropped but still the connection is active, then the server name will
+ * be NULL in output.
+ * - valid - true/false representing whether the connection is valid or not.
+ * Note that the connections can get invalidated in pgfdw_inval_callback.
+ *
+ * No records are returned when there are no cached connections at all.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* If cache doesn't exist, we return no records. */
+ if (!ConnectionHash)
+ {
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+ }
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+ bits16 flags = FSV_MISSING_OK;
+ Datum values[2];
+ bool nulls[2];
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, flags);
+
+ /*
+ * The foreign server may have been dropped in current explicit
+ * transaction. It is not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so, the drop query in another session
+ * blocks until the current transaction finishes.
+ *
+ * Even though the server is dropped in the current transaction, the
+ * cache can still have associated active connection entry, say we call
+ * such connections dangling. Since we can not fetch the server name
+ * from system catalogues for dangling connections, instead we show
+ * NULL value for server name in output.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But the server name in each cache entry requires 64 bytes of memory,
+ * which is huge, when there are many cached connections and the use
+ * case i.e. dropping the foreign server within the explicit current
+ * transaction seems rare. So, we chose to show NULL value for server
+ * name in output.
+ *
+ * Such dangling connections get closed either in next use or at the
+ * end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note that
+ * this connection would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated);
+
+ /* Show null, if no server name was found. */
+ values[0] = (Datum) NULL;
+ nulls[0] = true;
+ }
+ else
+ {
+ values[0] = CStringGetTextDatum(server->servername);
+ nulls[0] = false;
+ }
+
+ values[1] = BoolGetDatum(!entry->invalidated);
+ nulls[1] = false;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Disconnect cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+ bool is_in_use = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false, &is_in_use);
+
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+ else
+ {
+ result = disconnect_cached_connections(0, true, &is_in_use);
+
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform the
+ * user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection. Others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not closed any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections. And
+ * set is_in_use flag to true when there exists at least one connection that's
+ * being used in the current transaction.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all, bool *is_in_use)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ *is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, mark it so
+ * that we don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * Destroy the cache if we discarded all active connections i.e. if there
+ * is no single active connection, which we can know while scanning the
+ * cached entries in the above loop. Destroying the cache is better than to
+ * keep it in the memory with all inactive entries in it to save some
+ * memory. Cache can get initialized on the subsequent queries to foreign
+ * server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ conn_cache_destroyed = true;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..4a0078b1e7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,377 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should output temploopback1,
+-- temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | f
+ temploopback2 | t
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | f
+ temploopback2 | f
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2 and null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+ | f
+(2 rows)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output 2 rows of each null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ | f
+ | f
+(2 rows)
+
+COMMIT;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
new file mode 100644
index 0000000000..b616316999
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -0,0 +1,20 @@
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
+ OUT valid boolean)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..3465cc0f56 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,142 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+
+-- List all the existing cached connections. Should output temploopback1,
+-- temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+
+-- List all the existing cached connections. Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2 and null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER temploopback2 CASCADE;
+-- Should output 2 rows of each null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..1da9599f59 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -479,6 +479,82 @@ OPTIONS (ADD password_required 'false');
</sect3>
</sect2>
+<sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <listitem>
+ <para>
+ When called in the local session, it returns set of records made of the
+ foreign server names of all the open connections that are previously made
+ to the foreign servers and <literal>true</literal> or <literal>false</literal>
+ to show whether or not the connection is valid. The foreign server
+ connections can get invalidated due to alter statements on foreign server
+ or user mapping such connections get closed at the end of transaction. If
+ there are no open connections, then no record is returned. This function
+ issues a warning in case for any connection, the associated foreign
+ server has been dropped and the server name can not be fetched from the
+ system catalogues. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback1 | t
+ loopback2 | f
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with the foreign server name as input,
+ it discards the unused open connection previously made to the foreign
+ server and returns <literal>true</literal>. If the open connection is
+ still being used in the current transaction, it is not discarded, instead
+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal>
+ is returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect() returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with no input argument, it discards all
+ the unused open connections that are previously made to the foreign
+ servers and returns <literal>true</literal>. If there is any open
+ connection that is still being used in the current transaction, then a
+ warning is issued. <literal>false</literal> is returned when no open
+ connection is discarded or there are no open connections at all. Example
+ usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+</sect2>
+
<sect2>
<title>Connection Management</title>
@@ -490,6 +566,20 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v10-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/x-patch; name=v10-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From ddaaa288e7115457d32420bd177435a98790514f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 16 Jan 2021 11:13:58 +0530
Subject: [PATCH v10 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 30 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 44 +++++++++++++++-
6 files changed, 151 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 396ae63415..260e956195 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -815,6 +815,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -825,6 +826,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -959,16 +962,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 4a0078b1e7..a655c7025f 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,3 +9449,53 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
(2 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 3465cc0f56..8a16dc40aa 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2871,3 +2871,33 @@ DROP SERVER temploopback2 CASCADE;
-- Should output 2 rows of each null server name with valid false.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
COMMIT;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 1da9599f59..49344efff2 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -555,6 +555,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -573,12 +609,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v10-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/x-patch; name=v10-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From a67378b0f0d154e0c59381e39d7f0edef90dd064 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 16 Jan 2021 11:15:42 +0530
Subject: [PATCH v10 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 150 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 260e956195..1e305312f7 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -125,6 +127,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -267,6 +271,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -291,6 +304,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -962,15 +976,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1124,7 +1141,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index a655c7025f..1057e44a6a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9499,3 +9499,45 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8a16dc40aa..c9b780abe8 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2901,3 +2901,29 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 49344efff2..f554351dd0 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -578,6 +607,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -621,6 +658,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
Hi,
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name.
I think the following would read better:
This patch introduces *a* new function postgres_fdw_disconnect(). When
called with a foreign server name, it discards the associated
connections with the server.
Please note the removal of the 'name' at the end - connection is with
server, not server name.
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is
still in use")));
It would be better to include servername in the message.
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some
of them are still in use")));
I think showing the number of active connections would be more informative.
This can be achieved by changing active_conn_exists from bool to int (named
active_conns, e.g.):
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
Instead of setting the bool value, active_conns can be incremented.
Cheers
On Sat, Jan 16, 2021 at 11:39 PM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
Show quoted text
On Sat, Jan 16, 2021 at 10:36 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:Please consider the v9 patch set for further review.
Thanks for updating the patch! I reviewed only 0001 patch.
I addressed the review comments and attached v10 patch set. Please
consider it for further review.With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Sun, Jan 17, 2021 at 11:30 PM Zhihong Yu <zyu@yugabyte.com> wrote:
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name.I think the following would read better:
This patch introduces a new function postgres_fdw_disconnect(). When
called with a foreign server name, it discards the associated
connections with the server.
Thanks. I corrected the commit message.
Please note the removal of the 'name' at the end - connection is with server, not server name.
+ if (is_in_use) + ereport(WARNING, + (errmsg("cannot close the connection because it is still in use")));It would be better to include servername in the message.
User would have provided the servername in
postgres_fdw_disconnect('myserver'), I don't think we need to emit the
warning again with the servername. The existing warning seems fine.
+ ereport(WARNING, + (errmsg("cannot close all connections because some of them are still in use")));I think showing the number of active connections would be more informative.
This can be achieved by changing active_conn_exists from bool to int (named active_conns, e.g.):+ if (entry->conn && !active_conn_exists) + active_conn_exists = true;Instead of setting the bool value, active_conns can be incremented.
IMO, the number of active connections is not informative, because
users can not do anything with them. What's actually more informative
would be to list all the server names for which the connections are
active, instead of the warning - "cannot close all connections because
some of them are still in use". Having said that, I feel like it's an
overkill for now to do that. If required, we can enhance the warnings
in future. Thoughts?
Attaching v11 patch set, with changes only in 0001. The changes are
commit message correction and moved the warning related code to
disconnect_cached_connections from postgres_fdw_disconnect.
Please review v11 further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v11-0001-postgres_fdw-functions-to-show-and-discard-cache.patchapplication/octet-stream; name=v11-0001-postgres_fdw-functions-to-show-and-discard-cache.patchDownload
From c02f4438168764b9417f7ed8dc3906b4d8548087 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 08:55:59 +0530
Subject: [PATCH v11 1/3] postgres_fdw functions to show and discard cached
connections
This patch introduces a new function postgres_fdw_disconnect().
When called with a foreign server name, it discards the associated
connection with the server. When called without any argument, it
discards all the existing cached connections.
This patch also adds another function postgres_fdw_get_connections()
to get the list of all cached connections by corresponding foreign
server names.
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 333 +++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 414 +++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 20 +
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 160 +++++++
doc/src/sgml/postgres-fdw.sgml | 90 ++++
7 files changed, 1002 insertions(+), 19 deletions(-)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..c1b0cad453 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eaedfea9f2..e008910124 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -16,12 +16,14 @@
#include "access/xact.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -66,6 +68,7 @@ typedef struct ConnCacheEntry
* Connection cache (initialized on first use)
*/
static HTAB *ConnectionHash = NULL;
+static bool conn_cache_destroyed = false;
/* for assigning cursor numbers and prepared statement numbers */
static unsigned int cursor_number = 0;
@@ -74,6 +77,12 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -95,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -127,15 +137,20 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
HASH_ELEM | HASH_BLOBS);
/*
- * Register some callback functions that manage connection cleanup.
- * This should be done just once in each backend.
+ * Register callback functions that manage connection cleanup. This
+ * should be done just once in each backend. We don't register the
+ * callbacks again, if the connection cache is destroyed at least once
+ * in the backend.
*/
- RegisterXactCallback(pgfdw_xact_callback, NULL);
- RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
- CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
- pgfdw_inval_callback, (Datum) 0);
- CacheRegisterSyscacheCallback(USERMAPPINGOID,
- pgfdw_inval_callback, (Datum) 0);
+ if (!conn_cache_destroyed)
+ {
+ RegisterXactCallback(pgfdw_xact_callback, NULL);
+ RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
+ CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+ pgfdw_inval_callback, (Datum) 0);
+ CacheRegisterSyscacheCallback(USERMAPPINGOID,
+ pgfdw_inval_callback, (Datum) 0);
+ }
}
/* Set flag that we did GetConnection during the current transaction */
@@ -1095,6 +1110,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1335,3 +1357,298 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List active foreign server connections.
+ *
+ * This function takes no input parameter and returns setof record made of
+ * following values:
+ * - server_name - server name of active connection. In case the foreign server
+ * is dropped but still the connection is active, then the server name will
+ * be NULL in output.
+ * - valid - true/false representing whether the connection is valid or not.
+ * Note that the connections can get invalidated in pgfdw_inval_callback.
+ *
+ * No records are returned when there are no cached connections at all.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* If cache doesn't exist, we return no records. */
+ if (!ConnectionHash)
+ {
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+ }
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+ bits16 flags = FSV_MISSING_OK;
+ Datum values[2];
+ bool nulls[2];
+
+ /* We only look for open remote connections. */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, flags);
+
+ /*
+ * The foreign server may have been dropped in current explicit
+ * transaction. It is not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so, the drop query in another session
+ * blocks until the current transaction finishes.
+ *
+ * Even though the server is dropped in the current transaction, the
+ * cache can still have associated active connection entry, say we call
+ * such connections dangling. Since we can not fetch the server name
+ * from system catalogues for dangling connections, instead we show
+ * NULL value for server name in output.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But the server name in each cache entry requires 64 bytes of memory,
+ * which is huge, when there are many cached connections and the use
+ * case i.e. dropping the foreign server within the explicit current
+ * transaction seems rare. So, we chose to show NULL value for server
+ * name in output.
+ *
+ * Such dangling connections get closed either in next use or at the
+ * end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note that
+ * this connection would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated);
+
+ /* Show null, if no server name was found. */
+ values[0] = (Datum) NULL;
+ nulls[0] = true;
+ }
+ else
+ {
+ values[0] = CStringGetTextDatum(server->servername);
+ nulls[0] = false;
+ }
+
+ values[1] = BoolGetDatum(!entry->invalidated);
+ nulls[1] = false;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Disconnect cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false);
+ }
+ else
+ result = disconnect_cached_connections(0, true);
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+ bool is_in_use = false;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, ensure we
+ * don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * is_in_use flag would be set to true when there exists at least one
+ * connection that's being used in the current transaction.
+ */
+ if (all)
+ {
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection, others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not close any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+
+ /*
+ * Destroy the cache if we discarded all active connections i.e. if there
+ * is no single active connection, which we can know while scanning the
+ * cached entries in the above loop. Destroying the cache is better than to
+ * keep it in the memory with all inactive entries in it to save some
+ * memory. Cache can get initialized on the subsequent queries to foreign
+ * server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ conn_cache_destroyed = true;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..4a0078b1e7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,22 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +139,16 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -191,15 +211,17 @@ ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
- List of foreign tables
- Schema | Table | Server | FDW options | Description
---------+-------+-----------+---------------------------------------+-------------
- public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
- public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
- public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
- public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ List of foreign tables
+ Schema | Table | Server | FDW options | Description
+--------+------------+---------------+---------------------------------------+-------------
+ public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') |
+ public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
+ public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
+ public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl1 | temploopback1 | (schema_name 'S 1', table_name 'T 4') |
+ public | templbtbl2 | temploopback2 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,3 +9075,377 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. Should output temploopback1,
+-- temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+ERROR: foreign server "unknownserver" does not exist
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+COMMIT;
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+COMMIT;
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+COMMIT;
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | f
+ temploopback2 | t
+(2 rows)
+
+COMMIT;
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+(1 row)
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | f
+ temploopback2 | f
+(2 rows)
+
+COMMIT;
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback1 | t
+ temploopback2 | t
+(2 rows)
+
+DROP SERVER temploopback1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback1
+drop cascades to foreign table templbtbl1
+-- Should output temploopback2 and null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+---------------+-------
+ temploopback2 | t
+ | f
+(2 rows)
+
+DROP SERVER temploopback2 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server temploopback2
+drop cascades to foreign table templbtbl2
+-- Should output 2 rows of each null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ | f
+ | f
+(2 rows)
+
+COMMIT;
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
new file mode 100644
index 0000000000..b616316999
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -0,0 +1,20 @@
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
+ OUT valid boolean)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME','postgres_fdw_get_connections'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..3465cc0f56 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,14 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER temploopback1 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ EXECUTE $$CREATE SERVER temploopback2 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -22,6 +30,8 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER temploopback1;
+CREATE USER MAPPING FOR public SERVER temploopback2;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +152,17 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE templbtbl1 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback1 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
+CREATE FOREIGN TABLE templbtbl2 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER temploopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2711,3 +2732,142 @@ SELECT 1 FROM ft1 LIMIT 1;
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+
+-- ========================================================================
+-- Test postgres_fdw_get_connections and postgres_fdw_disconnect functions
+-- ========================================================================
+
+-- Discard all existing connections, before starting the tests.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Ensure to have temploopback1 and temploopback2 connections cached.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+
+-- List all the existing cached connections. Should output temploopback1,
+-- temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- temploopback1 connection is disconnected. temploopback2 connection still
+-- exists.
+SELECT postgres_fdw_disconnect('temploopback1'); /* TRUE */
+
+-- List all the existing cached connections. Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Cache exists, but the temploopback1 connection is not present in it.
+SELECT postgres_fdw_disconnect('temploopback1'); /* FALSE */
+
+-- Cache exists, but the server name provided doesn't exist.
+SELECT postgres_fdw_disconnect('unknownserver'); /* ERROR */
+
+-- Cache and temploopback2 connection exist, so discard it.
+SELECT postgres_fdw_disconnect(); /* TRUE */
+
+-- Cache does not exist.
+SELECT postgres_fdw_disconnect(); /* FALSE */
+
+-- Test the functions inside explicit xact.
+-- Connections are being used in the xact, so they cannot be disconnected.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* WARNING and FALSE */
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and FALSE */
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Connections can be closed in the xact because they are not in use.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+BEGIN;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect('temploopback1'); /* TRUE */
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is closed and temploopback2 is not, because it's
+-- being used in the xact.
+SELECT 1 FROM templbtbl1 LIMIT 1;
+BEGIN;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT * FROM postgres_fdw_disconnect(); /* WARNING and TRUE */
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- temploopback1 connection is invalidated and temploopback2 is not.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output temploopback1 as invalid, temploopback2 as valid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Invalidated connection i.e. temploopback1 was closed at the end of the xact.
+-- Should output temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Should disconnect temploopback2.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- Both temploopback1 and temploopback2 connections are invalidated.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+ALTER SERVER temploopback1 OPTIONS (SET use_remote_estimate 'off');
+ALTER SERVER temploopback2 OPTIONS (ADD use_remote_estimate 'off');
+-- Should output both temploopback1 and temploopback2 as invalid.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+
+-- Invalidated connections i.e. temploopback1 and temploopback2 were closed at
+-- the end of the xact. Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- No active connections.
+SELECT * FROM postgres_fdw_disconnect(); /* FALSE */
+
+-- Both the servers were dropped inside the xact block, so a warning is
+-- emitted.
+BEGIN;
+SELECT 1 FROM templbtbl1 LIMIT 1;
+SELECT 1 FROM templbtbl2 LIMIT 1;
+-- Should output temploopback1, temploopback2.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER temploopback1 CASCADE;
+-- Should output temploopback2 and null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER temploopback2 CASCADE;
+-- Should output 2 rows of each null server name with valid false.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..1da9599f59 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -479,6 +479,82 @@ OPTIONS (ADD password_required 'false');
</sect3>
</sect2>
+<sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <listitem>
+ <para>
+ When called in the local session, it returns set of records made of the
+ foreign server names of all the open connections that are previously made
+ to the foreign servers and <literal>true</literal> or <literal>false</literal>
+ to show whether or not the connection is valid. The foreign server
+ connections can get invalidated due to alter statements on foreign server
+ or user mapping such connections get closed at the end of transaction. If
+ there are no open connections, then no record is returned. This function
+ issues a warning in case for any connection, the associated foreign
+ server has been dropped and the server name can not be fetched from the
+ system catalogues. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback1 | t
+ loopback2 | f
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with the foreign server name as input,
+ it discards the unused open connection previously made to the foreign
+ server and returns <literal>true</literal>. If the open connection is
+ still being used in the current transaction, it is not discarded, instead
+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal>
+ is returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect() returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with no input argument, it discards all
+ the unused open connections that are previously made to the foreign
+ servers and returns <literal>true</literal>. If there is any open
+ connection that is still being used in the current transaction, then a
+ warning is issued. <literal>false</literal> is returned when no open
+ connection is discarded or there are no open connections at all. Example
+ usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+</sect2>
+
<sect2>
<title>Connection Management</title>
@@ -490,6 +566,20 @@ OPTIONS (ADD password_required 'false');
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v11-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v11-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From ddaaa288e7115457d32420bd177435a98790514f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 16 Jan 2021 11:13:58 +0530
Subject: [PATCH v11 2/3] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 50 +++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 ++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 30 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 44 +++++++++++++++-
6 files changed, 151 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 396ae63415..260e956195 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -815,6 +815,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -825,6 +826,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -959,16 +962,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 4a0078b1e7..a655c7025f 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,3 +9449,53 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
(2 rows)
COMMIT;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 3465cc0f56..8a16dc40aa 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2871,3 +2871,33 @@ DROP SERVER temploopback2 CASCADE;
-- Should output 2 rows of each null server name with valid false.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
COMMIT;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 1da9599f59..49344efff2 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -555,6 +555,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -573,12 +609,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v11-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v11-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From a67378b0f0d154e0c59381e39d7f0edef90dd064 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 16 Jan 2021 11:15:42 +0530
Subject: [PATCH v11 3/3] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 35 ++++++++++++---
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 26 +++++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 150 insertions(+), 7 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 260e956195..1e305312f7 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -125,6 +127,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -267,6 +271,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -291,6 +304,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -962,15 +976,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
@@ -1124,7 +1141,15 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
if (!ConnectionHash)
return;
- /* ConnectionHash must exist already, if we're registered */
+ /*
+ * Since we can discard ConnectionHash with postgres_fdw_disconnect, we may
+ * have a NULL ConnectionHash. So return in that case. We do not need to
+ * invalidate the cache entries as the cache itself would have discarded
+ * with postgres_fdw_disconnect.
+ */
+ if (!ConnectionHash)
+ return;
+
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index a655c7025f..1057e44a6a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8933,7 +8933,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9499,3 +9499,45 @@ SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8a16dc40aa..c9b780abe8 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2901,3 +2901,29 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Discard loopback connection.
SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Discard loopback connection.
+SELECT * FROM postgres_fdw_disconnect(); /* TRUE */
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 49344efff2..f554351dd0 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -578,6 +607,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -621,6 +658,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/18 12:33, Bharath Rupireddy wrote:
On Sun, Jan 17, 2021 at 11:30 PM Zhihong Yu <zyu@yugabyte.com> wrote:
This patch introduces new function postgres_fdw_disconnect() when
called with a foreign server name discards the associated
connections with the server name.I think the following would read better:
This patch introduces a new function postgres_fdw_disconnect(). When
called with a foreign server name, it discards the associated
connections with the server.Thanks. I corrected the commit message.
Please note the removal of the 'name' at the end - connection is with server, not server name.
+ if (is_in_use) + ereport(WARNING, + (errmsg("cannot close the connection because it is still in use")));It would be better to include servername in the message.
User would have provided the servername in
postgres_fdw_disconnect('myserver'), I don't think we need to emit the
warning again with the servername. The existing warning seems fine.+ ereport(WARNING, + (errmsg("cannot close all connections because some of them are still in use")));I think showing the number of active connections would be more informative.
This can be achieved by changing active_conn_exists from bool to int (named active_conns, e.g.):+ if (entry->conn && !active_conn_exists) + active_conn_exists = true;Instead of setting the bool value, active_conns can be incremented.
IMO, the number of active connections is not informative, because
users can not do anything with them. What's actually more informative
would be to list all the server names for which the connections are
active, instead of the warning - "cannot close all connections because
some of them are still in use". Having said that, I feel like it's an
overkill for now to do that. If required, we can enhance the warnings
in future. Thoughts?Attaching v11 patch set, with changes only in 0001. The changes are
commit message correction and moved the warning related code to
disconnect_cached_connections from postgres_fdw_disconnect.Please review v11 further.
Thanks for updating the patch!
The patch for postgres_fdw_get_connections() basically looks good to me.
So at first I'd like to push it. Attached is the patch that I extracted
postgres_fdw_get_connections() part from 0001 patch and tweaked.
Thought?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attachments:
postgres_fdw_get_connections_v1.patchtext/plain; charset=UTF-8; name=postgres_fdw_get_connections_v1.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..c1b0cad453 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eaedfea9f2..a1404cb6bb 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -16,12 +16,14 @@
#include "access/xact.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -74,6 +76,11 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -1335,3 +1342,131 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List active foreign server connections.
+ *
+ * This function takes no input parameter and returns setof record made of
+ * following values:
+ * - server_name - server name of active connection. In case the foreign server
+ * is dropped but still the connection is active, then the server name will
+ * be NULL in output.
+ * - valid - true/false representing whether the connection is valid or not.
+ * Note that the connections can get invalidated in pgfdw_inval_callback.
+ *
+ * No records are returned when there are no cached connections at all.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+#define POSTGRES_FDW_GET_CONNECTIONS_COLS 2
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* If cache doesn't exist, we return no records */
+ if (!ConnectionHash)
+ {
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+ }
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+ Datum values[POSTGRES_FDW_GET_CONNECTIONS_COLS];
+ bool nulls[POSTGRES_FDW_GET_CONNECTIONS_COLS];
+
+ /* We only look for open remote connections */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, FSV_MISSING_OK);
+
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * The foreign server may have been dropped in current explicit
+ * transaction. It is not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so, the drop query in another session
+ * blocks until the current transaction finishes.
+ *
+ * Even though the server is dropped in the current transaction, the
+ * cache can still have associated active connection entry, say we
+ * call such connections dangling. Since we can not fetch the server
+ * name from system catalogs for dangling connections, instead we
+ * show NULL value for server name in output.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But the server name in each cache entry requires 64 bytes of
+ * memory, which is huge, when there are many cached connections and
+ * the use case i.e. dropping the foreign server within the explicit
+ * current transaction seems rare. So, we chose to show NULL value for
+ * server name in output.
+ *
+ * Such dangling connections get closed either in next use or at the
+ * end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this connection would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated);
+
+ /* Show null, if no server name was found */
+ nulls[0] = true;
+ }
+ else
+ values[0] = CStringGetTextDatum(server->servername);
+
+ values[1] = BoolGetDatum(!entry->invalidated);
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..418addcc4c 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,18 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR CURRENT_USER SERVER loopback3;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +135,11 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft7 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -199,7 +210,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
+(6 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9040,6 +9052,13 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- ===================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
+-- List all the existing cached connections. Only loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
?column?
@@ -9047,9 +9066,49 @@ SELECT 1 FROM ft1 LIMIT 1;
1
(1 row)
+SELECT 1 FROM ft7 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback3
+-- also should be output as valid connections.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+ loopback3 | t
+(3 rows)
+
-- Connection is not closed at the end of the alter statement in
-- pgfdw_inval_callback. That's because the connection is in midst of this
-- xact, it is just marked as invalid.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
+DROP SERVER loopback3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for postgres on server loopback3
+drop cascades to foreign table ft7
+-- List all the existing cached connections. loopback and loopback3
+-- should be output as invalid connections. Also the server name for
+-- loopback3 should be NULL because the server was dropped.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | f
+ loopback2 | t
+ | f
+(3 rows)
+
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- List all the existing cached connections. loopback and loopback3
+-- should not be output because they should be closed at the end of
+-- the above transaction.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
new file mode 100644
index 0000000000..7f85784466
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -0,0 +1,10 @@
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
+ OUT valid boolean)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..c1a7f57222 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,11 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+
END;
$d$;
@@ -22,6 +27,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR CURRENT_USER SERVER loopback3;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +148,12 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft7 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2703,11 +2715,26 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- ===================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
+-- List all the existing cached connections. Only loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft7 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback3
+-- also should be output as valid connections.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Connection is not closed at the end of the alter statement in
-- pgfdw_inval_callback. That's because the connection is in midst of this
-- xact, it is just marked as invalid.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
+DROP SERVER loopback3 CASCADE;
+-- List all the existing cached connections. loopback and loopback3
+-- should be output as invalid connections. Also the server name for
+-- loopback3 should be NULL because the server was dropped.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- List all the existing cached connections. loopback and loopback3
+-- should not be output because they should be closed at the end of
+-- the above transaction.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..6a91926da8 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -479,6 +479,38 @@ OPTIONS (ADD password_required 'false');
</sect3>
</sect2>
+<sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <listitem>
+ <para>
+ This function returns the foreign server names of all the open
+ connections that <filename>postgres_fdw</filename> established from
+ the local session to the foreign servers. It also returns whether
+ each connection is valid or not. <literal>false</literal> is returned
+ if the foreign server connection is used in the current local
+ transaction but its foreign server or user mapping is changed or
+ dropped, and then such invalid connection will be closed at
+ the end of that transaction. <literal>true</literal> is returned
+ otherwise. If there are no open connections, no record is returned.
+ Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback1 | t
+ loopback2 | f
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+</sect2>
+
<sect2>
<title>Connection Management</title>
On Mon, Jan 18, 2021 at 9:38 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Please review v11 further.
Thanks for updating the patch!
The patch for postgres_fdw_get_connections() basically looks good to me.
So at first I'd like to push it. Attached is the patch that I extracted
postgres_fdw_get_connections() part from 0001 patch and tweaked.
Thought?
Thanks.
We need to create the loopback3 with user mapping public, otherwise
the test might become unstable as shown below. Note that loopback and
loopback2 are not dropped in the test, so no problem with them.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
DROP SERVER loopback3 CASCADE;
NOTICE: drop cascades to 2 other objects
-DETAIL: drop cascades to user mapping for postgres on server loopback3
+DETAIL: drop cascades to user mapping for bharath on server loopback3
Attaching v2 patch for postgres_fdw_get_connections. Please have a look.
I will post patches for the other function postgres_fdw_disconnect,
GUC and server level option later.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
postgres_fdw_get_connections_v2.patchapplication/octet-stream; name=postgres_fdw_get_connections_v2.patchDownload
From c28f59a43386a928907016b54650cf09ad2dd137 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 10:11:40 +0530
Subject: [PATCH v2] postgres_fdw_get_connections
---
contrib/postgres_fdw/Makefile | 2 +-
contrib/postgres_fdw/connection.c | 135 ++++++++++++++++++
.../postgres_fdw/expected/postgres_fdw.out | 61 +++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 ++
contrib/postgres_fdw/postgres_fdw.control | 2 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 27 ++++
doc/src/sgml/postgres-fdw.sgml | 32 +++++
7 files changed, 266 insertions(+), 3 deletions(-)
create mode 100644 contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..c1b0cad453 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)
EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
REGRESS = postgres_fdw
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eaedfea9f2..a1404cb6bb 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -16,12 +16,14 @@
#include "access/xact.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postgres_fdw.h"
#include "storage/fd.h"
#include "storage/latch.h"
+#include "utils/builtins.h"
#include "utils/datetime.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
@@ -74,6 +76,11 @@ static unsigned int prep_stmt_number = 0;
/* tracks whether any work is needed in callback functions */
static bool xact_got_connection = false;
+/*
+ * SQL functions
+ */
+PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
@@ -1335,3 +1342,131 @@ exit: ;
*result = last_res;
return timed_out;
}
+
+/*
+ * List active foreign server connections.
+ *
+ * This function takes no input parameter and returns setof record made of
+ * following values:
+ * - server_name - server name of active connection. In case the foreign server
+ * is dropped but still the connection is active, then the server name will
+ * be NULL in output.
+ * - valid - true/false representing whether the connection is valid or not.
+ * Note that the connections can get invalidated in pgfdw_inval_callback.
+ *
+ * No records are returned when there are no cached connections at all.
+ */
+Datum
+postgres_fdw_get_connections(PG_FUNCTION_ARGS)
+{
+#define POSTGRES_FDW_GET_CONNECTIONS_COLS 2
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* If cache doesn't exist, we return no records */
+ if (!ConnectionHash)
+ {
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+ }
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ ForeignServer *server;
+ Datum values[POSTGRES_FDW_GET_CONNECTIONS_COLS];
+ bool nulls[POSTGRES_FDW_GET_CONNECTIONS_COLS];
+
+ /* We only look for open remote connections */
+ if (!entry->conn)
+ continue;
+
+ server = GetForeignServerExtended(entry->serverid, FSV_MISSING_OK);
+
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /*
+ * The foreign server may have been dropped in current explicit
+ * transaction. It is not possible to drop the server from another
+ * session when the connection associated with it is in use in the
+ * current transaction, if tried so, the drop query in another session
+ * blocks until the current transaction finishes.
+ *
+ * Even though the server is dropped in the current transaction, the
+ * cache can still have associated active connection entry, say we
+ * call such connections dangling. Since we can not fetch the server
+ * name from system catalogs for dangling connections, instead we
+ * show NULL value for server name in output.
+ *
+ * We could have done better by storing the server name in the cache
+ * entry instead of server oid so that it could be used in the output.
+ * But the server name in each cache entry requires 64 bytes of
+ * memory, which is huge, when there are many cached connections and
+ * the use case i.e. dropping the foreign server within the explicit
+ * current transaction seems rare. So, we chose to show NULL value for
+ * server name in output.
+ *
+ * Such dangling connections get closed either in next use or at the
+ * end of current explicit transaction in pgfdw_xact_callback.
+ */
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated in
+ * pgfdw_inval_callback at the end of drop sever command. Note
+ * that this connection would not have been closed in
+ * pgfdw_inval_callback because it is still being used in the
+ * current explicit transaction. So, assert that here.
+ */
+ Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated);
+
+ /* Show null, if no server name was found */
+ nulls[0] = true;
+ }
+ else
+ values[0] = CStringGetTextDatum(server->servername);
+
+ values[1] = BoolGetDatum(!entry->invalidated);
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ PG_RETURN_VOID();
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c11092f8cc..1cad311436 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13,12 +13,18 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER loopback3;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -129,6 +135,11 @@ CREATE FOREIGN TABLE ft6 (
c2 int NOT NULL,
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft7 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -199,7 +210,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') |
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
-(5 rows)
+ public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
+(6 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9040,6 +9052,13 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- ===================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
+-- List all the existing cached connections. Only loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
?column?
@@ -9047,9 +9066,49 @@ SELECT 1 FROM ft1 LIMIT 1;
1
(1 row)
+SELECT 1 FROM ft7 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback3
+-- also should be output as valid connections.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+ loopback3 | t
+(3 rows)
+
-- Connection is not closed at the end of the alter statement in
-- pgfdw_inval_callback. That's because the connection is in midst of this
-- xact, it is just marked as invalid.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
+DROP SERVER loopback3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server loopback3
+drop cascades to foreign table ft7
+-- List all the existing cached connections. loopback and loopback3
+-- should be output as invalid connections. Also the server name for
+-- loopback3 should be NULL because the server was dropped.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | f
+ loopback2 | t
+ | f
+(3 rows)
+
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- List all the existing cached connections. loopback and loopback3
+-- should not be output because they should be closed at the end of
+-- the above transaction.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
new file mode 100644
index 0000000000..7f85784466
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -0,0 +1,10 @@
+/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
+ OUT valid boolean)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index f9ed490752..d489382064 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.0'
+default_version = '1.1'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 25dbc08b98..ebf6eb10a6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -15,6 +15,11 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
+ EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+
END;
$d$;
@@ -22,6 +27,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
+CREATE USER MAPPING FOR public SERVER loopback3;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -142,6 +148,12 @@ CREATE FOREIGN TABLE ft6 (
c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft7 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2703,11 +2715,26 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- ===================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
+-- List all the existing cached connections. Only loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
+SELECT 1 FROM ft7 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback3
+-- also should be output as valid connections.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Connection is not closed at the end of the alter statement in
-- pgfdw_inval_callback. That's because the connection is in midst of this
-- xact, it is just marked as invalid.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
+DROP SERVER loopback3 CASCADE;
+-- List all the existing cached connections. loopback and loopback3
+-- should be output as invalid connections. Also the server name for
+-- loopback3 should be NULL because the server was dropped.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- The invalid connection gets closed in pgfdw_xact_callback during commit.
COMMIT;
+-- List all the existing cached connections. loopback and loopback3
+-- should not be output because they should be closed at the end of
+-- the above transaction.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index e6fd2143c1..6a91926da8 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -479,6 +479,38 @@ OPTIONS (ADD password_required 'false');
</sect3>
</sect2>
+<sect2>
+ <title>Functions</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <listitem>
+ <para>
+ This function returns the foreign server names of all the open
+ connections that <filename>postgres_fdw</filename> established from
+ the local session to the foreign servers. It also returns whether
+ each connection is valid or not. <literal>false</literal> is returned
+ if the foreign server connection is used in the current local
+ transaction but its foreign server or user mapping is changed or
+ dropped, and then such invalid connection will be closed at
+ the end of that transaction. <literal>true</literal> is returned
+ otherwise. If there are no open connections, no record is returned.
+ Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback1 | t
+ loopback2 | f
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+</sect2>
+
<sect2>
<title>Connection Management</title>
--
2.25.1
We need to create the loopback3 with user mapping public, otherwise the
test might become unstable as shown below. Note that loopback and
loopback2 are not dropped in the test, so no problem with them.ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); DROP SERVER loopback3 CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to user mapping for postgres on server loopback3 +DETAIL: drop cascades to user mapping for bharath on server loopback3Attaching v2 patch for postgres_fdw_get_connections. Please have a look.
Hi
I have a comment for the doc about postgres_fdw_get_connections.
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <listitem>
+ <para>
+ This function returns the foreign server names of all the open
+ connections that <filename>postgres_fdw</filename> established from
+ the local session to the foreign servers. It also returns whether
+ each connection is valid or not. <literal>false</literal> is returned
+ if the foreign server connection is used in the current local
+ transaction but its foreign server or user mapping is changed or
+ dropped, and then such invalid connection will be closed at
+ the end of that transaction. <literal>true</literal> is returned
+ otherwise. If there are no open connections, no record is returned.
+ Example usage of the function:
The doc seems does not memtion the case when the function returns NULL in server_name.
Users may be a little confused about why NULL was returned.
Best regards,
houzj
On 2021/01/18 13:46, Bharath Rupireddy wrote:
On Mon, Jan 18, 2021 at 9:38 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Please review v11 further.
Thanks for updating the patch!
The patch for postgres_fdw_get_connections() basically looks good to me.
So at first I'd like to push it. Attached is the patch that I extracted
postgres_fdw_get_connections() part from 0001 patch and tweaked.
Thought?Thanks.
We need to create the loopback3 with user mapping public, otherwise
the test might become unstable as shown below. Note that loopback and
loopback2 are not dropped in the test, so no problem with them.ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); DROP SERVER loopback3 CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to user mapping for postgres on server loopback3 +DETAIL: drop cascades to user mapping for bharath on server loopback3Attaching v2 patch for postgres_fdw_get_connections. Please have a look.
Thanks! You're right. I pushed the v2 patch.
I will post patches for the other function postgres_fdw_disconnect,
GUC and server level option later.
Thanks!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/01/18 15:02, Hou, Zhijie wrote:
We need to create the loopback3 with user mapping public, otherwise the
test might become unstable as shown below. Note that loopback and
loopback2 are not dropped in the test, so no problem with them.ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); DROP SERVER loopback3 CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to user mapping for postgres on server loopback3 +DETAIL: drop cascades to user mapping for bharath on server loopback3Attaching v2 patch for postgres_fdw_get_connections. Please have a look.
Hi
I have a comment for the doc about postgres_fdw_get_connections.
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term> + <listitem> + <para> + This function returns the foreign server names of all the open + connections that <filename>postgres_fdw</filename> established from + the local session to the foreign servers. It also returns whether + each connection is valid or not. <literal>false</literal> is returned + if the foreign server connection is used in the current local + transaction but its foreign server or user mapping is changed or + dropped, and then such invalid connection will be closed at + the end of that transaction. <literal>true</literal> is returned + otherwise. If there are no open connections, no record is returned. + Example usage of the function:The doc seems does not memtion the case when the function returns NULL in server_name.
Users may be a little confused about why NULL was returned.
Yes, so what about adding
(Note that the returned server name of invalid connection is NULL if its server is dropped)
into the following (just after "dropped")?
+ if the foreign server connection is used in the current local
+ transaction but its foreign server or user mapping is changed or
+ dropped
Or better description?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Jan 18, 2021 at 11:58 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/18 15:02, Hou, Zhijie wrote:
We need to create the loopback3 with user mapping public, otherwise the
test might become unstable as shown below. Note that loopback and
loopback2 are not dropped in the test, so no problem with them.ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); DROP SERVER loopback3 CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to user mapping for postgres on server loopback3 +DETAIL: drop cascades to user mapping for bharath on server loopback3Attaching v2 patch for postgres_fdw_get_connections. Please have a look.
Hi
I have a comment for the doc about postgres_fdw_get_connections.
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term> + <listitem> + <para> + This function returns the foreign server names of all the open + connections that <filename>postgres_fdw</filename> established from + the local session to the foreign servers. It also returns whether + each connection is valid or not. <literal>false</literal> is returned + if the foreign server connection is used in the current local + transaction but its foreign server or user mapping is changed or + dropped, and then such invalid connection will be closed at + the end of that transaction. <literal>true</literal> is returned + otherwise. If there are no open connections, no record is returned. + Example usage of the function:The doc seems does not memtion the case when the function returns NULL in server_name.
Users may be a little confused about why NULL was returned.Yes, so what about adding
(Note that the returned server name of invalid connection is NULL if its server is dropped)
into the following (just after "dropped")?
+ if the foreign server connection is used in the current local + transaction but its foreign server or user mapping is changed or + droppedOr better description?
+1 to add it after "dropped (Note ........)", how about as follows
with slight changes?
dropped (Note that server name of an invalid connection can be NULL if
the server is dropped), and then such .....
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/18 15:37, Bharath Rupireddy wrote:
On Mon, Jan 18, 2021 at 11:58 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/18 15:02, Hou, Zhijie wrote:
We need to create the loopback3 with user mapping public, otherwise the
test might become unstable as shown below. Note that loopback and
loopback2 are not dropped in the test, so no problem with them.ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); DROP SERVER loopback3 CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to user mapping for postgres on server loopback3 +DETAIL: drop cascades to user mapping for bharath on server loopback3Attaching v2 patch for postgres_fdw_get_connections. Please have a look.
Hi
I have a comment for the doc about postgres_fdw_get_connections.
+ <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term> + <listitem> + <para> + This function returns the foreign server names of all the open + connections that <filename>postgres_fdw</filename> established from + the local session to the foreign servers. It also returns whether + each connection is valid or not. <literal>false</literal> is returned + if the foreign server connection is used in the current local + transaction but its foreign server or user mapping is changed or + dropped, and then such invalid connection will be closed at + the end of that transaction. <literal>true</literal> is returned + otherwise. If there are no open connections, no record is returned. + Example usage of the function:The doc seems does not memtion the case when the function returns NULL in server_name.
Users may be a little confused about why NULL was returned.Yes, so what about adding
(Note that the returned server name of invalid connection is NULL if its server is dropped)
into the following (just after "dropped")?
+ if the foreign server connection is used in the current local + transaction but its foreign server or user mapping is changed or + droppedOr better description?
+1 to add it after "dropped (Note ........)", how about as follows
with slight changes?dropped (Note that server name of an invalid connection can be NULL if
the server is dropped), and then such .....
Yes, I like this one. One question is; "should" or "is" is better than
"can" in this case because the server name of invalid connection is
always NULL when its server is dropped?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Jan 18, 2021 at 6:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
+1 to add it after "dropped (Note ........)", how about as follows
with slight changes?dropped (Note that server name of an invalid connection can be NULL if
the server is dropped), and then such .....Yes, I like this one. One question is; "should" or "is" is better than
"can" in this case because the server name of invalid connection is
always NULL when its server is dropped?
I think "dropped (Note that server name of an invalid connection will
be NULL if the server is dropped), and then such ....."
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Jan 18, 2021 at 11:44 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
I will post patches for the other function postgres_fdw_disconnect,
GUC and server level option later.Thanks!
Attaching v12 patch set. 0001 is for postgres_fdw_disconnect()
function, 0002 is for keep_connections GUC and 0003 is for
keep_connection server level option.
Please review it further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v12-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/octet-stream; name=v12-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From 3d2d5473fae0ed9675c3302caacabdc45dfb72bc Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 17:56:57 +0530
Subject: [PATCH v12] postgres_fdw function to discard cached connections
This patch introduces a new function postgres_fdw_disconnect().
When called with a foreign server name, it discards the associated
connection with the server. When called without any argument, it
discards all the existing cached connections.
---
contrib/postgres_fdw/connection.c | 196 +++++++++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 93 +++++++++
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 +
contrib/postgres_fdw/sql/postgres_fdw.sql | 35 ++++
doc/src/sgml/postgres-fdw.sgml | 58 ++++++
5 files changed, 384 insertions(+), 8 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..1e93a84aaa 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -68,6 +68,7 @@ typedef struct ConnCacheEntry
* Connection cache (initialized on first use)
*/
static HTAB *ConnectionHash = NULL;
+static bool conn_cache_destroyed = false;
/* for assigning cursor numbers and prepared statement numbers */
static unsigned int cursor_number = 0;
@@ -80,6 +81,7 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -134,15 +137,20 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
HASH_ELEM | HASH_BLOBS);
/*
- * Register some callback functions that manage connection cleanup.
- * This should be done just once in each backend.
+ * Register callback functions that manage connection cleanup. This
+ * should be done just once in each backend. We don't register the
+ * callbacks again, if the connection cache is destroyed at least once
+ * in the backend.
*/
- RegisterXactCallback(pgfdw_xact_callback, NULL);
- RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
- CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
- pgfdw_inval_callback, (Datum) 0);
- CacheRegisterSyscacheCallback(USERMAPPINGOID,
- pgfdw_inval_callback, (Datum) 0);
+ if (!conn_cache_destroyed)
+ {
+ RegisterXactCallback(pgfdw_xact_callback, NULL);
+ RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
+ CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+ pgfdw_inval_callback, (Datum) 0);
+ CacheRegisterSyscacheCallback(USERMAPPINGOID,
+ pgfdw_inval_callback, (Datum) 0);
+ }
}
/* Set flag that we did GetConnection during the current transaction */
@@ -1102,6 +1110,13 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID);
+ /*
+ * Quick exit if the cache has been destroyed in
+ * disconnect_cached_connections.
+ */
+ if (!ConnectionHash)
+ return;
+
/* ConnectionHash must exist already, if we're registered */
hash_seq_init(&scan, ConnectionHash);
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
@@ -1470,3 +1485,168 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected. The cache can be destroyed when there are no active
+ * connections left.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ if (PG_NARGS() == 1)
+ {
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false);
+ }
+ else
+ result = disconnect_cached_connections(0, true);
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function destroys the cache when there are no active connections.
+ *
+ * This function returns true in the following cases if at least one connection
+ * is disconnected. Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+ bool is_in_use = false;
+ bool active_conn_exists = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+
+ /*
+ * If at least one active connection exists in the cache, ensure we
+ * don't destroy the cache.
+ */
+ if (entry->conn && !active_conn_exists)
+ active_conn_exists = true;
+ }
+
+ /*
+ * is_in_use flag would be set to true when there exists at least one
+ * connection that's being used in the current transaction.
+ */
+ if (all)
+ {
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection, others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not close any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+
+ /*
+ * Destroy the cache if we discarded all active connections i.e. if there
+ * is no single active connection, which we can know while scanning the
+ * cached entries in the above loop. Destroying the cache is better than to
+ * keep it in the memory with all inactive entries in it to save some
+ * memory. Cache can get initialized on the subsequent queries to foreign
+ * server.
+ */
+ if (!active_conn_exists)
+ {
+ hash_destroy(ConnectionHash);
+ ConnectionHash = NULL;
+ conn_cache_destroyed = true;
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1cad311436..33c61d8a14 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9112,3 +9112,96 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback2 | t
(1 row)
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Returns true as it closes loopback2 connection.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Returns false as there is no cached connection.
+SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issues a warning, returns false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect();
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Issues a warning and returns false as it can not close any connection in the
+-- cache.
+SELECT * FROM postgres_fdw_disconnect();
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+COMMIT;
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
+-- Returns an error as there is no foreign server with given name.
+SELECT * FROM postgres_fdw_disconnect('unknownserver');
+ERROR: foreign server "unknownserver" does not exist
+-- Closes loopback2 connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..63dff0f7a8 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index ebf6eb10a6..e9c3fd2d41 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2738,3 +2738,38 @@ COMMIT;
-- should not be output because they should be closed at the end of
-- the above transaction.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Returns true as it closes loopback2 connection.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+-- Returns false as there is no cached connection.
+SELECT * FROM postgres_fdw_disconnect();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issues a warning, returns false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect();
+-- Issues a warning and returns false as it can not close any connection in the
+-- cache.
+SELECT * FROM postgres_fdw_disconnect();
+COMMIT;
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Returns an error as there is no foreign server with given name.
+SELECT * FROM postgres_fdw_disconnect('unknownserver');
+-- Closes loopback2 connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 6a91926da8..4c5264aaa3 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -507,6 +507,50 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with the foreign server name as input,
+ it discards the unused open connection previously made to the foreign
+ server and returns <literal>true</literal>. If the open connection is
+ still being used in the current transaction, it is not discarded, instead
+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal>
+ is returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect() returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with no input argument, it discards all
+ the unused open connections that are previously made to the foreign
+ servers and returns <literal>true</literal>. If there is any open
+ connection that is still being used in the current transaction, then a
+ warning is issued. <literal>false</literal> is returned when no open
+ connection is discarded or there are no open connections at all. Example
+ usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -522,6 +566,20 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v12-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v12-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 899be02dbf203ac31b7dff97dba65c3273c40bd0 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 18:13:04 +0530
Subject: [PATCH v12] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 ++++++++++++++++++-
6 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 1e93a84aaa..88943361c6 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -814,6 +814,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -824,6 +825,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -958,16 +961,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 33c61d8a14..e5e5018769 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9205,3 +9205,46 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-------------+-------
(0 rows)
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index e9c3fd2d41..8f054690c4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2773,3 +2773,23 @@ SELECT * FROM postgres_fdw_disconnect();
-- List all the existing cached connections. No connection exists, so NULL
-- should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 4c5264aaa3..0ee1f6bd87 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -555,6 +555,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -573,12 +609,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v12-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v12-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From f89d2b280593e2dc16bcfbf7dd64db0f47eb4ea8 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 18:26:36 +0530
Subject: [PATCH v12] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 19 ++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 88943361c6..35bafd1287 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -266,6 +270,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -290,6 +303,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -961,15 +975,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e5e5018769..40329f9b74 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8923,7 +8923,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9248,3 +9248,45 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8f054690c4..ded8805763 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2793,3 +2793,22 @@ SET postgres_fdw.keep_connections TO on;
SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 0ee1f6bd87..fca19eb653 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -578,6 +607,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -621,6 +658,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/18 23:14, Bharath Rupireddy wrote:
On Mon, Jan 18, 2021 at 11:44 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:I will post patches for the other function postgres_fdw_disconnect,
GUC and server level option later.Thanks!
Attaching v12 patch set. 0001 is for postgres_fdw_disconnect()
function, 0002 is for keep_connections GUC and 0003 is for
keep_connection server level option.
Thanks!
Please review it further.
+ server = GetForeignServerByName(servername, true);
+
+ if (!server)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
+ errmsg("foreign server \"%s\" does not exist", servername)));
ISTM we can simplify this code as follows.
server = GetForeignServerByName(servername, false);
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
When the server name is specified, even if its connection is successfully
closed, postgres_fdw_disconnect() scans through all the entries to check
whether there are active connections. But if "result" is true and
active_conn_exists is true, we can get out of this loop to avoid unnecessary
scans.
+ /*
+ * Destroy the cache if we discarded all active connections i.e. if there
+ * is no single active connection, which we can know while scanning the
+ * cached entries in the above loop. Destroying the cache is better than to
+ * keep it in the memory with all inactive entries in it to save some
+ * memory. Cache can get initialized on the subsequent queries to foreign
+ * server.
How much memory is assumed to be saved by destroying the cache in
many cases? I'm not sure if it's really worth destroying the cache to save
the memory.
+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal>
+ is returned when there are no open connections. When there are some open
+ connections, but there is no connection for the given foreign server,
+ then <literal>false</literal> is returned. When no foreign server exists
+ with the given name, an error is emitted. Example usage of the function:
When a non-existent server name is specified, postgres_fdw_disconnect()
emits an error if there is at least one open connection, but just returns
false otherwise. At least for me, this behavior looks inconsitent and strange.
In that case, IMO the function always should emit an error.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/01/18 22:03, Bharath Rupireddy wrote:
On Mon, Jan 18, 2021 at 6:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
+1 to add it after "dropped (Note ........)", how about as follows
with slight changes?dropped (Note that server name of an invalid connection can be NULL if
the server is dropped), and then such .....Yes, I like this one. One question is; "should" or "is" is better than
"can" in this case because the server name of invalid connection is
always NULL when its server is dropped?I think "dropped (Note that server name of an invalid connection will
be NULL if the server is dropped), and then such ....."
Sounds good to me. So patch attached.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attachments:
0001-doc-Add-note-about-the-server-name-of-postgres_fdw_g.patchtext/plain; charset=UTF-8; name=0001-doc-Add-note-about-the-server-name-of-postgres_fdw_g.patch; x-mac-creator=0; x-mac-type=0Download
From cbe84856899f23a810fe4d42cf1cd5e46b3d6870 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 19 Jan 2021 00:56:10 +0900
Subject: [PATCH] doc: Add note about the server name of
postgres_fdw_get_connections() returns.
Previously the document didn't mention the case where
postgres_fdw_get_connections() returns NULL in server_name column.
Users might be confused about why NULL was returned.
This commit adds the note that, in postgres_fdw_get_connections(),
the server name of an invalid connection will be NULL if the server is dropped.
Suggested-by: Zhijie Hou
Author: Bharath Rupireddy
Reviewed-by: Fujii Masao
Discussion: https://postgr.es/m/e7ddd14e96444fce88e47a709c196537@G08CNEXMBPEKD05.g08.fujitsu.local
---
doc/src/sgml/postgres-fdw.sgml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 6a91926da8..9adc8d12a9 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -493,7 +493,9 @@ OPTIONS (ADD password_required 'false');
each connection is valid or not. <literal>false</literal> is returned
if the foreign server connection is used in the current local
transaction but its foreign server or user mapping is changed or
- dropped, and then such invalid connection will be closed at
+ dropped (Note that server name of an invalid connection will be
+ <literal>NULL</literal> if the server is dropped),
+ and then such invalid connection will be closed at
the end of that transaction. <literal>true</literal> is returned
otherwise. If there are no open connections, no record is returned.
Example usage of the function:
--
2.27.0
+1 to add it after "dropped (Note ........)", how about as follows
with slight changes?dropped (Note that server name of an invalid connection can be NULL
if the server is dropped), and then such .....Yes, I like this one. One question is; "should" or "is" is better
than "can" in this case because the server name of invalid connection
is always NULL when its server is dropped?I think "dropped (Note that server name of an invalid connection will
be NULL if the server is dropped), and then such ....."Sounds good to me. So patch attached.
+1
Best regards,
houzj
On Mon, Jan 18, 2021 at 9:11 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching v12 patch set. 0001 is for postgres_fdw_disconnect()
function, 0002 is for keep_connections GUC and 0003 is for
keep_connection server level option.Thanks!
Please review it further.
+ server = GetForeignServerByName(servername, true); + + if (!server) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST), + errmsg("foreign server \"%s\" does not exist", servername)));ISTM we can simplify this code as follows.
server = GetForeignServerByName(servername, false);
Done.
+ hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))When the server name is specified, even if its connection is successfully
closed, postgres_fdw_disconnect() scans through all the entries to check
whether there are active connections. But if "result" is true and
active_conn_exists is true, we can get out of this loop to avoid unnecessary
scans.
My initial thought was that it's possible to have two entries with the
same foreign server name but with different user mappings, looks like
it's not possible. I tried associating a foreign server with two
different user mappings [1]postgres=# select * from pg_user_mappings ; umid | srvid | srvname | umuser | usename | umoptions -------+-------+-----------+--------+---------+----------- 16395 | 16394 | loopback1 | 10 | bharath | -----> cache entry is initially made with this user mapping. 16399 | 16394 | loopback1 | 0 | public | -----> if the above user mapping is dropped, then the cache entry is made with this user mapping., then the cache entry is getting
associated initially with the user mapping that comes first in the
pg_user_mappings, if this user mapping is dropped then the cache entry
gets invalidated, so next time the second user mapping is used.
Since there's no way we can have two cache entries with the same
foreign server name, we can get out of the loop when we find the cache
entry match with the given server. I made the changes.
[1]: postgres=# select * from pg_user_mappings ; umid | srvid | srvname | umuser | usename | umoptions -------+-------+-----------+--------+---------+----------- 16395 | 16394 | loopback1 | 10 | bharath | -----> cache entry is initially made with this user mapping. 16399 | 16394 | loopback1 | 0 | public | -----> if the above user mapping is dropped, then the cache entry is made with this user mapping.
postgres=# select * from pg_user_mappings ;
umid | srvid | srvname | umuser | usename | umoptions
-------+-------+-----------+--------+---------+-----------
16395 | 16394 | loopback1 | 10 | bharath | -----> cache entry
is initially made with this user mapping.
16399 | 16394 | loopback1 | 0 | public | -----> if the
above user mapping is dropped, then the cache entry is made with this
user mapping.
+ /* + * Destroy the cache if we discarded all active connections i.e. if there + * is no single active connection, which we can know while scanning the + * cached entries in the above loop. Destroying the cache is better than to + * keep it in the memory with all inactive entries in it to save some + * memory. Cache can get initialized on the subsequent queries to foreign + * server.How much memory is assumed to be saved by destroying the cache in
many cases? I'm not sure if it's really worth destroying the cache to save
the memory.
I removed the cache destroying code, if somebody complains in
future(after the feature commit), we can really revisit then.
+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal> + is returned when there are no open connections. When there are some open + connections, but there is no connection for the given foreign server, + then <literal>false</literal> is returned. When no foreign server exists + with the given name, an error is emitted. Example usage of the function:When a non-existent server name is specified, postgres_fdw_disconnect()
emits an error if there is at least one open connection, but just returns
false otherwise. At least for me, this behavior looks inconsitent and strange.
In that case, IMO the function always should emit an error.
Done.
Attaching v13 patch set, please review it further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v13-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/x-patch; name=v13-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From 0731c6244ac228818916d62cc51ea1434178c5be Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Tue, 19 Jan 2021 08:23:55 +0530
Subject: [PATCH v13] postgres_fdw function to discard cached connections
This patch introduces a new function postgres_fdw_disconnect().
When called with a foreign server name, it discards the associated
connection with the server. When called without any argument, it
discards all the existing cached connections.
---
contrib/postgres_fdw/connection.c | 147 ++++++++++++++++++
.../postgres_fdw/expected/postgres_fdw.out | 93 +++++++++++
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 35 +++++
doc/src/sgml/postgres-fdw.sgml | 59 +++++++
5 files changed, 344 insertions(+)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..287a047c80 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -80,6 +80,7 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +103,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1470,3 +1472,148 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect cached connections.
+ *
+ * If server name is provided as input, this function disconnects the
+ * associated cached connection. Otherwise all the cached connections are
+ * disconnected.
+ *
+ * This function returns false if the cache doesn't exist.
+ * When the cache exists:
+ * 1) If the server name is provided, it first checks whether the foreign
+ * server exists, if not, an error is emitted. Otherwise it disconnects the
+ * associated connection when it's not being used in current transaction
+ * and returns true. If it's in use, then issues a warning and returns
+ * false.
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections. If all the connections are not being used, then it
+ * disconnects them and returns true. If all the connections are being
+ * used, then it issues a warning and returns false. If at least one
+ * connection is closed and others are in use, then issues a warning and
+ * returns true.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (PG_NARGS() == 1)
+ {
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, false);
+
+ if (!ConnectionHash)
+ PG_RETURN_BOOL(result);
+
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false);
+ }
+ else if (PG_NARGS() == 0 && ConnectionHash)
+ result = disconnect_cached_connections(0, true);
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function returns true if at least one connection is disconnected.
+ * Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+ bool is_in_use = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ if (entry->xact_depth > 0)
+ is_in_use = true;
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+
+ /*
+ * For the given server, if we closed connection or it is still in
+ * use, then no need of scanning the cache further.
+ */
+ if (entry->server_hashvalue == hashvalue && (is_in_use ||
+ result))
+ {
+ hash_seq_term(&scan);
+ break;
+ }
+ }
+ }
+
+ /*
+ * is_in_use flag would be set to true when there exists at least one
+ * connection that's being used in the current transaction.
+ */
+ if (all)
+ {
+ /*
+ * Check if some or all of the connections are in use i.e.
+ * entry->xact_depth > 0. Since we can not close them, so inform user.
+ */
+ if (is_in_use)
+ {
+ if (result)
+ {
+ /* We closed at least one connection, others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
+ else
+ {
+ /* We did not close any connection, all are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close any connection because they are still in use")));
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Check if the connection associated with the given foreign server is
+ * in use i.e. entry->xact_depth > 0. Since we can not close it, so
+ * error out.
+ */
+ if (is_in_use)
+ ereport(WARNING,
+ (errmsg("cannot close the connection because it is still in use")));
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1cad311436..b478a8382d 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9112,3 +9112,96 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback2 | t
(1 row)
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Returns true as it closes loopback2 connection.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Returns false as there is no cached connection.
+SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issues a warning, returns false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+WARNING: cannot close the connection because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect();
+WARNING: cannot close all connections because some of them are still in use
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Issues a warning and returns false as it can not close any connection in the
+-- cache.
+SELECT * FROM postgres_fdw_disconnect();
+WARNING: cannot close any connection because they are still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+COMMIT;
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
+-- Returns an error as there is no foreign server with given name.
+SELECT * FROM postgres_fdw_disconnect('unknownserver');
+ERROR: server "unknownserver" does not exist
+-- Closes loopback2 connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..63dff0f7a8 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index ebf6eb10a6..e9c3fd2d41 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2738,3 +2738,38 @@ COMMIT;
-- should not be output because they should be closed at the end of
-- the above transaction.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Returns true as it closes loopback2 connection.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+-- Returns false as there is no cached connection.
+SELECT * FROM postgres_fdw_disconnect();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issues a warning, returns false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect();
+-- Issues a warning and returns false as it can not close any connection in the
+-- cache.
+SELECT * FROM postgres_fdw_disconnect();
+COMMIT;
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Returns an error as there is no foreign server with given name.
+SELECT * FROM postgres_fdw_disconnect('unknownserver');
+-- Closes loopback2 connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 6a91926da8..c05d29a996 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -507,6 +507,51 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in local session with foreign server name as input, it
+ discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still
+ being used in current transaction, it is not discarded, instead a warning
+ is issued and <literal>false</literal> is returned. When no foreign
+ server exists with the given name, an error is emitted. <literal>false</literal>
+ is returned when there are no open connections at all. When there are
+ some open connections, but there is no open connection for the given
+ foreign server, then <literal>false</literal> is returned. Example usage
+ of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect() returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with no input argument, it discards all
+ the unused open connections that are previously made to the foreign
+ servers and returns <literal>true</literal>. If there is any open
+ connection that is still being used in the current transaction, then a
+ warning is issued. <literal>false</literal> is returned when no open
+ connection is discarded or there are no open connections at all. Example
+ usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -522,6 +567,20 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v13-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/x-patch; name=v13-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 899be02dbf203ac31b7dff97dba65c3273c40bd0 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 18:13:04 +0530
Subject: [PATCH v13] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 ++++++++++++++++++-
6 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 1e93a84aaa..88943361c6 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -814,6 +814,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -824,6 +825,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -958,16 +961,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 33c61d8a14..e5e5018769 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9205,3 +9205,46 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-------------+-------
(0 rows)
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index e9c3fd2d41..8f054690c4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2773,3 +2773,23 @@ SELECT * FROM postgres_fdw_disconnect();
-- List all the existing cached connections. No connection exists, so NULL
-- should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 4c5264aaa3..0ee1f6bd87 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -555,6 +555,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -573,12 +609,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect()</function> to discard all the
connections or <function>postgres_fdw_disconnect(text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v13-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/x-patch; name=v13-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From f89d2b280593e2dc16bcfbf7dd64db0f47eb4ea8 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 18 Jan 2021 18:26:36 +0530
Subject: [PATCH v13] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 19 ++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 88943361c6..35bafd1287 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -266,6 +270,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -290,6 +303,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -961,15 +975,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e5e5018769..40329f9b74 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8923,7 +8923,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9248,3 +9248,45 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8f054690c4..ded8805763 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2793,3 +2793,22 @@ SET postgres_fdw.keep_connections TO on;
SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect();
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 0ee1f6bd87..fca19eb653 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -578,6 +607,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -621,6 +658,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/19 9:53, Hou, Zhijie wrote:
+1 to add it after "dropped (Note ........)", how about as follows
with slight changes?dropped (Note that server name of an invalid connection can be NULL
if the server is dropped), and then such .....Yes, I like this one. One question is; "should" or "is" is better
than "can" in this case because the server name of invalid connection
is always NULL when its server is dropped?I think "dropped (Note that server name of an invalid connection will
be NULL if the server is dropped), and then such ....."Sounds good to me. So patch attached.
+1
Thanks! I pushed the patch.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/01/19 12:09, Bharath Rupireddy wrote:
On Mon, Jan 18, 2021 at 9:11 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching v12 patch set. 0001 is for postgres_fdw_disconnect()
function, 0002 is for keep_connections GUC and 0003 is for
keep_connection server level option.Thanks!
Please review it further.
+ server = GetForeignServerByName(servername, true); + + if (!server) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST), + errmsg("foreign server \"%s\" does not exist", servername)));ISTM we can simplify this code as follows.
server = GetForeignServerByName(servername, false);
Done.
+ hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))When the server name is specified, even if its connection is successfully
closed, postgres_fdw_disconnect() scans through all the entries to check
whether there are active connections. But if "result" is true and
active_conn_exists is true, we can get out of this loop to avoid unnecessary
scans.My initial thought was that it's possible to have two entries with the
same foreign server name but with different user mappings, looks like
it's not possible. I tried associating a foreign server with two
different user mappings [1], then the cache entry is getting
associated initially with the user mapping that comes first in the
pg_user_mappings, if this user mapping is dropped then the cache entry
gets invalidated, so next time the second user mapping is used.Since there's no way we can have two cache entries with the same
foreign server name, we can get out of the loop when we find the cache
entry match with the given server. I made the changes.
So, furthermore, we can use hash_search() to find the target cached
connection, instead of using hash_seq_search(), when the server name
is given. This would simplify the code a bit more? Of course,
hash_seq_search() is necessary when closing all the connections, though.
[1]
postgres=# select * from pg_user_mappings ;
umid | srvid | srvname | umuser | usename | umoptions
-------+-------+-----------+--------+---------+-----------
16395 | 16394 | loopback1 | 10 | bharath | -----> cache entry
is initially made with this user mapping.
16399 | 16394 | loopback1 | 0 | public | -----> if the
above user mapping is dropped, then the cache entry is made with this
user mapping.+ /* + * Destroy the cache if we discarded all active connections i.e. if there + * is no single active connection, which we can know while scanning the + * cached entries in the above loop. Destroying the cache is better than to + * keep it in the memory with all inactive entries in it to save some + * memory. Cache can get initialized on the subsequent queries to foreign + * server.How much memory is assumed to be saved by destroying the cache in
many cases? I'm not sure if it's really worth destroying the cache to save
the memory.I removed the cache destroying code, if somebody complains in
future(after the feature commit), we can really revisit then.+ a warning is issued and <literal>false</literal> is returned. <literal>false</literal> + is returned when there are no open connections. When there are some open + connections, but there is no connection for the given foreign server, + then <literal>false</literal> is returned. When no foreign server exists + with the given name, an error is emitted. Example usage of the function:When a non-existent server name is specified, postgres_fdw_disconnect()
emits an error if there is at least one open connection, but just returns
false otherwise. At least for me, this behavior looks inconsitent and strange.
In that case, IMO the function always should emit an error.Done.
Attaching v13 patch set, please review it further.
Thanks!
+ * 2) If no input argument is provided, then it tries to disconnect all the
+ * connections.
I'm concerned that users can easily forget to specify the argument and
accidentally discard all the connections. So, IMO, to alleviate this situation,
what about changing the function name (only when closing all the connections)
to something postgres_fdw_disconnect_all(), like we have
pg_advisory_unlock_all() against pg_advisory_unlock()?
+ if (result)
+ {
+ /* We closed at least one connection, others are in use. */
+ ereport(WARNING,
+ (errmsg("cannot close all connections because some of them are still in use")));
+ }
Sorry if this was already discussed upthread. Isn't it more helpful to
emit a warning for every connections that fail to be closed? For example,
WARNING: cannot close connection for server "loopback1" because it is still in use
WARNING: cannot close connection for server "loopback2" because it is still in use
WARNING: cannot close connection for server "loopback3" because it is still in use
...
This enables us to identify easily which server connections cannot be
closed for now.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Wed, Jan 20, 2021 at 11:53 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
So, furthermore, we can use hash_search() to find the target cached
connection, instead of using hash_seq_search(), when the server name
is given. This would simplify the code a bit more? Of course,
hash_seq_search() is necessary when closing all the connections, though.
Note that the cache entry key is user mapping oid and to use
hash_search() we need the user mapping oid. But in
postgres_fdw_disconnect we can get server oid and we can also get user
mapping id using GetUserMapping, but it requires
GetUserId()/CurrentUserId as an input, I doubt we will have problems
if CurrentUserId is changed somehow with the change of current user in
the session. And user mapping may be dropped but still the connection
can exist if it's in use, in that case GetUserMapping fails in cache
lookup.
And yes, disconnecting all connections requires hash_seq_search().
Keeping above in mind, I feel we can do hash_seq_search(), as we do
currently, even when the server name is given as input. This way, we
don't need to bother much on the above points.
Thoughts?
+ * 2) If no input argument is provided, then it tries to disconnect all the + * connections.I'm concerned that users can easily forget to specify the argument and
accidentally discard all the connections. So, IMO, to alleviate this situation,
what about changing the function name (only when closing all the connections)
to something postgres_fdw_disconnect_all(), like we have
pg_advisory_unlock_all() against pg_advisory_unlock()?
+1. We will have two functions postgres_fdw_disconnect(server name),
postgres_fdw_disconnect_all.
+ if (result) + { + /* We closed at least one connection, others are in use. */ + ereport(WARNING, + (errmsg("cannot close all connections because some of them are still in use"))); + }Sorry if this was already discussed upthread. Isn't it more helpful to
emit a warning for every connections that fail to be closed? For example,WARNING: cannot close connection for server "loopback1" because it is still in use
WARNING: cannot close connection for server "loopback2" because it is still in use
WARNING: cannot close connection for server "loopback3" because it is still in use
...This enables us to identify easily which server connections cannot be
closed for now.
+1. Looks like pg_advisory_unlock is doing that. Given the fact that
still in use connections are possible only in explicit txns, we might
not have many still in use connections in the real world use case, so
I'm okay to change that way.
I will address all these comments and post an updated patch set soon.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/20 17:41, Bharath Rupireddy wrote:
On Wed, Jan 20, 2021 at 11:53 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:So, furthermore, we can use hash_search() to find the target cached
connection, instead of using hash_seq_search(), when the server name
is given. This would simplify the code a bit more? Of course,
hash_seq_search() is necessary when closing all the connections, though.Note that the cache entry key is user mapping oid and to use
hash_search() we need the user mapping oid. But in
postgres_fdw_disconnect we can get server oid and we can also get user
mapping id using GetUserMapping, but it requires
GetUserId()/CurrentUserId as an input, I doubt we will have problems
if CurrentUserId is changed somehow with the change of current user in
the session. And user mapping may be dropped but still the connection
can exist if it's in use, in that case GetUserMapping fails in cache
lookup.And yes, disconnecting all connections requires hash_seq_search().
Keeping above in mind, I feel we can do hash_seq_search(), as we do
currently, even when the server name is given as input. This way, we
don't need to bother much on the above points.Thoughts?
Thanks for explaining this! You're right. I'd withdraw my suggestion.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Wed, Jan 20, 2021 at 3:24 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Keeping above in mind, I feel we can do hash_seq_search(), as we do
currently, even when the server name is given as input. This way, we
don't need to bother much on the above points.Thoughts?
Thanks for explaining this! You're right. I'd withdraw my suggestion.
Attaching v14 patch set with review comments addressed. Please review
it further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v14-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/octet-stream; name=v14-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From 4cdb6f258e6f2ff65f19f5ed893a332d1f80224f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 20 Jan 2021 15:33:52 +0530
Subject: [PATCH v14] postgres_fdw function to discard cached connections
This patch introduces two new functions postgres_fdw_disconnect()
and postgres_fdw_disconnect_all() to discard cached connection
for a given foreign server or discard all cached connections
respectively.
---
contrib/postgres_fdw/connection.c | 124 ++++++++++++++++++
.../postgres_fdw/expected/postgres_fdw.out | 102 ++++++++++++++
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 37 ++++++
doc/src/sgml/postgres-fdw.sgml | 59 +++++++++
5 files changed, 332 insertions(+)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..707cfa9d84 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -80,6 +80,8 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect_all);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1470,3 +1473,124 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect cached foreign server connection.
+ *
+ * This function throws an error when there is no foreign server with the given
+ * name.
+ *
+ * It checks if the cache has a connection for the given foreign server that's
+ * not being used within current transaction, then returns true. If the
+ * connection is in use, then it emits a warning and returns false.
+ *
+ * It returns false, if the cache doesn't exit.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+ bool result = false;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, false);
+
+ if (ConnectionHash)
+ {
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+ result = disconnect_cached_connections(hashvalue, false);
+ }
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Disconnect all cached connections.
+ *
+ * This function scans all the cache entries, closes connections that are not
+ * being used within current transaction. It emits warning for each connection
+ * that's in use.
+ *
+ * It returns true, if it closes at least one connection, otherwise false.
+ *
+ * It returns false, if the cache doesn't exit.
+ */
+Datum
+postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
+{
+ bool result = false;
+
+ if (ConnectionHash)
+ result = disconnect_cached_connections(0, true);
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * Workhorse to disconnect the cached connections.
+ *
+ * This function tries to disconnect the connections only when they are not in
+ * use in the current transaction.
+ *
+ * If all is true, all the cached connections that are not being used in the
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
+ *
+ * This function returns true if at least one connection is disconnected.
+ * Otherwise it returns false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+
+ /* We are here only when ConnectionHash exists. */
+ Assert(ConnectionHash);
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ /* We cannot close connection that's in use, so issue a warning. */
+ if (entry->xact_depth > 0)
+ {
+ ForeignServer *server;
+
+ server = GetForeignServer(entry->serverid);
+ ereport(WARNING,
+ (errmsg("cannot close connection for server \"%s\" because it is still in use",
+ server->servername)));
+ }
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+
+ /*
+ * For the given server, if we closed connection or it is still in
+ * use, then no need of scanning the cache further.
+ */
+ if (entry->server_hashvalue == hashvalue &&
+ (entry->xact_depth > 0 || result))
+ {
+ hash_seq_term(&scan);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1cad311436..2dfb0c77e0 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9112,3 +9112,105 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback2 | t
(1 row)
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Returns true as it closes loopback2 connection.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- Returns false as there is no cached connection.
+SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issues a warning, returns false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Cannot close any connection as they are still in use so issues warnings for
+-- both loopback and loopback2 connections and returns false.
+SELECT * FROM postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+COMMIT;
+-- Returns an error as there is no foreign server with given name.
+SELECT * FROM postgres_fdw_disconnect('unknownserver');
+ERROR: server "unknownserver" does not exist
+-- Closes loopback, loopback2 connections and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..b8c0209036 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect_all ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect_all'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index ebf6eb10a6..f885072915 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2738,3 +2738,40 @@ COMMIT;
-- should not be output because they should be closed at the end of
-- the above transaction.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Returns true as it closes loopback2 connection.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+-- Returns false as there is no cached connection.
+SELECT * FROM postgres_fdw_disconnect_all();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issues a warning, returns false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT * FROM postgres_fdw_disconnect('loopback2');
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect_all();
+SELECT 1 FROM ft1 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Cannot close any connection as they are still in use so issues warnings for
+-- both loopback and loopback2 connections and returns false.
+SELECT * FROM postgres_fdw_disconnect_all();
+COMMIT;
+-- Returns an error as there is no foreign server with given name.
+SELECT * FROM postgres_fdw_disconnect('unknownserver');
+-- Closes loopback, loopback2 connections and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 9adc8d12a9..ca8dcc2b65 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -509,6 +509,51 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in local session with foreign server name as input, it
+ discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>. If the open connection is still
+ being used in current transaction, it is not discarded, instead a warning
+ is issued and <literal>false</literal> is returned. When no foreign
+ server exists with the given name, an error is emitted. <literal>false</literal>
+ is returned when there are no open connections at all. When there are
+ some open connections, but there is no open connection for the given
+ foreign server, then <literal>false</literal> is returned. Example usage
+ of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect_all() returns boolean</function></term>
+ <listitem>
+ <para>
+ When called in the local session with no input argument, it discards all
+ the unused open connections that are previously made to the foreign
+ servers and returns <literal>true</literal>. If there is any open
+ connection that is still being used in the current transaction, then a
+ warning is issued. <literal>false</literal> is returned when no open
+ connection is discarded or there are no open connections at all. Example
+ usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -524,6 +569,20 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect_all()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(IN servername text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v14-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v14-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 575f4f392ad257a0159c08050282b984ba412e02 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 20 Jan 2021 15:37:46 +0530
Subject: [PATCH v14] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 ++++++++++++++++++-
6 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 707cfa9d84..90da46a79f 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2dfb0c77e0..79a09f1392 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9214,3 +9214,46 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-------------+-------
(0 rows)
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2f2d4d171c..0c2ced501e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -302,6 +302,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -506,6 +508,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_o,
const PgFdwRelationInfo *fpinfo_i);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 19ea27a1bc..4d8dd4cd4a 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index f885072915..23b2417de7 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2775,3 +2775,23 @@ SELECT * FROM postgres_fdw_disconnect_all();
-- List all the existing cached connections. No connection exists, so NULL
-- should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index ca8dcc2b65..6a9942b666 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -558,6 +558,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -576,12 +612,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect_all()</function> to discard all the
connections or <function>postgres_fdw_disconnect(IN servername text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v14-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v14-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From 99f3faca25d33340f09a16b8dc8d56716847796d Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 20 Jan 2021 15:42:05 +0530
Subject: [PATCH v14] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 19 ++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 90da46a79f..84cc7fe0f4 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,15 +970,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 79a09f1392..c85e69eeec 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8923,7 +8923,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9257,3 +9257,45 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 1fec3c3eea..e8a144c06c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -213,6 +214,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 23b2417de7..f2cdbc6979 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2795,3 +2795,22 @@ SET postgres_fdw.keep_connections TO on;
SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 6a9942b666..5417ae48f8 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -477,6 +477,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -581,6 +610,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -624,6 +661,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/20 19:17, Bharath Rupireddy wrote:
On Wed, Jan 20, 2021 at 3:24 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Keeping above in mind, I feel we can do hash_seq_search(), as we do
currently, even when the server name is given as input. This way, we
don't need to bother much on the above points.Thoughts?
Thanks for explaining this! You're right. I'd withdraw my suggestion.
Attaching v14 patch set with review comments addressed. Please review
it further.
Thanks for updating the patch!
+ * It checks if the cache has a connection for the given foreign server that's
+ * not being used within current transaction, then returns true. If the
+ * connection is in use, then it emits a warning and returns false.
The comment also should mention the case where no open connection
for the given server is found? What about rewriting this to the following?
---------------------
If the cached connection for the given foreign server is found and has not
been used within current transaction yet, close the connection and return
true. Even when it's found, if it's already used, keep the connection, emit
a warning and return false. If it's not found, return false.
---------------------
+ * It returns true, if it closes at least one connection, otherwise false.
+ *
+ * It returns false, if the cache doesn't exit.
The above second comment looks redundant.
+ if (ConnectionHash)
+ result = disconnect_cached_connections(0, true);
Isn't it smarter to make disconnect_cached_connections() check
ConnectionHash and return false if it's NULL? If we do that, we can
simplify the code of postgres_fdw_disconnect() and _all().
+ * current transaction are disconnected. Otherwise, the unused entries with the
+ * given hashvalue are disconnected.
In the above second comment, a singular form should be used instead?
Because there must be no multiple entries with the given hashvalue.
+ server = GetForeignServer(entry->serverid);
This seems to cause an error "cache lookup failed"
if postgres_fdw_disconnect_all() is called when there is
a connection in use but its server is dropped. To avoid this error,
GetForeignServerExtended() with FSV_MISSING_OK should be used
instead, like postgres_fdw_get_connections() does?
+ if (entry->server_hashvalue == hashvalue &&
+ (entry->xact_depth > 0 || result))
+ {
+ hash_seq_term(&scan);
+ break;
entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?
+-- Closes loopback connection, returns true and issues a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT * FROM postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
After the above test, isn't it better to call postgres_fdw_get_connections()
to check that loopback is not output?
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
Just in the case please let me confirm that the order of these warning
messages is always stable?
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>
I think that "IN" of "IN servername text" is not necessary.
I'd like to replace "servername" with "server_name" because
postgres_fdw_get_connections() uses "server_name" as the output
column name.
+ <listitem>
+ <para>
+ When called in local session with foreign server name as input, it
+ discards the unused open connection previously made to the foreign server
+ and returns <literal>true</literal>.
"unused open connection" sounds confusing to me. What about the following?
---------------------
This function discards the open connection that postgres_fdw established
from the local session to the foriegn server with the given name if it's not
used in the current local transaction yet, and then returns true. If it's
already used, the function doesn't discard the connection, emits
a warning and then returns false. If there is no open connection to
the given foreign server, false is returned. If no foreign server with
the given name is found, an error is emitted. Example usage of the function:
---------------------
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
"SELECT postgres_fdw_disconnect('loopback1')" is more common?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Wed, Jan 20, 2021 at 6:58 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
+ * It checks if the cache has a connection for the given foreign server that's + * not being used within current transaction, then returns true. If the + * connection is in use, then it emits a warning and returns false.The comment also should mention the case where no open connection
for the given server is found? What about rewriting this to the following?---------------------
If the cached connection for the given foreign server is found and has not
been used within current transaction yet, close the connection and return
true. Even when it's found, if it's already used, keep the connection, emit
a warning and return false. If it's not found, return false.
---------------------
Done.
+ * It returns true, if it closes at least one connection, otherwise false. + * + * It returns false, if the cache doesn't exit.The above second comment looks redundant.
Yes. "otherwise false" means it.
+ if (ConnectionHash) + result = disconnect_cached_connections(0, true);Isn't it smarter to make disconnect_cached_connections() check
ConnectionHash and return false if it's NULL? If we do that, we can
simplify the code of postgres_fdw_disconnect() and _all().
Done.
+ * current transaction are disconnected. Otherwise, the unused entries with the + * given hashvalue are disconnected.In the above second comment, a singular form should be used instead?
Because there must be no multiple entries with the given hashvalue.
Rephrased the function comment a bit. Mentioned the _disconnect and
_disconnect_all in comments because we have said enough what each of
those two functions do.
+/*
+ * Workhorse to disconnect cached connections.
+ *
+ * This function disconnects either all unused connections when called from
+ * postgres_fdw_disconnect_all or a given foreign server unused connection when
+ * called from postgres_fdw_disconnect.
+ *
+ * This function returns true if at least one connection is disconnected,
+ * otherwise false.
+ */
+ server = GetForeignServer(entry->serverid);
This seems to cause an error "cache lookup failed"
if postgres_fdw_disconnect_all() is called when there is
a connection in use but its server is dropped. To avoid this error,
GetForeignServerExtended() with FSV_MISSING_OK should be used
instead, like postgres_fdw_get_connections() does?
+1. So, I changed it to GetForeignServerExtended, added an assertion
for invalidation just like postgres_fdw_get_connections. I also added
a test case for this, we now emit a slightly different warning for
this case alone that is (errmsg("cannot close dropped server
connection because it is still in use")));. This warning looks okay as
we cannot show any other server name in the ouput and we know that
this rare case can exist when someone drops the server in an explicit
transaction.
+ if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break;entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?
I don't think so entry->server_hashvalue can be zero, because
GetSysCacheHashValue1/CatalogCacheComputeHashValue will not return 0
as hash value. I have not seen someone comparing hashvalue with an
expectation that it has 0 value, for instance see if (hashvalue == 0
|| riinfo->oidHashValue == hashvalue).
Having if(!all) something like below there doesn't suffice because we
might call hash_seq_term, when some connection other than the given
foreign server connection is in use. Our intention to call
hash_seq_term is only when a given server is found and either it's in
use or is closed.
if (!all && (entry->xact_depth > 0 || result))
{
hash_seq_term(&scan);
break;
}
Given the above points, the existing check looks good to me.
+-- Closes loopback connection, returns true and issues a warning as loopback2 +-- connection is still in use and can not be closed. +SELECT * FROM postgres_fdw_disconnect_all(); +WARNING: cannot close connection for server "loopback2" because it is still in use + postgres_fdw_disconnect_all +----------------------------- + t +(1 row)After the above test, isn't it better to call postgres_fdw_get_connections()
to check that loopback is not output?
+1.
+WARNING: cannot close connection for server "loopback" because it is still in use +WARNING: cannot close connection for server "loopback2" because it is still in useJust in the case please let me confirm that the order of these warning
messages is always stable?
I think the order of the above warnings depends on how the connections
are stored in cache and we emit the warnings. Looks like new cached
connections are stored at the beginning of the cache always and the
warnings also will show up in that order i.e new entries to old
entries. I think it's stable and I didn't see cfbot complaining about
that on v14.
+ <varlistentry> + <term><function>postgres_fdw_disconnect(IN servername text) returns boolean</function></term>I think that "IN" of "IN servername text" is not necessary.
Done.
I'd like to replace "servername" with "server_name" because
postgres_fdw_get_connections() uses "server_name" as the output
column name.
Done.
+ <listitem> + <para> + When called in local session with foreign server name as input, it + discards the unused open connection previously made to the foreign server + and returns <literal>true</literal>."unused open connection" sounds confusing to me. What about the following?
---------------------
This function discards the open connection that postgres_fdw established
from the local session to the foriegn server with the given name if it's not
used in the current local transaction yet, and then returns true. If it's
already used, the function doesn't discard the connection, emits
a warning and then returns false. If there is no open connection to
the given foreign server, false is returned. If no foreign server with
the given name is found, an error is emitted. Example usage of the function:
---------------------
Done.
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
"SELECT postgres_fdw_disconnect('loopback1')" is more common?
Done.
Attaching v15 patch set. Please consider it for further review.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v15-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v15-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 4bc531d8fae93164431ad88fed0866243c94d4af Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 21 Jan 2021 08:26:37 +0530
Subject: [PATCH v15] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 ++++++++++++++++++-
6 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index be0ff43b4d..4e65a4e47a 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 27e6a8f141..ffd0af4931 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9411,3 +9411,46 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-------------+-------
(0 rows)
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9a31bbb86b..1698563a0e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -312,6 +312,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -527,6 +529,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_i);
static int get_batch_size_option(Relation rel);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 1f67b4d9fd..ceea46b304 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 4924cab74f..fc243ce972 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2891,3 +2891,23 @@ SELECT postgres_fdw_disconnect_all();
-- List all the existing cached connections. No connection exists, so NULL
-- should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 738f0cbc85..cb5513344e 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -569,6 +569,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -587,12 +623,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect_all()</function> to discard all the
connections or <function>postgres_fdw_disconnect(server_name text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v15-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v15-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From 3407b6a48699350ccf6008832ec1143ee1263618 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 21 Jan 2021 07:10:17 +0530
Subject: [PATCH v15] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 19 ++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 4e65a4e47a..da054d38c2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,15 +970,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ffd0af4931..7a0053b908 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8939,7 +8939,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9454,3 +9454,45 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..75126a84bc 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -227,6 +228,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index fc243ce972..8539e69895 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2911,3 +2911,22 @@ SET postgres_fdw.keep_connections TO on;
SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 7d9921094a..d385d253b6 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -490,6 +490,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -592,6 +621,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -635,6 +672,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
v15-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/octet-stream; name=v15-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From 2c0054327c65d34bb65228ec89f3409b71935061 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 21 Jan 2021 07:13:23 +0530
Subject: [PATCH v15] postgres_fdw function to discard cached connections
This patch introduces two new functions postgres_fdw_disconnect()
and postgres_fdw_disconnect_all() to discard cached connection
for a given foreign server or discard all cached connections
respectively.
---
contrib/postgres_fdw/connection.c | 129 ++++++++++++++
.../postgres_fdw/expected/postgres_fdw.out | 162 +++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 62 ++++++-
doc/src/sgml/postgres-fdw.sgml | 57 ++++++
5 files changed, 417 insertions(+), 3 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..be0ff43b4d 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -80,6 +80,8 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect_all);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(uint32 hashvalue, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1470,3 +1473,129 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect cached foreign server connection.
+ *
+ * This function throws an error when there is no foreign server with the given
+ * name.
+ *
+ * If cached connection for the given foreign server is found and has not been
+ * used within current transaction yet, close the connection and return true.
+ * Even when it's found, if it's already used, keep the connection, emit a
+ * warning and return false. If it's not found, return false.
+ *
+ * It returns false, if the cache doesn't exit.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ ForeignServer *server = NULL;
+ char *servername = NULL;
+ uint32 hashvalue;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, false);
+ hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(server->serverid));
+
+ PG_RETURN_BOOL(disconnect_cached_connections(hashvalue, false));
+}
+
+/*
+ * Disconnect all cached connections.
+ *
+ * This function scans all the cache entries, closes connections that are not
+ * being used within current transaction. It emits warning for each connection
+ * that's in use.
+ *
+ * It returns true, if it closes at least one connection, otherwise false.
+ */
+Datum
+postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(disconnect_cached_connections(0, true));
+}
+
+/*
+ * Workhorse to disconnect cached connections.
+ *
+ * This function disconnects either all unused connections when called from
+ * postgres_fdw_disconnect_all or a given foreign server unused connection when
+ * called from postgres_fdw_disconnect.
+ *
+ * This function returns true if at least one connection is disconnected,
+ * otherwise false.
+ */
+static bool
+disconnect_cached_connections(uint32 hashvalue, bool all)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+
+ if (!ConnectionHash)
+ return result;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
+ {
+ /* We cannot close connection that's in use, so issue a warning. */
+ if (entry->xact_depth > 0)
+ {
+ ForeignServer *server;
+
+ server = GetForeignServerExtended(entry->serverid,
+ FSV_MISSING_OK);
+
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated
+ * in pgfdw_inval_callback at the end of drop sever
+ * command. Note that this connection would not have been
+ * closed in pgfdw_inval_callback because it is still being
+ * used in the current explicit transaction. So, assert
+ * that here.
+ */
+ Assert(entry->invalidated);
+
+ ereport(WARNING,
+ (errmsg("cannot close dropped server connection because it is still in use")));
+ }
+ else
+ ereport(WARNING,
+ (errmsg("cannot close connection for server \"%s\" because it is still in use",
+ server->servername)));
+
+ }
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+
+ /*
+ * For the given server, if we closed connection or it is still in
+ * use, then no need of scanning the cache further.
+ */
+ if (entry->server_hashvalue == hashvalue &&
+ (entry->xact_depth > 0 || result))
+ {
+ hash_seq_term(&scan);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b4a04d2c14..27e6a8f141 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -17,7 +17,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
@@ -25,6 +28,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -140,6 +144,11 @@ CREATE FOREIGN TABLE ft7 (
c2 int NOT NULL,
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -211,7 +220,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
-(6 rows)
+ public | ft8 | loopback4 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9253,3 +9263,151 @@ SELECT COUNT(*) FROM batch_table;
-- Clean up
DROP TABLE batch_table CASCADE;
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Return false as connections are still in use, warnings are issued for both
+-- loopback and loopback2 connections.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+ loopback4 | t
+(3 rows)
+
+DROP SERVER loopback4 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server loopback4
+drop cascades to foreign table ft8
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close dropped server connection because it is still in use
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+ERROR: server "unknownserver" does not exist
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..b8c0209036 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect_all ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect_all'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 28b82f5f9d..4924cab74f 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -19,7 +19,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -28,6 +31,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -154,6 +158,12 @@ CREATE FOREIGN TABLE ft7 (
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2831,3 +2841,53 @@ SELECT COUNT(*) FROM batch_table;
-- Clean up
DROP TABLE batch_table CASCADE;
+
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Return false as connections are still in use, warnings are issued for both
+-- loopback and loopback2 connections.
+SELECT postgres_fdw_disconnect_all();
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER loopback4 CASCADE;
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index fb4c22ac69..738f0cbc85 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -522,6 +522,49 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(server_name text) returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards the open connection that <filename>postgres_fdw</filename>
+ established from the local session to the foreign server with the given
+ name if it's not used in the current local transaction yet, and then
+ returns <literal>true</literal>. If it's already used, the function
+ doesn't discard the connection, emits a warning and then returns <literal>false</literal>.
+ If there is no open connection to the given foreign server, <literal>false</literal>
+ is returned. If no foreign server with the given name is found, an error
+ is emitted. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect_all() returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards all the open connections that <filename>postgres_fdw</filename>
+ established from the local session to the foreign server if they are not
+ used in the current local transaction yet. It doesn't discard the
+ connections that's already used in the current local transaction and
+ emits a warning for each such connection. It returns <literal>true</literal>,
+ if it closes at least one connection, otherwise <literal>false</literal>.
+ Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -537,6 +580,20 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect_all()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(server_name text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
On 2021/01/21 12:00, Bharath Rupireddy wrote:
On Wed, Jan 20, 2021 at 6:58 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
+ * It checks if the cache has a connection for the given foreign server that's + * not being used within current transaction, then returns true. If the + * connection is in use, then it emits a warning and returns false.The comment also should mention the case where no open connection
for the given server is found? What about rewriting this to the following?---------------------
If the cached connection for the given foreign server is found and has not
been used within current transaction yet, close the connection and return
true. Even when it's found, if it's already used, keep the connection, emit
a warning and return false. If it's not found, return false.
---------------------Done.
+ * It returns true, if it closes at least one connection, otherwise false. + * + * It returns false, if the cache doesn't exit.The above second comment looks redundant.
Yes. "otherwise false" means it.
+ if (ConnectionHash) + result = disconnect_cached_connections(0, true);Isn't it smarter to make disconnect_cached_connections() check
ConnectionHash and return false if it's NULL? If we do that, we can
simplify the code of postgres_fdw_disconnect() and _all().Done.
+ * current transaction are disconnected. Otherwise, the unused entries with the + * given hashvalue are disconnected.In the above second comment, a singular form should be used instead?
Because there must be no multiple entries with the given hashvalue.Rephrased the function comment a bit. Mentioned the _disconnect and
_disconnect_all in comments because we have said enough what each of
those two functions do.+/* + * Workhorse to disconnect cached connections. + * + * This function disconnects either all unused connections when called from + * postgres_fdw_disconnect_all or a given foreign server unused connection when + * called from postgres_fdw_disconnect. + * + * This function returns true if at least one connection is disconnected, + * otherwise false. + */+ server = GetForeignServer(entry->serverid);
This seems to cause an error "cache lookup failed"
if postgres_fdw_disconnect_all() is called when there is
a connection in use but its server is dropped. To avoid this error,
GetForeignServerExtended() with FSV_MISSING_OK should be used
instead, like postgres_fdw_get_connections() does?+1. So, I changed it to GetForeignServerExtended, added an assertion
for invalidation just like postgres_fdw_get_connections. I also added
a test case for this, we now emit a slightly different warning for
this case alone that is (errmsg("cannot close dropped server
connection because it is still in use")));. This warning looks okay as
we cannot show any other server name in the ouput and we know that
this rare case can exist when someone drops the server in an explicit
transaction.+ if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break;entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?I don't think so entry->server_hashvalue can be zero, because
GetSysCacheHashValue1/CatalogCacheComputeHashValue will not return 0
as hash value. I have not seen someone comparing hashvalue with an
expectation that it has 0 value, for instance see if (hashvalue == 0
|| riinfo->oidHashValue == hashvalue).Having if(!all) something like below there doesn't suffice because we
might call hash_seq_term, when some connection other than the given
foreign server connection is in use.
No because we check the following condition before reaching that code. No?
+ if ((all || entry->server_hashvalue == hashvalue) &&
I was thinking that "(entry->xact_depth > 0 || result))" condition is not
necessary because "result" is set to true when xact_depth <= 0 and that
condition always indicates true.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attaching v15 patch set. Please consider it for further review.
Hi
I have some comments for the 0001 patch
In v15-0001-postgres_fdw-function-to-discard-cached-connecti
1.
+ If there is no open connection to the given foreign server, <literal>false</literal>
+ is returned. If no foreign server with the given name is found, an error
Do you think it's better add some testcases about:
call postgres_fdw_disconnect and postgres_fdw_disconnect_all when there is no open connection to the given foreign server
2.
+ /*
+ * For the given server, if we closed connection or it is still in
+ * use, then no need of scanning the cache further.
+ */
+ if (entry->server_hashvalue == hashvalue &&
+ (entry->xact_depth > 0 || result))
+ {
+ hash_seq_term(&scan);
+ break;
+ }
If I am not wrong, is the following condition always true ?
(entry->xact_depth > 0 || result)
Best regards,
houzj
On Thu, Jan 21, 2021 at 10:06 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
+ if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break;entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?I don't think so entry->server_hashvalue can be zero, because
GetSysCacheHashValue1/CatalogCacheComputeHashValue will not return 0
as hash value. I have not seen someone comparing hashvalue with an
expectation that it has 0 value, for instance see if (hashvalue == 0
|| riinfo->oidHashValue == hashvalue).Having if(!all) something like below there doesn't suffice because we
might call hash_seq_term, when some connection other than the given
foreign server connection is in use.No because we check the following condition before reaching that code. No?
+ if ((all || entry->server_hashvalue == hashvalue) &&
I was thinking that "(entry->xact_depth > 0 || result))" condition is not
necessary because "result" is set to true when xact_depth <= 0 and that
condition always indicates true.
I think that condition is too confusing. How about having a boolean
can_terminate_scan like below?
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
bool can_terminate_scan = false;
/*
* Either disconnect given or all the active and not in use cached
* connections.
*/
if ((all || entry->server_hashvalue == hashvalue) &&
entry->conn)
{
/* We cannot close connection that's in use, so issue a warning. */
if (entry->xact_depth > 0)
{
ForeignServer *server;
if (!all)
can_terminate_scan = true;
server = GetForeignServerExtended(entry->serverid,
FSV_MISSING_OK);
if (!server)
{
/*
* If the server has been dropped in the current explicit
* transaction, then this entry would have been invalidated
* in pgfdw_inval_callback at the end of drop sever
* command. Note that this connection would not have been
* closed in pgfdw_inval_callback because it is still being
* used in the current explicit transaction. So, assert
* that here.
*/
Assert(entry->invalidated);
ereport(WARNING,
(errmsg("cannot close dropped server
connection because it is still in use")));
}
else
ereport(WARNING,
(errmsg("cannot close connection for
server \"%s\" because it is still in use",
server->servername)));
}
else
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
result = true;
if (!all)
can_terminate_scan = true;
}
/*
* For the given server, if we closed connection or it is still in
* use, then no need of scanning the cache further.
*/
if (can_terminate_scan)
{
hash_seq_term(&scan);
break;
}
}
}
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Jan 21, 2021 at 11:15 AM Hou, Zhijie <houzj.fnst@cn.fujitsu.com> wrote:
Attaching v15 patch set. Please consider it for further review.
Hi
I have some comments for the 0001 patch
In v15-0001-postgres_fdw-function-to-discard-cached-connecti
1. + If there is no open connection to the given foreign server, <literal>false</literal> + is returned. If no foreign server with the given name is found, an errorDo you think it's better add some testcases about:
call postgres_fdw_disconnect and postgres_fdw_disconnect_all when there is no open connection to the given foreign server
Do you mean a test case where foreign server exists but
postgres_fdw_disconnect() returns false because there's no connection
for that server?
2. + /* + * For the given server, if we closed connection or it is still in + * use, then no need of scanning the cache further. + */ + if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break; + }If I am not wrong, is the following condition always true ?
(entry->xact_depth > 0 || result)
It's not always true. But it seems like it's too confusing, please
have a look at the upthread suggestion to change this with
can_terminate_scan boolean.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attaching v15 patch set. Please consider it for further review.
Hi
I have some comments for the 0001 patch
In v15-0001-postgres_fdw-function-to-discard-cached-connecti
1.
+ If there is no open connection to the given foreign server,<literal>false</literal>
+ is returned. If no foreign server with the given name is found, + an errorDo you think it's better add some testcases about:
call postgres_fdw_disconnect and postgres_fdw_disconnect_all
when there is no open connection to the given foreign serverDo you mean a test case where foreign server exists but
postgres_fdw_disconnect() returns false because there's no connection for
that server?
Yes, I read this from the doc, so I think it's better to test this.
2. + /* + * For the given server, if we closed connectionor it is still in
+ * use, then no need of scanning the cache
further.
+ */ + if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break; + }If I am not wrong, is the following condition always true ?
(entry->xact_depth > 0 || result)It's not always true. But it seems like it's too confusing, please have
a look at the upthread suggestion to change this with can_terminate_scan
boolean.
Thanks for the remind, I will look at that.
Best regards,
houzj
On 2021/01/21 14:46, Bharath Rupireddy wrote:
On Thu, Jan 21, 2021 at 10:06 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:+ if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break;entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?I don't think so entry->server_hashvalue can be zero, because
GetSysCacheHashValue1/CatalogCacheComputeHashValue will not return 0
as hash value. I have not seen someone comparing hashvalue with an
expectation that it has 0 value, for instance see if (hashvalue == 0
|| riinfo->oidHashValue == hashvalue).Having if(!all) something like below there doesn't suffice because we
might call hash_seq_term, when some connection other than the given
foreign server connection is in use.No because we check the following condition before reaching that code. No?
+ if ((all || entry->server_hashvalue == hashvalue) &&
I was thinking that "(entry->xact_depth > 0 || result))" condition is not
necessary because "result" is set to true when xact_depth <= 0 and that
condition always indicates true.I think that condition is too confusing. How about having a boolean
can_terminate_scan like below?
Thanks for thinking this. But at least for me, "if (!all)" looks not so confusing.
And the comment seems to explain why we can end the scan.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Thu, Jan 21, 2021 at 12:17 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/21 14:46, Bharath Rupireddy wrote:
On Thu, Jan 21, 2021 at 10:06 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:+ if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break;entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?I don't think so entry->server_hashvalue can be zero, because
GetSysCacheHashValue1/CatalogCacheComputeHashValue will not return 0
as hash value. I have not seen someone comparing hashvalue with an
expectation that it has 0 value, for instance see if (hashvalue == 0
|| riinfo->oidHashValue == hashvalue).Having if(!all) something like below there doesn't suffice because we
might call hash_seq_term, when some connection other than the given
foreign server connection is in use.No because we check the following condition before reaching that code. No?
+ if ((all || entry->server_hashvalue == hashvalue) &&
I was thinking that "(entry->xact_depth > 0 || result))" condition is not
necessary because "result" is set to true when xact_depth <= 0 and that
condition always indicates true.I think that condition is too confusing. How about having a boolean
can_terminate_scan like below?Thanks for thinking this. But at least for me, "if (!all)" looks not so confusing.
And the comment seems to explain why we can end the scan.
May I know if it's okay to have the boolean can_terminate_scan as shown in [1]/messages/by-id/CALj2ACVx0+iOsrAA-wXbo3RLAKqUoNvvEd7foJ0vLwOdu8XjXw@mail.gmail.com?
[1]: /messages/by-id/CALj2ACVx0+iOsrAA-wXbo3RLAKqUoNvvEd7foJ0vLwOdu8XjXw@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/21 16:16, Bharath Rupireddy wrote:
On Thu, Jan 21, 2021 at 12:17 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/21 14:46, Bharath Rupireddy wrote:
On Thu, Jan 21, 2021 at 10:06 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:+ if (entry->server_hashvalue == hashvalue && + (entry->xact_depth > 0 || result)) + { + hash_seq_term(&scan); + break;entry->server_hashvalue can be 0? If yes, since postgres_fdw_disconnect_all()
specifies 0 as hashvalue, ISTM that the above condition can be true
unexpectedly. Can we replace this condition with just "if (!all)"?I don't think so entry->server_hashvalue can be zero, because
GetSysCacheHashValue1/CatalogCacheComputeHashValue will not return 0
as hash value. I have not seen someone comparing hashvalue with an
expectation that it has 0 value, for instance see if (hashvalue == 0
|| riinfo->oidHashValue == hashvalue).Having if(!all) something like below there doesn't suffice because we
might call hash_seq_term, when some connection other than the given
foreign server connection is in use.No because we check the following condition before reaching that code. No?
+ if ((all || entry->server_hashvalue == hashvalue) &&
I was thinking that "(entry->xact_depth > 0 || result))" condition is not
necessary because "result" is set to true when xact_depth <= 0 and that
condition always indicates true.I think that condition is too confusing. How about having a boolean
can_terminate_scan like below?Thanks for thinking this. But at least for me, "if (!all)" looks not so confusing.
And the comment seems to explain why we can end the scan.May I know if it's okay to have the boolean can_terminate_scan as shown in [1]?
My opinion is to check "!all", but if others prefer using such boolean flag,
I'd withdraw my opinion.
+ if ((all || entry->server_hashvalue == hashvalue) &&
What about making disconnect_cached_connections() accept serverid instead
of hashvalue, and perform the above comparison based on serverid? That is,
I'm thinking "if (all || entry->serverid == serverid)". If we do that, we can
simplify postgres_fdw_disconnect() a bit more by getting rid of the calculation
of hashvalue.
+ if ((all || entry->server_hashvalue == hashvalue) &&
+ entry->conn)
I think that it's better to make the check of "entry->conn" independent
like other functions in postgres_fdw/connection.c. What about adding
the following check before the above?
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
continue;
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated
+ * in pgfdw_inval_callback at the end of drop sever
+ * command. Note that this connection would not have been
+ * closed in pgfdw_inval_callback because it is still being
+ * used in the current explicit transaction. So, assert
+ * that here.
+ */
+ Assert(entry->invalidated);
As this comment explains, even when the connection is used in the transaction,
its server can be dropped in the same transaction. The connection can remain
until the end of transaction even though its server has been already dropped.
I'm now wondering if this behavior itself is problematic and should be forbid.
Of course, this is separate topic from this patch, though..
BTW, my just idea for that is;
1. change postgres_fdw_get_connections() return also serverid and xact_depth.
2. make postgres_fdw define the event trigger on DROP SERVER command so that
an error is thrown if the connection to the server is still in use.
The event trigger function uses postgres_fdw_get_connections() to check
if the server connection is still in use or not.
I'm not sure if this just idea is really feasible or not, though...
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Thu, Jan 21, 2021 at 8:58 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
My opinion is to check "!all", but if others prefer using such boolean flag,
I'd withdraw my opinion.
I'm really sorry, actually if (!all) is enough there, my earlier
understanding was wrong.
+ if ((all || entry->server_hashvalue == hashvalue) &&
What about making disconnect_cached_connections() accept serverid instead
of hashvalue, and perform the above comparison based on serverid? That is,
I'm thinking "if (all || entry->serverid == serverid)". If we do that, we can
simplify postgres_fdw_disconnect() a bit more by getting rid of the calculation
of hashvalue.
That's a good idea. I missed this point. Thanks.
+ if ((all || entry->server_hashvalue == hashvalue) && + entry->conn)I think that it's better to make the check of "entry->conn" independent
like other functions in postgres_fdw/connection.c. What about adding
the following check before the above?/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
continue;
Done.
+ /* + * If the server has been dropped in the current explicit + * transaction, then this entry would have been invalidated + * in pgfdw_inval_callback at the end of drop sever + * command. Note that this connection would not have been + * closed in pgfdw_inval_callback because it is still being + * used in the current explicit transaction. So, assert + * that here. + */ + Assert(entry->invalidated);As this comment explains, even when the connection is used in the transaction,
its server can be dropped in the same transaction. The connection can remain
until the end of transaction even though its server has been already dropped.
I'm now wondering if this behavior itself is problematic and should be forbid.
Of course, this is separate topic from this patch, though..BTW, my just idea for that is;
1. change postgres_fdw_get_connections() return also serverid and xact_depth.
2. make postgres_fdw define the event trigger on DROP SERVER command so that
an error is thrown if the connection to the server is still in use.
The event trigger function uses postgres_fdw_get_connections() to check
if the server connection is still in use or not.I'm not sure if this just idea is really feasible or not, though...
I'm not quite sure if we can create such a dependency i.e. blocking
"drop foreign server" when at least one session has an in use cached
connection on it? What if a user wants to drop a server from one
session, all other sessions one after the other keep having in-use
connections related to that server, (though this use case sounds
impractical) will the drop server ever be successful? Since we can
have hundreds of sessions in real world postgres environment, I don't
know if it's a good idea to create such dependency.
As you suggested, this point can be discussed in a separate thread and
if any of the approaches proposed by you above is finalized we can
extend postgres_fdw_get_connections anytime.
Thoughts?
Attaching v16 patch set, addressing above review comments and also
added a test case suggested upthread that postgres_fdw_disconnect()
with existing server name returns false that is when the cache doesn't
have active connection.
Please review the v16 patch set further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v16-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/octet-stream; name=v16-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From 251f16246889005af516d3cc311f0e15310fe98f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 21 Jan 2021 21:38:04 +0530
Subject: [PATCH v16] postgres_fdw function to discard cached connections
This patch introduces two new functions postgres_fdw_disconnect()
and postgres_fdw_disconnect_all() to discard cached connection
for a given foreign server or discard all cached connections
respectively.
---
contrib/postgres_fdw/connection.c | 129 +++++++++++++
.../postgres_fdw/expected/postgres_fdw.out | 169 +++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 64 ++++++-
doc/src/sgml/postgres-fdw.sgml | 57 ++++++
5 files changed, 426 insertions(+), 3 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..b73703546c 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -80,6 +80,8 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect_all);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(Oid serverid, bool all);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1470,3 +1473,129 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect cached foreign server connection.
+ *
+ * This function throws an error when there is no foreign server with the given
+ * name.
+ *
+ * If cached connection for the given foreign server is found and has not been
+ * used within current transaction yet, close the connection and return true.
+ * Even when it's found, if it's already used, keep the connection, emit a
+ * warning and return false. If it's not found, return false.
+ *
+ * It returns false, if the cache doesn't exit.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ ForeignServer *server;
+ char *servername;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, false);
+
+ PG_RETURN_BOOL(disconnect_cached_connections(server->serverid, false));
+}
+
+/*
+ * Disconnect all cached connections.
+ *
+ * This function scans all the cache entries, closes connections that are not
+ * being used within current transaction. It emits warning for each connection
+ * that's in use.
+ *
+ * It returns true, if it closes at least one connection, otherwise false.
+ */
+Datum
+postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(disconnect_cached_connections(InvalidOid, true));
+}
+
+/*
+ * Workhorse to disconnect cached connections.
+ *
+ * This function disconnects either all unused connections when called from
+ * postgres_fdw_disconnect_all or a given foreign server unused connection when
+ * called from postgres_fdw_disconnect.
+ *
+ * This function returns true if at least one connection is disconnected,
+ * otherwise false.
+ */
+static bool
+disconnect_cached_connections(Oid serverid, bool all)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool result = false;
+
+ if (!ConnectionHash)
+ return result;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /* Ignore cache entry if no open connection right now. */
+ if (!entry->conn)
+ continue;
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if (all || (OidIsValid(serverid) && entry->serverid == serverid))
+ {
+ /* We cannot close connection that's in use, so issue a warning. */
+ if (entry->xact_depth > 0)
+ {
+ ForeignServer *server;
+
+ server = GetForeignServerExtended(entry->serverid,
+ FSV_MISSING_OK);
+
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated
+ * in pgfdw_inval_callback at the end of drop sever
+ * command. Note that this connection would not have been
+ * closed in pgfdw_inval_callback because it is still being
+ * used in the current explicit transaction. So, assert
+ * that here.
+ */
+ Assert(entry->invalidated);
+
+ ereport(WARNING,
+ (errmsg("cannot close dropped server connection because it is still in use")));
+ }
+ else
+ ereport(WARNING,
+ (errmsg("cannot close connection for server \"%s\" because it is still in use",
+ server->servername)));
+
+ }
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+
+ /*
+ * For the given server, if we closed connection or it is still in
+ * use, then no need of scanning the cache further. We do this
+ * because the cache can not have multiple cache entries for a
+ * single foreign server.
+ */
+ if (!all)
+ {
+ hash_seq_term(&scan);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b4a04d2c14..87e8624cef 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -17,7 +17,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
@@ -25,6 +28,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -140,6 +144,11 @@ CREATE FOREIGN TABLE ft7 (
c2 int NOT NULL,
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -211,7 +220,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
-(6 rows)
+ public | ft8 | loopback4 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9253,3 +9263,158 @@ SELECT COUNT(*) FROM batch_table;
-- Clean up
DROP TABLE batch_table CASCADE;
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Return false as connections are still in use, warnings are issued for both
+-- loopback and loopback2 connections.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+ loopback4 | t
+(3 rows)
+
+DROP SERVER loopback4 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server loopback4
+drop cascades to foreign table ft8
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close dropped server connection because it is still in use
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Return false as loopback2 connectin is closed already.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+ERROR: server "unknownserver" does not exist
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..b8c0209036 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect_all ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect_all'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 28b82f5f9d..2a688ccc04 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -19,7 +19,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -28,6 +31,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -154,6 +158,12 @@ CREATE FOREIGN TABLE ft7 (
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2831,3 +2841,55 @@ SELECT COUNT(*) FROM batch_table;
-- Clean up
DROP TABLE batch_table CASCADE;
+
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Return false as connections are still in use, warnings are issued for both
+-- loopback and loopback2 connections.
+SELECT postgres_fdw_disconnect_all();
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER loopback4 CASCADE;
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Return false as loopback2 connectin is closed already.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index fb4c22ac69..738f0cbc85 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -522,6 +522,49 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(server_name text) returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards the open connection that <filename>postgres_fdw</filename>
+ established from the local session to the foreign server with the given
+ name if it's not used in the current local transaction yet, and then
+ returns <literal>true</literal>. If it's already used, the function
+ doesn't discard the connection, emits a warning and then returns <literal>false</literal>.
+ If there is no open connection to the given foreign server, <literal>false</literal>
+ is returned. If no foreign server with the given name is found, an error
+ is emitted. Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect_all() returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards all the open connections that <filename>postgres_fdw</filename>
+ established from the local session to the foreign server if they are not
+ used in the current local transaction yet. It doesn't discard the
+ connections that's already used in the current local transaction and
+ emits a warning for each such connection. It returns <literal>true</literal>,
+ if it closes at least one connection, otherwise <literal>false</literal>.
+ Example usage of the function:
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -537,6 +580,20 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect_all()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(server_name text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v16-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v16-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 4bc531d8fae93164431ad88fed0866243c94d4af Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 21 Jan 2021 08:26:37 +0530
Subject: [PATCH v16] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 ++++++++++++++++++-
6 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index be0ff43b4d..4e65a4e47a 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 27e6a8f141..ffd0af4931 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9411,3 +9411,46 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-------------+-------
(0 rows)
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9a31bbb86b..1698563a0e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -312,6 +312,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -527,6 +529,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_i);
static int get_batch_size_option(Relation rel);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 1f67b4d9fd..ceea46b304 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 4924cab74f..fc243ce972 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2891,3 +2891,23 @@ SELECT postgres_fdw_disconnect_all();
-- List all the existing cached connections. No connection exists, so NULL
-- should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 738f0cbc85..cb5513344e 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -569,6 +569,42 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -587,12 +623,18 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect_all()</function> to discard all the
connections or <function>postgres_fdw_disconnect(server_name text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v16-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v16-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From 3407b6a48699350ccf6008832ec1143ee1263618 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 21 Jan 2021 07:10:17 +0530
Subject: [PATCH v16] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 44 ++++++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 19 ++++++++
doc/src/sgml/postgres-fdw.sgml | 43 ++++++++++++++++++
5 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 4e65a4e47a..da054d38c2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,15 +970,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ffd0af4931..7a0053b908 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8939,7 +8939,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9454,3 +9454,45 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..75126a84bc 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -227,6 +228,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index fc243ce972..8539e69895 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2911,3 +2911,22 @@ SET postgres_fdw.keep_connections TO on;
SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Closes loopback connection and returns true.
+SELECT * FROM postgres_fdw_disconnect_all();
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 7d9921094a..d385d253b6 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -490,6 +490,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -592,6 +621,14 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -635,6 +672,12 @@ postgres=# SELECT * FROM postgres_fdw_disconnect_all();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/22 1:17, Bharath Rupireddy wrote:
On Thu, Jan 21, 2021 at 8:58 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
My opinion is to check "!all", but if others prefer using such boolean flag,
I'd withdraw my opinion.I'm really sorry, actually if (!all) is enough there, my earlier
understanding was wrong.+ if ((all || entry->server_hashvalue == hashvalue) &&
What about making disconnect_cached_connections() accept serverid instead
of hashvalue, and perform the above comparison based on serverid? That is,
I'm thinking "if (all || entry->serverid == serverid)". If we do that, we can
simplify postgres_fdw_disconnect() a bit more by getting rid of the calculation
of hashvalue.That's a good idea. I missed this point. Thanks.
+ if ((all || entry->server_hashvalue == hashvalue) && + entry->conn)I think that it's better to make the check of "entry->conn" independent
like other functions in postgres_fdw/connection.c. What about adding
the following check before the above?/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
continue;Done.
+ /* + * If the server has been dropped in the current explicit + * transaction, then this entry would have been invalidated + * in pgfdw_inval_callback at the end of drop sever + * command. Note that this connection would not have been + * closed in pgfdw_inval_callback because it is still being + * used in the current explicit transaction. So, assert + * that here. + */ + Assert(entry->invalidated);As this comment explains, even when the connection is used in the transaction,
its server can be dropped in the same transaction. The connection can remain
until the end of transaction even though its server has been already dropped.
I'm now wondering if this behavior itself is problematic and should be forbid.
Of course, this is separate topic from this patch, though..BTW, my just idea for that is;
1. change postgres_fdw_get_connections() return also serverid and xact_depth.
2. make postgres_fdw define the event trigger on DROP SERVER command so that
an error is thrown if the connection to the server is still in use.
The event trigger function uses postgres_fdw_get_connections() to check
if the server connection is still in use or not.I'm not sure if this just idea is really feasible or not, though...
I'm not quite sure if we can create such a dependency i.e. blocking
"drop foreign server" when at least one session has an in use cached
connection on it?
Maybe my explanation was not clear... I was thinking to prevent the server whose connection is used *within the current transaction* from being dropped. IOW, I was thinking to forbid the drop of server if xact_depth of its connection is more than one. So one session can drop the server even when its connection is open in other session if it's not used within the transaction (i.e., xact_depth == 0).
BTW, for now, if the connection is used within the transaction, other session cannot drop the corresponding server because the transaction holds the lock on the relations that depend on the server. Only the session running that transaction can drop the server. This can cause the issue in discussion.
So, my just idea is to disallow even that session running the transaction to drop the server. This means that no session can drop the server while its connection is used within the transaction (xact_depth > 0).
What if a user wants to drop a server from one
session, all other sessions one after the other keep having in-use
connections related to that server, (though this use case sounds
impractical) will the drop server ever be successful? Since we can
have hundreds of sessions in real world postgres environment, I don't
know if it's a good idea to create such dependency.As you suggested, this point can be discussed in a separate thread and
if any of the approaches proposed by you above is finalized we can
extend postgres_fdw_get_connections anytime.Thoughts?
I will consider more before starting separate discussion!
Attaching v16 patch set, addressing above review comments and also
added a test case suggested upthread that postgres_fdw_disconnect()
with existing server name returns false that is when the cache doesn't
have active connection.Please review the v16 patch set further.
Thanks! Will review that later.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/01/22 3:29, Fujii Masao wrote:
On 2021/01/22 1:17, Bharath Rupireddy wrote:
On Thu, Jan 21, 2021 at 8:58 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
My opinion is to check "!all", but if others prefer using such boolean flag,
I'd withdraw my opinion.I'm really sorry, actually if (!all) is enough there, my earlier
understanding was wrong.+ if ((all || entry->server_hashvalue == hashvalue) &&
What about making disconnect_cached_connections() accept serverid instead
of hashvalue, and perform the above comparison based on serverid? That is,
I'm thinking "if (all || entry->serverid == serverid)". If we do that, we can
simplify postgres_fdw_disconnect() a bit more by getting rid of the calculation
of hashvalue.That's a good idea. I missed this point. Thanks.
+ if ((all || entry->server_hashvalue == hashvalue) && + entry->conn)I think that it's better to make the check of "entry->conn" independent
like other functions in postgres_fdw/connection.c. What about adding
the following check before the above?/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
continue;Done.
+ /* + * If the server has been dropped in the current explicit + * transaction, then this entry would have been invalidated + * in pgfdw_inval_callback at the end of drop sever + * command. Note that this connection would not have been + * closed in pgfdw_inval_callback because it is still being + * used in the current explicit transaction. So, assert + * that here. + */ + Assert(entry->invalidated);As this comment explains, even when the connection is used in the transaction,
its server can be dropped in the same transaction. The connection can remain
until the end of transaction even though its server has been already dropped.
I'm now wondering if this behavior itself is problematic and should be forbid.
Of course, this is separate topic from this patch, though..BTW, my just idea for that is;
1. change postgres_fdw_get_connections() return also serverid and xact_depth.
2. make postgres_fdw define the event trigger on DROP SERVER command so that
an error is thrown if the connection to the server is still in use.
The event trigger function uses postgres_fdw_get_connections() to check
if the server connection is still in use or not.I'm not sure if this just idea is really feasible or not, though...
I'm not quite sure if we can create such a dependency i.e. blocking
"drop foreign server" when at least one session has an in use cached
connection on it?Maybe my explanation was not clear... I was thinking to prevent the server whose connection is used *within the current transaction* from being dropped. IOW, I was thinking to forbid the drop of server if xact_depth of its connection is more than one. So one session can drop the server even when its connection is open in other session if it's not used within the transaction (i.e., xact_depth == 0).
BTW, for now, if the connection is used within the transaction, other session cannot drop the corresponding server because the transaction holds the lock on the relations that depend on the server. Only the session running that transaction can drop the server. This can cause the issue in discussion.
So, my just idea is to disallow even that session running the transaction to drop the server. This means that no session can drop the server while its connection is used within the transaction (xact_depth > 0).
What if a user wants to drop a server from one
session, all other sessions one after the other keep having in-use
connections related to that server, (though this use case sounds
impractical) will the drop server ever be successful? Since we can
have hundreds of sessions in real world postgres environment, I don't
know if it's a good idea to create such dependency.As you suggested, this point can be discussed in a separate thread and
if any of the approaches proposed by you above is finalized we can
extend postgres_fdw_get_connections anytime.Thoughts?
I will consider more before starting separate discussion!
Attaching v16 patch set, addressing above review comments and also
added a test case suggested upthread that postgres_fdw_disconnect()
with existing server name returns false that is when the cache doesn't
have active connection.Please review the v16 patch set further.
Thanks! Will review that later.
+ /*
+ * For the given server, if we closed connection or it is still in
+ * use, then no need of scanning the cache further. We do this
+ * because the cache can not have multiple cache entries for a
+ * single foreign server.
+ */
On second thought, ISTM that single foreign server can have multiple cache
entries. For example,
CREATE ROLE foo1 SUPERUSER;
CREATE ROLE foo2 SUPERUSER;
CREATE EXTENSION postgres_fdw;
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS (port '5432');
CREATE USER MAPPING FOR foo1 SERVER loopback OPTIONS (user 'postgres');
CREATE USER MAPPING FOR foo2 SERVER loopback OPTIONS (user 'postgres');
CREATE TABLE t (i int);
CREATE FOREIGN TABLE ft (i int) SERVER loopback OPTIONS (table_name 't');
SET SESSION AUTHORIZATION foo1;
SELECT * FROM ft;
SET SESSION AUTHORIZATION foo2;
SELECT * FROM ft;
Then you can see there are multiple open connections for the same server
as follows. So we need to scan all the entries even when the serverid is
specified.
SELECT * FROM postgres_fdw_get_connections();
server_name | valid
-------------+-------
loopback | t
loopback | t
(2 rows)
This means that user (even non-superuser) can disconnect the connection
established by another user (superuser), by using postgres_fdw_disconnect_all().
Is this really OK?
+ if (all || (OidIsValid(serverid) && entry->serverid == serverid))
+ {
I don't think that "OidIsValid(serverid)" condition is necessary here.
But you're just concerned about the case where the caller mistakenly
specifies invalid oid and all=false? One idea to avoid that inconsistent
combination of inputs is to change disconnect_cached_connections()
as follows.
-disconnect_cached_connections(Oid serverid, bool all)
+disconnect_cached_connections(Oid serverid)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
+ bool all = !OidIsValid(serverid);
+ * in pgfdw_inval_callback at the end of drop sever
Typo: "sever" should be "server".
+-- ===================================================================
+-- test postgres_fdw_disconnect function
+-- ===================================================================
This regression test is placed at the end of test file. But isn't it better
to place that just after the regression test "test connection invalidation
cases" because they are related?
+ <screen>
+postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
The tag <screen> should start from the beginning.
As I commented upthread, what about replacing the example query with
"SELECT postgres_fdw_disconnect('loopback1');" because it's more common?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 22, 2021 at 6:43 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Please review the v16 patch set further.
Thanks! Will review that later.
+ /* + * For the given server, if we closed connection or it is still in + * use, then no need of scanning the cache further. We do this + * because the cache can not have multiple cache entries for a + * single foreign server. + */On second thought, ISTM that single foreign server can have multiple cache
entries. For example,CREATE ROLE foo1 SUPERUSER;
CREATE ROLE foo2 SUPERUSER;
CREATE EXTENSION postgres_fdw;
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS (port '5432');
CREATE USER MAPPING FOR foo1 SERVER loopback OPTIONS (user 'postgres');
CREATE USER MAPPING FOR foo2 SERVER loopback OPTIONS (user 'postgres');
CREATE TABLE t (i int);
CREATE FOREIGN TABLE ft (i int) SERVER loopback OPTIONS (table_name 't');
SET SESSION AUTHORIZATION foo1;
SELECT * FROM ft;
SET SESSION AUTHORIZATION foo2;
SELECT * FROM ft;Then you can see there are multiple open connections for the same server
as follows. So we need to scan all the entries even when the serverid is
specified.SELECT * FROM postgres_fdw_get_connections();
server_name | valid
-------------+-------
loopback | t
loopback | t
(2 rows)
This is a great finding. Thanks a lot. I will remove
hash_seq_term(&scan); in disconnect_cached_connections and add this as
a test case for postgres_fdw_get_connections function, just to show
there can be multiple connections with a single server name.
This means that user (even non-superuser) can disconnect the connection
established by another user (superuser), by using postgres_fdw_disconnect_all().
Is this really OK?
Yeah, connections can be discarded by non-super users using
postgres_fdw_disconnect_all and postgres_fdw_disconnect. Given the
fact that a non-super user requires a password to access foreign
tables [1]SELECT * FROM ft1_nopw LIMIT 1; ERROR: password is required DETAIL: Non-superusers must provide a password in the user mapping., IMO a non-super user changing something related to a super
user makes no sense at all. If okay, we can have a check in
disconnect_cached_connections something like below:
+static bool
+disconnect_cached_connections(Oid serverid)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool all = !OidIsValid(serverid);
+ bool result = false;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to discard open connections")));
+
+ if (!ConnectionHash)
Having said that, it looks like dblink_disconnect doesn't perform
superuser checks.
Thoughts?
[1]: SELECT * FROM ft1_nopw LIMIT 1; ERROR: password is required DETAIL: Non-superusers must provide a password in the user mapping.
SELECT * FROM ft1_nopw LIMIT 1;
ERROR: password is required
DETAIL: Non-superusers must provide a password in the user mapping.
+ if (all || (OidIsValid(serverid) && entry->serverid == serverid)) + {I don't think that "OidIsValid(serverid)" condition is necessary here.
But you're just concerned about the case where the caller mistakenly
specifies invalid oid and all=false? One idea to avoid that inconsistent
combination of inputs is to change disconnect_cached_connections()
as follows.-disconnect_cached_connections(Oid serverid, bool all) +disconnect_cached_connections(Oid serverid) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; + bool all = !OidIsValid(serverid);
+1. Will change it.
+ * in pgfdw_inval_callback at the end of drop sever
Typo: "sever" should be "server".
+1. Will change it.
+-- =================================================================== +-- test postgres_fdw_disconnect function +-- ===================================================================This regression test is placed at the end of test file. But isn't it better
to place that just after the regression test "test connection invalidation
cases" because they are related?
+1. Will change it.
+ <screen> +postgres=# SELECT * FROM postgres_fdw_disconnect('loopback1'); + postgres_fdw_disconnectThe tag <screen> should start from the beginning.
+1. Will change it.
As I commented upthread, what about replacing the example query with
"SELECT postgres_fdw_disconnect('loopback1');" because it's more common?
Sorry, I forgot to check that in the documentation earlier. +1. Will change it.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/23 13:40, Bharath Rupireddy wrote:
On Fri, Jan 22, 2021 at 6:43 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Please review the v16 patch set further.
Thanks! Will review that later.
+ /* + * For the given server, if we closed connection or it is still in + * use, then no need of scanning the cache further. We do this + * because the cache can not have multiple cache entries for a + * single foreign server. + */On second thought, ISTM that single foreign server can have multiple cache
entries. For example,CREATE ROLE foo1 SUPERUSER;
CREATE ROLE foo2 SUPERUSER;
CREATE EXTENSION postgres_fdw;
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS (port '5432');
CREATE USER MAPPING FOR foo1 SERVER loopback OPTIONS (user 'postgres');
CREATE USER MAPPING FOR foo2 SERVER loopback OPTIONS (user 'postgres');
CREATE TABLE t (i int);
CREATE FOREIGN TABLE ft (i int) SERVER loopback OPTIONS (table_name 't');
SET SESSION AUTHORIZATION foo1;
SELECT * FROM ft;
SET SESSION AUTHORIZATION foo2;
SELECT * FROM ft;Then you can see there are multiple open connections for the same server
as follows. So we need to scan all the entries even when the serverid is
specified.SELECT * FROM postgres_fdw_get_connections();
server_name | valid
-------------+-------
loopback | t
loopback | t
(2 rows)This is a great finding. Thanks a lot. I will remove
hash_seq_term(&scan); in disconnect_cached_connections and add this as
a test case for postgres_fdw_get_connections function, just to show
there can be multiple connections with a single server name.This means that user (even non-superuser) can disconnect the connection
established by another user (superuser), by using postgres_fdw_disconnect_all().
Is this really OK?Yeah, connections can be discarded by non-super users using
postgres_fdw_disconnect_all and postgres_fdw_disconnect. Given the
fact that a non-super user requires a password to access foreign
tables [1], IMO a non-super user changing something related to a super
user makes no sense at all. If okay, we can have a check in
disconnect_cached_connections something like below:
Also like pg_terminate_backend(), we should disallow non-superuser to disconnect the connections established by other non-superuser if the requesting user is not a member of the other? Or that's overkill because the target to discard is just a connection and it can be established again if necessary?
For now I'm thinking that it might better to add the restriction like pg_terminate_backend() at first and relax that later if possible. But I'd like hear more opinions about this.
+static bool +disconnect_cached_connections(Oid serverid) +{ + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + bool all = !OidIsValid(serverid); + bool result = false; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to discard open connections"))); + + if (!ConnectionHash)Having said that, it looks like dblink_disconnect doesn't perform
superuser checks.
Also non-superuser (set by SET ROLE or SET SESSION AUTHORIZATION) seems to be able to run SQL using the dblink connection established by superuser. If we didn't think that this is a problem, we also might not need to care about issue even for postgres_fdw.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Jan 25, 2021 at 1:20 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yeah, connections can be discarded by non-super users using
postgres_fdw_disconnect_all and postgres_fdw_disconnect. Given the
fact that a non-super user requires a password to access foreign
tables [1], IMO a non-super user changing something related to a super
user makes no sense at all. If okay, we can have a check in
disconnect_cached_connections something like below:Also like pg_terminate_backend(), we should disallow non-superuser to disconnect the connections established by other non-superuser if the requesting user is not a member of the other? Or that's overkill because the target to discard is just a connection and it can be established again if necessary?
Yes, if required backends can establish the connection again. But my
worry is this - a non-super user disconnecting all or a given
connection created by a super user?
For now I'm thinking that it might better to add the restriction like pg_terminate_backend() at first and relax that later if possible. But I'd like hear more opinions about this.
I agree. If required we can lift it later, once we get the users using
these functions? Maybe we can have a comment near superchecks in
disconnect_cached_connections saying, we can lift this in future?
Do you want me to add these checks like in pg_signal_backend?
/* Only allow superusers to signal superuser-owned backends. */
if (superuser_arg(proc->roleId) && !superuser())
return SIGNAL_BACKEND_NOSUPERUSER;
/* Users can signal backends they have role membership in. */
if (!has_privs_of_role(GetUserId(), proc->roleId) &&
!has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
return SIGNAL_BACKEND_NOPERMISSION;
or only below is enough?
+ /* Non-super users are not allowed to disconnect cached connections. */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to discard open connections")));
+static bool +disconnect_cached_connections(Oid serverid) +{ + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + bool all = !OidIsValid(serverid); + bool result = false; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to discard open connections"))); + + if (!ConnectionHash)Having said that, it looks like dblink_disconnect doesn't perform
superuser checks.Also non-superuser (set by SET ROLE or SET SESSION AUTHORIZATION) seems to be able to run SQL using the dblink connection established by superuser. If we didn't think that this is a problem, we also might not need to care about issue even for postgres_fdw.
IMO, we can have superuser checks for postgres_fdw new functions for now.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/25 18:13, Bharath Rupireddy wrote:
On Mon, Jan 25, 2021 at 1:20 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yeah, connections can be discarded by non-super users using
postgres_fdw_disconnect_all and postgres_fdw_disconnect. Given the
fact that a non-super user requires a password to access foreign
tables [1], IMO a non-super user changing something related to a super
user makes no sense at all. If okay, we can have a check in
disconnect_cached_connections something like below:Also like pg_terminate_backend(), we should disallow non-superuser to disconnect the connections established by other non-superuser if the requesting user is not a member of the other? Or that's overkill because the target to discard is just a connection and it can be established again if necessary?
Yes, if required backends can establish the connection again. But my
worry is this - a non-super user disconnecting all or a given
connection created by a super user?
Yes, I was also worried about that. But I found that there are other similar cases, for example,
- a cursor that superuser declared can be closed by non-superuser (set by SET ROLE or SET SESSION AUTHORIZATION) in the same session.
- a prepared statement that superuser created can be deallocated by non-superuser in the same session.
This makes me think that it's OK even for non-superuser to disconnect the connections established by superuser in the same session. For now I've not found any real security issue by doing that yet. Thought? Am I missing something?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Jan 25, 2021 at 3:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yes, if required backends can establish the connection again. But my
worry is this - a non-super user disconnecting all or a given
connection created by a super user?Yes, I was also worried about that. But I found that there are other similar cases, for example,
- a cursor that superuser declared can be closed by non-superuser (set by SET ROLE or SET SESSION AUTHORIZATION) in the same session.
- a prepared statement that superuser created can be deallocated by non-superuser in the same session.This makes me think that it's OK even for non-superuser to disconnect the connections established by superuser in the same session. For now I've not found any real security issue by doing that yet. Thought? Am I missing something?
Oh, and added to that list is dblink_disconnect(). I don't know
whether there's any security risk if we allow non-superusers to
discard the super users connections. In this case, the super users
will just have to re make the connection.
For now I'm thinking that it might better to add the restriction like pg_terminate_backend() at first and relax that later if possible. But I'd like hear more opinions about this.
I agree. If required we can lift it later, once we get the users using
these functions? Maybe we can have a comment near superchecks in
disconnect_cached_connections saying, we can lift this in future?
Maybe we can do the opposite of the above that is not doing any
superuser checks in disconnect functions for now, and later if some
users complain we can add it? We can leave a comment there that "As of
now we don't see any security risks if a non-super user disconnects
the connections made by super users. If required, non-supers can be
disallowed to disconnct the connections" ?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/25 19:28, Bharath Rupireddy wrote:
On Mon, Jan 25, 2021 at 3:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yes, if required backends can establish the connection again. But my
worry is this - a non-super user disconnecting all or a given
connection created by a super user?Yes, I was also worried about that. But I found that there are other similar cases, for example,
- a cursor that superuser declared can be closed by non-superuser (set by SET ROLE or SET SESSION AUTHORIZATION) in the same session.
- a prepared statement that superuser created can be deallocated by non-superuser in the same session.This makes me think that it's OK even for non-superuser to disconnect the connections established by superuser in the same session. For now I've not found any real security issue by doing that yet. Thought? Am I missing something?
Oh, and added to that list is dblink_disconnect(). I don't know
whether there's any security risk if we allow non-superusers to
discard the super users connections.
I guess that's ok because superuser and nonsuperuser are running in the same session. That is, since this is the case where superuser switches to nonsuperuser intentionally, interactions between them is also intentional.
OTOH, if nonsuperuser in one session can affect superuser in another session that way, which would be problematic. So, for example, for now pg_stat_activity disallows nonsuperuser to see the query that superuser in another session is running, from it.
In this case, the super users
will just have to re make the connection.For now I'm thinking that it might better to add the restriction like pg_terminate_backend() at first and relax that later if possible. But I'd like hear more opinions about this.
I agree. If required we can lift it later, once we get the users using
these functions? Maybe we can have a comment near superchecks in
disconnect_cached_connections saying, we can lift this in future?Maybe we can do the opposite of the above that is not doing any
superuser checks in disconnect functions for now, and later if some
users complain we can add it?
+1
We can leave a comment there that "As of
now we don't see any security risks if a non-super user disconnects
the connections made by super users. If required, non-supers can be
disallowed to disconnct the connections" ?
Yes. Also we should note that that's ok because they are in the same session.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Jan 25, 2021 at 7:20 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/25 19:28, Bharath Rupireddy wrote:
On Mon, Jan 25, 2021 at 3:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yes, if required backends can establish the connection again. But my
worry is this - a non-super user disconnecting all or a given
connection created by a super user?Yes, I was also worried about that. But I found that there are other similar cases, for example,
- a cursor that superuser declared can be closed by non-superuser (set by SET ROLE or SET SESSION AUTHORIZATION) in the same session.
- a prepared statement that superuser created can be deallocated by non-superuser in the same session.This makes me think that it's OK even for non-superuser to disconnect the connections established by superuser in the same session. For now I've not found any real security issue by doing that yet. Thought? Am I missing something?
Oh, and added to that list is dblink_disconnect(). I don't know
whether there's any security risk if we allow non-superusers to
discard the super users connections.I guess that's ok because superuser and nonsuperuser are running in the same session. That is, since this is the case where superuser switches to nonsuperuser intentionally, interactions between them is also intentional.
OTOH, if nonsuperuser in one session can affect superuser in another session that way, which would be problematic. So, for example, for now pg_stat_activity disallows nonsuperuser to see the query that superuser in another session is running, from it.
Hmm, that makes sense.
In this case, the super users
will just have to re make the connection.For now I'm thinking that it might better to add the restriction like pg_terminate_backend() at first and relax that later if possible. But I'd like hear more opinions about this.
I agree. If required we can lift it later, once we get the users using
these functions? Maybe we can have a comment near superchecks in
disconnect_cached_connections saying, we can lift this in future?Maybe we can do the opposite of the above that is not doing any
superuser checks in disconnect functions for now, and later if some
users complain we can add it?+1
Thanks, will send the updated patch set soon.
We can leave a comment there that "As of
now we don't see any security risks if a non-super user disconnects
the connections made by super users. If required, non-supers can be
disallowed to disconnct the connections" ?Yes. Also we should note that that's ok because they are in the same session.
I will add this comment in disconnect_cached_connections so that we
don't lose track of it.
I will provide the updated patch set soon.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Jan 25, 2021 at 7:28 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I will provide the updated patch set soon.
Attaching v17 patch set, please review it further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v17-0001-postgres_fdw-function-to-discard-cached-connecti.patchapplication/octet-stream; name=v17-0001-postgres_fdw-function-to-discard-cached-connecti.patchDownload
From e82755587a4e039d628235a5e1dc8b1849d0499e Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 25 Jan 2021 19:53:43 +0530
Subject: [PATCH v17] postgres_fdw function to discard cached connections
This patch introduces two new functions postgres_fdw_disconnect()
and postgres_fdw_disconnect_all() to discard cached connection
for a given foreign server or discard all cached connections
respectively.
---
contrib/postgres_fdw/connection.c | 131 +++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 194 +++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 +
contrib/postgres_fdw/sql/postgres_fdw.sql | 91 +++++++-
doc/src/sgml/postgres-fdw.sgml | 64 +++++-
5 files changed, 479 insertions(+), 11 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..966a8860e8 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -80,6 +80,8 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect_all);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(Oid serverid);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1447,7 +1450,7 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
/*
* If the server has been dropped in the current explicit
* transaction, then this entry would have been invalidated in
- * pgfdw_inval_callback at the end of drop sever command. Note
+ * pgfdw_inval_callback at the end of drop server command. Note
* that this connection would not have been closed in
* pgfdw_inval_callback because it is still being used in the
* current explicit transaction. So, assert that here.
@@ -1470,3 +1473,129 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect cached foreign server connection.
+ *
+ * This function throws an error when there is no foreign server with the given
+ * name.
+ *
+ * It closes cached connections of the given foreign server that are not being
+ * used within current transaction yet and returns true if it manages to close
+ * at least one such connection. If any of the found connection is being used
+ * within the current transaction, then it doesn't close it, instead emits a
+ * warning. If all the found connections are in use or no connection is found
+ * for the given foreign server or cache doesn't exit at all, then it returns
+ * false.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ ForeignServer *server;
+ char *servername;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, false);
+
+ PG_RETURN_BOOL(disconnect_cached_connections(server->serverid));
+}
+
+/*
+ * Disconnect all cached connections.
+ *
+ * This function scans all the cache entries, closes connections that are not
+ * being used within current transaction. It emits warning for each connection
+ * that's in use.
+ *
+ * It returns true, if it closes at least one connection, otherwise false.
+ */
+Datum
+postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(disconnect_cached_connections(InvalidOid));
+}
+
+/*
+ * Workhorse to disconnect cached connections.
+ *
+ * This function disconnects either all unused connections when called from
+ * postgres_fdw_disconnect_all or a given foreign server unused connection when
+ * called from postgres_fdw_disconnect.
+ *
+ * It returns true if at least one connection is disconnected, otherwise false.
+ */
+static bool
+disconnect_cached_connections(Oid serverid)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool all = !OidIsValid(serverid);
+ bool result = false;
+
+ /*
+ * We allow non-super users to disconnect the cached connections made by
+ * super users. That's okay because all the cached connections are within
+ * the same session and if at all required, the connections can be remade.
+ * As of now we don't see any security risk doing this. If required,
+ * non-supers can be disallowed to disconnect the connections.
+ */
+
+ /*
+ * Connection cache is not yet initialized in this session, so return
+ * false.
+ */
+ if (!ConnectionHash)
+ return result;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /* Ignore cache entry if no open connection right now. */
+ if (!entry->conn)
+ continue;
+ /*
+ * Either disconnect given or all the active and not in use cached
+ * connections.
+ */
+ if (all || entry->serverid == serverid)
+ {
+ /* We cannot close connection that's in use, so issue a warning. */
+ if (entry->xact_depth > 0)
+ {
+ ForeignServer *server;
+
+ server = GetForeignServerExtended(entry->serverid,
+ FSV_MISSING_OK);
+
+ if (!server)
+ {
+ /*
+ * If the server has been dropped in the current explicit
+ * transaction, then this entry would have been invalidated
+ * in pgfdw_inval_callback at the end of drop server
+ * command. Note that this connection would not have been
+ * closed in pgfdw_inval_callback because it is still being
+ * used in the current explicit transaction. So, assert
+ * that here.
+ */
+ Assert(entry->invalidated);
+
+ ereport(WARNING,
+ (errmsg("cannot close dropped server connection because it is still in use")));
+ }
+ else
+ ereport(WARNING,
+ (errmsg("cannot close connection for server \"%s\" because it is still in use",
+ server->servername)));
+ }
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b4a04d2c14..d38d9cd4c8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -17,7 +17,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
@@ -25,6 +28,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -140,6 +144,11 @@ CREATE FOREIGN TABLE ft7 (
c2 int NOT NULL,
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -211,7 +220,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
-(6 rows)
+ public | ft8 | loopback4 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,9 +9063,9 @@ ERROR: 08006
COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
--- ===================================================================
--- test connection invalidation cases
--- ===================================================================
+-- =============================================================================
+-- test connection invalidation cases and postgres_fdw_get_connections function
+-- =============================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
-- List all the existing cached connections. Only loopback2 should be output.
@@ -9118,6 +9128,180 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback2 | t
(1 row)
+-- =======================================================================
+-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
+-- =======================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+ loopback4 | t
+(3 rows)
+
+DROP SERVER loopback4 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server loopback4
+drop cascades to foreign table ft8
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close dropped server connection because it is still in use
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Return false as loopback2 connectin is closed already.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+ERROR: server "unknownserver" does not exist
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- =============================================================================
+-- test case for having multiple cached connections for a foreign server
+-- =============================================================================
+CREATE ROLE multi_conn_user1 SUPERUSER;
+CREATE ROLE multi_conn_user2 SUPERUSER;
+CREATE USER MAPPING FOR multi_conn_user1 SERVER loopback;
+CREATE USER MAPPING FOR multi_conn_user2 SERVER loopback;
+SET ROLE multi_conn_user1;
+-- Will cache loopback connection with user mapping for multi_conn_user1
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+RESET ROLE;
+SET ROLE multi_conn_user2;
+-- Will cache loopback connection with user mapping for multi_conn_user2
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+RESET ROLE;
+-- Should output two connections for loopback server
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback | t
+(2 rows)
+
+-- Clean up
+DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
+DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
+DROP ROLE multi_conn_user1;
+DROP ROLE multi_conn_user2;
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..b8c0209036 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect_all ()
+RETURNS bool
+AS 'MODULE_PATHNAME','postgres_fdw_disconnect_all'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 28b82f5f9d..5f9e1f195f 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -19,7 +19,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -28,6 +31,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -154,6 +158,12 @@ CREATE FOREIGN TABLE ft7 (
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2710,9 +2720,9 @@ COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
--- ===================================================================
--- test connection invalidation cases
--- ===================================================================
+-- =============================================================================
+-- test connection invalidation cases and postgres_fdw_get_connections function
+-- =============================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
-- List all the existing cached connections. Only loopback2 should be output.
@@ -2739,6 +2749,79 @@ COMMIT;
-- the above transaction.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- =======================================================================
+-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
+-- =======================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER loopback4 CASCADE;
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Return false as loopback2 connectin is closed already.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- =============================================================================
+-- test case for having multiple cached connections for a foreign server
+-- =============================================================================
+CREATE ROLE multi_conn_user1 SUPERUSER;
+CREATE ROLE multi_conn_user2 SUPERUSER;
+CREATE USER MAPPING FOR multi_conn_user1 SERVER loopback;
+CREATE USER MAPPING FOR multi_conn_user2 SERVER loopback;
+
+SET ROLE multi_conn_user1;
+-- Will cache loopback connection with user mapping for multi_conn_user1
+SELECT 1 FROM ft1 LIMIT 1;
+RESET ROLE;
+
+SET ROLE multi_conn_user2;
+-- Will cache loopback connection with user mapping for multi_conn_user2
+SELECT 1 FROM ft1 LIMIT 1;
+RESET ROLE;
+
+-- Should output two connections for loopback server
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Clean up
+DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
+DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
+DROP ROLE multi_conn_user1;
+DROP ROLE multi_conn_user2;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index fb4c22ac69..7f614390a4 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -512,7 +512,7 @@ OPTIONS (ADD password_required 'false');
the end of that transaction. <literal>true</literal> is returned
otherwise. If there are no open connections, no record is returned.
Example usage of the function:
- <screen>
+<screen>
postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
server_name | valid
-------------+-------
@@ -522,6 +522,54 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(server_name text) returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards open connections that are established by
+ <filename>postgres_fdw</filename> from the local session to a foreign
+ server with the given name if they are not used in the current local
+ transaction yet, and then returns <literal>true</literal> if it manages
+ to discard at least one such connection. If any of the found connection
+ is being used within the current local transaction, then it doesn't
+ discard it, instead emits a warning for each such connection. If all the
+ found connections are in use or no connection is found for the given
+ foreign server or cache doesn't exit at all, then it returns <literal>false</literal>.
+ If no foreign server with the given name is found, an error is emitted.
+ Non-superusers are not allowed to discard any connection. Example usage
+ of the function:
+<screen>
+postgres=# SELECT postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect_all() returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards all the open connections that <filename>postgres_fdw</filename>
+ established from the local session to the foreign server if they are not
+ used in the current local transaction yet. It doesn't discard the
+ connections that's already used in the current local transaction and
+ emits a warning for each such connection. It returns <literal>true</literal>,
+ if it closes at least one connection, otherwise <literal>false</literal>.
+ Non-superusers are not allowed to discard any connection. Example usage
+ of the function:
+<screen>
+postgres=# SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -537,6 +585,20 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ Since the <filename>postgres_fdw</filename> keeps the connections to remote
+ servers in the local session, the corresponding sessions that are opened on
+ the remote servers are kept idle until they are re-used by the local session.
+ This may waste resources if those connections are not frequently used by the
+ local session. To address this, the <filename>postgres_fdw</filename>
+ provides following way to remove the connections to the remote servers and
+ so the remote sessions:
+
+ <function>postgres_fdw_disconnect_all()</function> to discard all the
+ connections or <function>postgres_fdw_disconnect(server_name text)</function>
+ to discard the connection associated with the given foreign server.
+ </para>
</sect2>
<sect2>
--
2.25.1
v17-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v17-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 50ce76c43d6982359b667570b85b3f902e92e47a Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 23 Jan 2021 13:54:41 +0530
Subject: [PATCH v17] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 ++++++++++++++++++-
6 files changed, 134 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ffe6d8cec2..99584b054e 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 66dc4131e0..32957fed21 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9326,6 +9326,49 @@ DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
DROP ROLE multi_conn_user1;
DROP ROLE multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8648be0b81..5f80f143dc 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -312,6 +312,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -527,6 +529,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_i);
static int get_batch_size_option(Relation rel);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 1f67b4d9fd..ceea46b304 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 27689f066e..cf0834bd77 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2836,6 +2836,26 @@ DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
DROP ROLE multi_conn_user1;
DROP ROLE multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index b27c4e8fa5..cc558218c5 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -576,6 +576,42 @@ postgres=# SELECT postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -594,12 +630,18 @@ postgres=# SELECT postgres_fdw_disconnect_all();
the remote servers are kept idle until they are re-used by the local session.
This may waste resources if those connections are not frequently used by the
local session. To address this, the <filename>postgres_fdw</filename>
- provides following way to remove the connections to the remote servers and
+ provides following ways to remove the connections to the remote servers and
so the remote sessions:
<function>postgres_fdw_disconnect_all()</function> to discard all the
connections or <function>postgres_fdw_disconnect(server_name text)</function>
to discard the connection associated with the given foreign server.
+
+ A configuration parameter, <varname>postgres_fdw.keep_connections</varname>,
+ default being <literal>on</literal>, when set to <literal>off</literal>, the
+ local session doesn't keep remote connections that are made to the foreign
+ servers. Each connection is discarded at the end of transaction in which it
+ is used.
</para>
</sect2>
--
2.25.1
v17-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v17-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From d66119f8a36d17420dafde1f71192a544c3ebb35 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Sat, 23 Jan 2021 13:59:16 +0530
Subject: [PATCH v17] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 37 +++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 17 ++++++++
doc/src/sgml/postgres-fdw.sgml | 43 +++++++++++++++++++
5 files changed, 125 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 99584b054e..a7cdc333f1 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,15 +970,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 32957fed21..1de115fc84 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8939,7 +8939,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9369,6 +9369,41 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..75126a84bc 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -227,6 +228,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index cf0834bd77..98921ea509 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2856,6 +2856,23 @@ SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index cc558218c5..68d3351629 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -490,6 +490,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -599,6 +628,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -642,6 +679,12 @@ postgres=# SELECT postgres_fdw_disconnect_all();
local session doesn't keep remote connections that are made to the foreign
servers. Each connection is discarded at the end of transaction in which it
is used.
+
+ A server level option, <literal>keep_connection</literal> that is used with
+ <xref linkend="sql-createserver"/>. Default being <literal>on</literal>,
+ when set to <literal>off</literal> the local session doesn't keep remote
+ connection associated with the foreign server. The connection is discarded
+ at the end of the transaction.
</para>
</sect2>
--
2.25.1
On 2021/01/26 0:12, Bharath Rupireddy wrote:
On Mon, Jan 25, 2021 at 7:28 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I will provide the updated patch set soon.
Attaching v17 patch set, please review it further.
Thanks for updating the patch!
Attached is the tweaked version of the patch. I didn't change any logic,
but I updated some comments and docs. Also I added the regresssion test
to check that postgres_fdw_disconnect() closes multiple connections.
Barring any objection, I will commit this version.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attachments:
v18-0001-postgres_fdw-Add-functions-to-discard-cached-connect.patchtext/plain; charset=UTF-8; name=v18-0001-postgres_fdw-Add-functions-to-discard-cached-connect.patch; x-mac-creator=0; x-mac-type=0Download
From fa3e0f0a700588ab644c4c752e06c03845450712 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 26 Jan 2021 03:54:46 +0900
Subject: [PATCH] postgres_fdw: Add functions to discard cached connections.
This commit introduces two new functions postgres_fdw_disconnect()
and postgres_fdw_disconnect_all(). The former function discards
the cached connection to the specified foreign server. The latter discards
all the cached connections. If the connection is used in the current
transaction, it's not closed and a warning messsage is emitted.
For example, these functions are useful when users want to explicitly
close the foreign server connections that are no longer necessary and
then to prevent them from eating up the foreign servers connections
capacity.
Author: Bharath Rupireddy, tweaked a bit by Fujii Masao
Reviewed-by: Alexey Kondratov, Zhijie Hou, Zhihong Yu, Fujii Masao
Discussion: https://postgr.es/m/CALj2ACVvrp5=AVp2PupEm+nAC8S4buqR3fJMmaCoc7ftT0aD2A@mail.gmail.com
---
contrib/postgres_fdw/connection.c | 135 +++++++++++-
.../postgres_fdw/expected/postgres_fdw.out | 208 +++++++++++++++++-
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 10 +
contrib/postgres_fdw/sql/postgres_fdw.sql | 98 ++++++++-
doc/src/sgml/postgres-fdw.sgml | 67 +++++-
5 files changed, 505 insertions(+), 13 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index a1404cb6bb..ee0b4acf0b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -80,6 +80,8 @@ static bool xact_got_connection = false;
* SQL functions
*/
PG_FUNCTION_INFO_V1(postgres_fdw_get_connections);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect);
+PG_FUNCTION_INFO_V1(postgres_fdw_disconnect_all);
/* prototypes of private functions */
static void make_new_connection(ConnCacheEntry *entry, UserMapping *user);
@@ -102,6 +104,7 @@ static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
PGresult **result);
static bool UserMappingPasswordRequired(UserMapping *user);
+static bool disconnect_cached_connections(Oid serverid);
/*
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -1428,8 +1431,8 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
* Even though the server is dropped in the current transaction, the
* cache can still have associated active connection entry, say we
* call such connections dangling. Since we can not fetch the server
- * name from system catalogs for dangling connections, instead we
- * show NULL value for server name in output.
+ * name from system catalogs for dangling connections, instead we show
+ * NULL value for server name in output.
*
* We could have done better by storing the server name in the cache
* entry instead of server oid so that it could be used in the output.
@@ -1447,7 +1450,7 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
/*
* If the server has been dropped in the current explicit
* transaction, then this entry would have been invalidated in
- * pgfdw_inval_callback at the end of drop sever command. Note
+ * pgfdw_inval_callback at the end of drop server command. Note
* that this connection would not have been closed in
* pgfdw_inval_callback because it is still being used in the
* current explicit transaction. So, assert that here.
@@ -1470,3 +1473,129 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
+
+/*
+ * Disconnect the specified cached connections.
+ *
+ * This function discards the open connections that are established by
+ * postgres_fdw from the local session to the foreign server with
+ * the given name. Note that there can be multiple connections to
+ * the given server using different user mappings. If the connections
+ * are used in the current local transaction, they are not disconnected
+ * and warning messages are reported. This function returns true
+ * if it disconnects at least one connection, otherwise false. If no
+ * foreign server with the given name is found, an error is reported.
+ */
+Datum
+postgres_fdw_disconnect(PG_FUNCTION_ARGS)
+{
+ ForeignServer *server;
+ char *servername;
+
+ servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ server = GetForeignServerByName(servername, false);
+
+ PG_RETURN_BOOL(disconnect_cached_connections(server->serverid));
+}
+
+/*
+ * Disconnect all the cached connections.
+ *
+ * This function discards all the open connections that are established by
+ * postgres_fdw from the local session to the foreign servers.
+ * If the connections are used in the current local transaction, they are
+ * not disconnected and warning messages are reported. This function
+ * returns true if it disconnects at least one connection, otherwise false.
+ */
+Datum
+postgres_fdw_disconnect_all(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(disconnect_cached_connections(InvalidOid));
+}
+
+/*
+ * Workhorse to disconnect cached connections.
+ *
+ * This function scans all the connection cache entries and disconnects
+ * the open connections whose foreign server OID matches with
+ * the specified one. If InvalidOid is specified, it disconnects all
+ * the cached connections.
+ *
+ * This function emits a warning for each connection that's used in
+ * the current transaction and doesn't close it. It returns true if
+ * it disconnects at least one connection, otherwise false.
+ *
+ * Note that this function disconnects even the connections that are
+ * established by other users in the same local session using different
+ * user mappings. This leads even non-superuser to be able to close
+ * the connections established by superusers in the same local session.
+ *
+ * XXX As of now we don't see any security risk doing this. But we should
+ * set some restrictions on that, for example, prevent non-superuser
+ * from closing the connections established by superusers even
+ * in the same session?
+ */
+static bool
+disconnect_cached_connections(Oid serverid)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+ bool all = !OidIsValid(serverid);
+ bool result = false;
+
+ /*
+ * Connection cache hashtable has not been initialized yet in this
+ * session, so return false.
+ */
+ if (!ConnectionHash)
+ return false;
+
+ hash_seq_init(&scan, ConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ /* Ignore cache entry if no open connection right now. */
+ if (!entry->conn)
+ continue;
+
+ if (all || entry->serverid == serverid)
+ {
+ /*
+ * Emit a warning because the connection to close is used in the
+ * current transaction and cannot be disconnected right now.
+ */
+ if (entry->xact_depth > 0)
+ {
+ ForeignServer *server;
+
+ server = GetForeignServerExtended(entry->serverid,
+ FSV_MISSING_OK);
+
+ if (!server)
+ {
+ /*
+ * If the foreign server was dropped while its connection
+ * was used in the current transaction, the connection
+ * must have been marked as invalid by
+ * pgfdw_inval_callback at the end of DROP SERVER command.
+ */
+ Assert(entry->invalidated);
+
+ ereport(WARNING,
+ (errmsg("cannot close dropped server connection because it is still in use")));
+ }
+ else
+ ereport(WARNING,
+ (errmsg("cannot close connection for server \"%s\" because it is still in use",
+ server->servername)));
+ }
+ else
+ {
+ elog(DEBUG3, "discarding connection %p", entry->conn);
+ disconnect_pg_server(entry);
+ result = true;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b4a04d2c14..e33c92d7f1 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -17,7 +17,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
@@ -25,6 +28,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -140,6 +144,11 @@ CREATE FOREIGN TABLE ft7 (
c2 int NOT NULL,
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -211,7 +220,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
-(6 rows)
+ public | ft8 | loopback4 | (schema_name 'S 1', table_name 'T 4') |
+(7 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9053,9 +9063,9 @@ ERROR: 08006
COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
--- ===================================================================
--- test connection invalidation cases
--- ===================================================================
+-- =============================================================================
+-- test connection invalidation cases and postgres_fdw_get_connections function
+-- =============================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
-- List all the existing cached connections. Only loopback2 should be output.
@@ -9118,6 +9128,194 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback2 | t
(1 row)
+-- =======================================================================
+-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
+-- =======================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+(2 rows)
+
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback2 | t
+(1 row)
+
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback2 | t
+ loopback4 | t
+(3 rows)
+
+DROP SERVER loopback4 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to user mapping for public on server loopback4
+drop cascades to foreign table ft8
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+WARNING: cannot close dropped server connection because it is still in use
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close connection for server "loopback2" because it is still in use
+ postgres_fdw_disconnect_all
+-----------------------------
+ f
+(1 row)
+
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
+-- Return false as loopback2 connectin is closed already.
+SELECT postgres_fdw_disconnect('loopback2');
+ postgres_fdw_disconnect
+-------------------------
+ f
+(1 row)
+
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+ERROR: server "unknownserver" does not exist
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- =============================================================================
+-- test case for having multiple cached connections for a foreign server
+-- =============================================================================
+CREATE ROLE multi_conn_user1 SUPERUSER;
+CREATE ROLE multi_conn_user2 SUPERUSER;
+CREATE USER MAPPING FOR multi_conn_user1 SERVER loopback;
+CREATE USER MAPPING FOR multi_conn_user2 SERVER loopback;
+-- Will cache loopback connection with user mapping for multi_conn_user1
+SET ROLE multi_conn_user1;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+RESET ROLE;
+-- Will cache loopback connection with user mapping for multi_conn_user2
+SET ROLE multi_conn_user2;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+RESET ROLE;
+-- Should output two connections for loopback server
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+ loopback | t
+(2 rows)
+
+-- Close loopback connections and return true.
+SELECT postgres_fdw_disconnect('loopback');
+ postgres_fdw_disconnect
+-------------------------
+ t
+(1 row)
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Clean up
+DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
+DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
+DROP ROLE multi_conn_user1;
+DROP ROLE multi_conn_user2;
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index 7f85784466..ed4ca378d4 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -8,3 +8,13 @@ CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
RETURNS SETOF record
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect (text)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION postgres_fdw_disconnect_all ()
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 28b82f5f9d..9473ab0762 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -19,7 +19,10 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
-
+ EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
END;
$d$;
@@ -28,6 +31,7 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
+CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -154,6 +158,12 @@ CREATE FOREIGN TABLE ft7 (
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
+CREATE FOREIGN TABLE ft8 (
+ c1 int NOT NULL,
+ c2 int NOT NULL,
+ c3 text
+) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
+
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2710,9 +2720,9 @@ COMMIT;
-- Clean up
DROP PROCEDURE terminate_backend_and_wait(text);
--- ===================================================================
--- test connection invalidation cases
--- ===================================================================
+-- =============================================================================
+-- test connection invalidation cases and postgres_fdw_get_connections function
+-- =============================================================================
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
-- List all the existing cached connections. Only loopback2 should be output.
@@ -2739,6 +2749,86 @@ COMMIT;
-- the above transaction.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- =======================================================================
+-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
+-- =======================================================================
+-- Return true as all cached connections are closed.
+SELECT postgres_fdw_disconnect_all();
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+BEGIN;
+-- Ensure to cache loopback2 connection.
+SELECT 1 FROM ft6 LIMIT 1;
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issue a warning and return false as loopback2 connection is still in use and
+-- can not be closed.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Close loopback connection, return true and issue a warning as loopback2
+-- connection is still in use and can not be closed.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. loopback2 should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Ensure to cache loopback connection.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Ensure to cache loopback4 connection.
+SELECT 1 FROM ft8 LIMIT 1;
+-- List all the existing cached connections. loopback, loopback2, loopback4
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+DROP SERVER loopback4 CASCADE;
+-- Return false as connections are still in use, warnings are issued.
+SELECT postgres_fdw_disconnect_all();
+COMMIT;
+-- Close loopback2 connection and return true.
+SELECT postgres_fdw_disconnect('loopback2');
+-- List all the existing cached connections. loopback should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Return false as loopback2 connectin is closed already.
+SELECT postgres_fdw_disconnect('loopback2');
+-- Return an error as there is no foreign server with given name.
+SELECT postgres_fdw_disconnect('unknownserver');
+-- Close loopback connection and return true.
+SELECT postgres_fdw_disconnect_all();
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- =============================================================================
+-- test case for having multiple cached connections for a foreign server
+-- =============================================================================
+CREATE ROLE multi_conn_user1 SUPERUSER;
+CREATE ROLE multi_conn_user2 SUPERUSER;
+CREATE USER MAPPING FOR multi_conn_user1 SERVER loopback;
+CREATE USER MAPPING FOR multi_conn_user2 SERVER loopback;
+
+-- Will cache loopback connection with user mapping for multi_conn_user1
+SET ROLE multi_conn_user1;
+SELECT 1 FROM ft1 LIMIT 1;
+RESET ROLE;
+
+-- Will cache loopback connection with user mapping for multi_conn_user2
+SET ROLE multi_conn_user2;
+SELECT 1 FROM ft1 LIMIT 1;
+RESET ROLE;
+
+-- Should output two connections for loopback server
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Close loopback connections and return true.
+SELECT postgres_fdw_disconnect('loopback');
+
+-- List all the existing cached connections. No connection exists, so NULL
+-- should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
+-- Clean up
+DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
+DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
+DROP ROLE multi_conn_user1;
+DROP ROLE multi_conn_user2;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index fb4c22ac69..86b9e33ba8 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -512,7 +512,7 @@ OPTIONS (ADD password_required 'false');
the end of that transaction. <literal>true</literal> is returned
otherwise. If there are no open connections, no record is returned.
Example usage of the function:
- <screen>
+<screen>
postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
server_name | valid
-------------+-------
@@ -522,6 +522,51 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect(server_name text) returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards the open connections that are established by
+ <filename>postgres_fdw</filename> from the local session to
+ the foreign server with the given name. Note that there can be
+ multiple connections to the given server using different user mappings.
+ If the connections are used in the current local transaction,
+ they are not disconnected and warning messages are reported.
+ This function returns <literal>true</literal> if it disconnects
+ at least one connection, otherwise <literal>false</literal>.
+ If no foreign server with the given name is found, an error is reported.
+ Example usage of the function:
+<screen>
+postgres=# SELECT postgres_fdw_disconnect('loopback1');
+ postgres_fdw_disconnect
+-------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><function>postgres_fdw_disconnect_all() returns boolean</function></term>
+ <listitem>
+ <para>
+ This function discards all the open connections that are established by
+ <filename>postgres_fdw</filename> from the local session to
+ the foreign servers. If the connections are used in the current local
+ transaction, they are not disconnected and warning messages are reported.
+ This function returns <literal>true</literal> if it disconnects
+ at least one connection, otherwise <literal>false</literal>.
+ Example usage of the function:
+<screen>
+postgres=# SELECT postgres_fdw_disconnect_all();
+ postgres_fdw_disconnect_all
+-----------------------------
+ t
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
@@ -537,6 +582,26 @@ postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
+
+ <para>
+ When changing the definition of or removing a foreign server or
+ a user mapping, the correspoinding connections are closed.
+ But note that if the connections are used in the current local transaction
+ at that moment, they are kept until the end of the transaction.
+ Closed connections will be established again when they are necessary
+ by subsequent queries using a foreign table.
+ </para>
+
+ <para>
+ Once a connection to a foreign server has been established,
+ it's usually kept until the local or the corresponding remote
+ session exits. To disconnect a connection explicitly,
+ <function>postgres_fdw_disconnect</function> and
+ <function>postgres_fdw_disconnect_all</function> functions
+ need to be used. For example, these are useful when closing
+ the connections that are no longer necessary and then preventing them
+ from consuming the foreign server connections capacity too much.
+ </para>
</sect2>
<sect2>
--
2.27.0
On Tue, Jan 26, 2021 at 12:38 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
Attaching v17 patch set, please review it further.
Thanks for updating the patch!
Attached is the tweaked version of the patch. I didn't change any logic,
but I updated some comments and docs. Also I added the regresssion test
to check that postgres_fdw_disconnect() closes multiple connections.
Barring any objection, I will commit this version.
Thanks. The patch LGTM, except few typos:
1) in the commit message "a warning messsage is emitted." it's
"message" not "messsage".
2) in the documentation "+ a user mapping, the correspoinding
connections are closed." it's "corresponding" not "correspoinding".
I will post "keep_connections" GUC and "keep_connection" server level
option patches later.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/26 12:08, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 12:38 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:Attaching v17 patch set, please review it further.
Thanks for updating the patch!
Attached is the tweaked version of the patch. I didn't change any logic,
but I updated some comments and docs. Also I added the regresssion test
to check that postgres_fdw_disconnect() closes multiple connections.
Barring any objection, I will commit this version.Thanks. The patch LGTM, except few typos:
1) in the commit message "a warning messsage is emitted." it's
"message" not "messsage".
2) in the documentation "+ a user mapping, the correspoinding
connections are closed." it's "corresponding" not "correspoinding".
Thanks for the review! I fixed them and pushed the patch!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Fujii Masao <masao.fujii@oss.nttdata.com> writes:
Thanks for the review! I fixed them and pushed the patch!
Buildfarm is very not happy ...
regards, tom lane
On 2021/01/26 16:05, Tom Lane wrote:
Fujii Masao <masao.fujii@oss.nttdata.com> writes:
Thanks for the review! I fixed them and pushed the patch!
Buildfarm is very not happy ...
Yes.... I'm investigating that.
-- Return false as connections are still in use, warnings are issued.
SELECT postgres_fdw_disconnect_all();
-WARNING: cannot close dropped server connection because it is still in use
-WARNING: cannot close connection for server "loopback" because it is still in use
WARNING: cannot close connection for server "loopback2" because it is still in use
+WARNING: cannot close connection for server "loopback" because it is still in use
+WARNING: cannot close dropped server connection because it is still in use
The cause of the regression test failure is that the order of warning messages
is not stable. So I'm thinking to set client_min_messages to ERROR temporarily
when doing the above test.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Tue, Jan 26, 2021 at 12:54 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/26 16:05, Tom Lane wrote:
Fujii Masao <masao.fujii@oss.nttdata.com> writes:
Thanks for the review! I fixed them and pushed the patch!
Buildfarm is very not happy ...
Yes.... I'm investigating that.
-- Return false as connections are still in use, warnings are issued. SELECT postgres_fdw_disconnect_all(); -WARNING: cannot close dropped server connection because it is still in use -WARNING: cannot close connection for server "loopback" because it is still in use WARNING: cannot close connection for server "loopback2" because it is still in use +WARNING: cannot close connection for server "loopback" because it is still in use +WARNING: cannot close dropped server connection because it is still in useThe cause of the regression test failure is that the order of warning messages
is not stable. So I'm thinking to set client_min_messages to ERROR temporarily
when doing the above test.
Looks like we do suppress warnings/notices by setting
client_min_messages to ERROR/WARNING. For instance, "suppress warning
that depends on wal_level" and "Suppress NOTICE messages when
users/groups don't exist".
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/26 16:33, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 12:54 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/26 16:05, Tom Lane wrote:
Fujii Masao <masao.fujii@oss.nttdata.com> writes:
Thanks for the review! I fixed them and pushed the patch!
Buildfarm is very not happy ...
Yes.... I'm investigating that.
-- Return false as connections are still in use, warnings are issued. SELECT postgres_fdw_disconnect_all(); -WARNING: cannot close dropped server connection because it is still in use -WARNING: cannot close connection for server "loopback" because it is still in use WARNING: cannot close connection for server "loopback2" because it is still in use +WARNING: cannot close connection for server "loopback" because it is still in use +WARNING: cannot close dropped server connection because it is still in useThe cause of the regression test failure is that the order of warning messages
is not stable. So I'm thinking to set client_min_messages to ERROR temporarily
when doing the above test.Looks like we do suppress warnings/notices by setting
client_min_messages to ERROR/WARNING. For instance, "suppress warning
that depends on wal_level" and "Suppress NOTICE messages when
users/groups don't exist".
Yes, so I pushed that change to stabilize the regression test.
Let's keep checking how the results of buildfarm members are changed.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/01/26 16:39, Fujii Masao wrote:
On 2021/01/26 16:33, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 12:54 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/26 16:05, Tom Lane wrote:
Fujii Masao <masao.fujii@oss.nttdata.com> writes:
Thanks for the review! I fixed them and pushed the patch!
Buildfarm is very not happy ...
Yes.... I'm investigating that.
-- Return false as connections are still in use, warnings are issued. SELECT postgres_fdw_disconnect_all(); -WARNING: cannot close dropped server connection because it is still in use -WARNING: cannot close connection for server "loopback" because it is still in use WARNING: cannot close connection for server "loopback2" because it is still in use +WARNING: cannot close connection for server "loopback" because it is still in use +WARNING: cannot close dropped server connection because it is still in useThe cause of the regression test failure is that the order of warning messages
is not stable. So I'm thinking to set client_min_messages to ERROR temporarily
when doing the above test.Looks like we do suppress warnings/notices by setting
client_min_messages to ERROR/WARNING. For instance, "suppress warning
that depends on wal_level" and "Suppress NOTICE messages when
users/groups don't exist".Yes, so I pushed that change to stabilize the regression test.
Let's keep checking how the results of buildfarm members are changed.
+WARNING: roles created by regression test cases should have names starting with "regress_"
CREATE ROLE multi_conn_user2 SUPERUSER;
+WARNING: roles created by regression test cases should have names starting with "regress_"
Hmm... another failure happened.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Tue, Jan 26, 2021 at 1:27 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yes, so I pushed that change to stabilize the regression test.
Let's keep checking how the results of buildfarm members are changed.
Sorry, I'm unfamiliar with checking the system status on the build
farm website - https://buildfarm.postgresql.org/cgi-bin/show_failures.pl.
I'm trying to figure that out.
+WARNING: roles created by regression test cases should have names starting with "regress_" CREATE ROLE multi_conn_user2 SUPERUSER; +WARNING: roles created by regression test cases should have names starting with "regress_"Hmm... another failure happened.
My bad. I should have caught that earlier. I will take care in future.
Attaching a patch to fix it.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v1-0001-Stabilize-test-case-for-postgres_fdw_disconnect_a.patchapplication/octet-stream; name=v1-0001-Stabilize-test-case-for-postgres_fdw_disconnect_a.patchDownload
From 35341f65fb550874f023a66a5656529f5660722c Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Tue, 26 Jan 2021 13:33:28 +0530
Subject: [PATCH v1] Stabilize test case for postgres_fdw_disconnect_all()
---
.../postgres_fdw/expected/postgres_fdw.out | 24 +++++++++----------
contrib/postgres_fdw/sql/postgres_fdw.sql | 24 +++++++++----------
2 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 9d77d6e850..07e06e5bf7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9267,12 +9267,12 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- =============================================================================
-- test case for having multiple cached connections for a foreign server
-- =============================================================================
-CREATE ROLE multi_conn_user1 SUPERUSER;
-CREATE ROLE multi_conn_user2 SUPERUSER;
-CREATE USER MAPPING FOR multi_conn_user1 SERVER loopback;
-CREATE USER MAPPING FOR multi_conn_user2 SERVER loopback;
--- Will cache loopback connection with user mapping for multi_conn_user1
-SET ROLE multi_conn_user1;
+CREATE ROLE regress_multi_conn_user1 SUPERUSER;
+CREATE ROLE regress_multi_conn_user2 SUPERUSER;
+CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
+CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+-- Will cache loopback connection with user mapping for regress_multi_conn_user1
+SET ROLE regress_multi_conn_user1;
SELECT 1 FROM ft1 LIMIT 1;
?column?
----------
@@ -9280,8 +9280,8 @@ SELECT 1 FROM ft1 LIMIT 1;
(1 row)
RESET ROLE;
--- Will cache loopback connection with user mapping for multi_conn_user2
-SET ROLE multi_conn_user2;
+-- Will cache loopback connection with user mapping for regress_multi_conn_user2
+SET ROLE regress_multi_conn_user2;
SELECT 1 FROM ft1 LIMIT 1;
?column?
----------
@@ -9312,10 +9312,10 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
(0 rows)
-- Clean up
-DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
-DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
-DROP ROLE multi_conn_user1;
-DROP ROLE multi_conn_user2;
+DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
+DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+DROP ROLE regress_multi_conn_user1;
+DROP ROLE regress_multi_conn_user2;
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 62382d1a55..647192cf6a 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2801,18 +2801,18 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- =============================================================================
-- test case for having multiple cached connections for a foreign server
-- =============================================================================
-CREATE ROLE multi_conn_user1 SUPERUSER;
-CREATE ROLE multi_conn_user2 SUPERUSER;
-CREATE USER MAPPING FOR multi_conn_user1 SERVER loopback;
-CREATE USER MAPPING FOR multi_conn_user2 SERVER loopback;
+CREATE ROLE regress_multi_conn_user1 SUPERUSER;
+CREATE ROLE regress_multi_conn_user2 SUPERUSER;
+CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
+CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
--- Will cache loopback connection with user mapping for multi_conn_user1
-SET ROLE multi_conn_user1;
+-- Will cache loopback connection with user mapping for regress_multi_conn_user1
+SET ROLE regress_multi_conn_user1;
SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
--- Will cache loopback connection with user mapping for multi_conn_user2
-SET ROLE multi_conn_user2;
+-- Will cache loopback connection with user mapping for regress_multi_conn_user2
+SET ROLE regress_multi_conn_user2;
SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
@@ -2827,10 +2827,10 @@ SELECT postgres_fdw_disconnect('loopback');
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Clean up
-DROP USER MAPPING FOR multi_conn_user1 SERVER loopback;
-DROP USER MAPPING FOR multi_conn_user2 SERVER loopback;
-DROP ROLE multi_conn_user1;
-DROP ROLE multi_conn_user2;
+DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
+DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+DROP ROLE regress_multi_conn_user1;
+DROP ROLE regress_multi_conn_user2;
-- ===================================================================
-- batch insert
--
2.25.1
On 2021/01/26 17:07, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 1:27 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Yes, so I pushed that change to stabilize the regression test.
Let's keep checking how the results of buildfarm members are changed.Sorry, I'm unfamiliar with checking the system status on the build
farm website - https://buildfarm.postgresql.org/cgi-bin/show_failures.pl.
I'm trying to figure that out.+WARNING: roles created by regression test cases should have names starting with "regress_" CREATE ROLE multi_conn_user2 SUPERUSER; +WARNING: roles created by regression test cases should have names starting with "regress_"Hmm... another failure happened.
My bad. I should have caught that earlier. I will take care in future.
Attaching a patch to fix it.
Thanks for the patch! I also created that patch, confirmed that the test
successfully passed with -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS,
and pushed the patch.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Tue, Jan 26, 2021 at 1:55 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Thanks for the patch! I also created that patch, confirmed that the test
successfully passed with -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS,
and pushed the patch.
Thanks a lot!
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Jan 26, 2021 at 8:38 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I will post "keep_connections" GUC and "keep_connection" server level
option patches later.
Attaching v19 patch set for "keep_connections" GUC and
"keep_connection" server level option. Please review them further.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v19-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v19-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From c76ddc36ecb96d57f17e3ad2f8cb6a24cb61648e Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 27 Jan 2021 06:33:55 +0530
Subject: [PATCH v19] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 43 ++++++++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 20 +++++++++
doc/src/sgml/postgres-fdw.sgml | 44 +++++++++++++++++++
6 files changed, 135 insertions(+), 3 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ee0b4acf0b..3325ce00f2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 07e06e5bf7..5bfb70d19e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9316,6 +9316,49 @@ DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2ce42ce3f1..c5a76b7ad0 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -312,6 +312,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -527,6 +529,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_i);
static int get_batch_size_option(Relation rel);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 1f67b4d9fd..ceea46b304 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 647192cf6a..37d36b0484 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2832,6 +2832,26 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Set it on i.e. connections are cached.
+SET postgres_fdw.keep_connections TO on;
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 8d6abd4c54..f23b77e847 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -571,6 +571,42 @@ postgres=# SELECT postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. To close all connections
+ immediately use <function>postgres_fdw_disconnect</function> function.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -602,6 +638,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
the connections that are no longer necessary and then preventing them
from consuming the foreign server connections capacity too much.
</para>
+
+ <para>
+ Use configuration parameter <varname>postgres_fdw.keep_connections</varname>,
+ so that the local session doesn't keep any remote connections that are made
+ to the foreign servers within it. Default being <literal>on</literal>, when
+ set to <literal>off</literal>, each connection is discarded at the end of
+ local transaction in which it is used.
+ </para>
</sect2>
<sect2>
--
2.25.1
v19-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v19-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From 86490673a1f4ba7dc9808ab2be35c9e4c74a3faa Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Tue, 26 Jan 2021 12:43:47 +0530
Subject: [PATCH v19] postgres_fdw server level option, keep_connection to not
cache connection
This patch adds a new server level option, keep_connection,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 25 +++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 37 +++++++++++++++-
contrib/postgres_fdw/option.c | 9 +++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 17 +++++++
doc/src/sgml/postgres-fdw.sgml | 44 +++++++++++++++++++
5 files changed, 126 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 3325ce00f2..c8b6159568 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -62,6 +62,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Keep or discard this connection at the end of xact */
+ bool keep_connection;
} ConnCacheEntry;
/*
@@ -124,6 +126,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +265,15 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connection") == 0)
+ entry->keep_connection = defGetBoolean(def);
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +298,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_connection = true;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -956,15 +970,18 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
- * open a new connection.
+ * invalid or this connection is used in current xact and
+ * keep_connections GUC is false or GUC is true but the server level
+ * option is false, then discard it to recover. Next GetConnection will
+ * open a new connection. Note that keep_connections GUC overrides the
+ * server level keep_connection option.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ce24ab543f..1aa731e99f 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8939,7 +8939,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, keep_connection
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9359,6 +9359,41 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
loopback | t
(1 row)
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | valid
+-------------+-------
+ loopback | t
+(1 row)
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..75126a84bc 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connection") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -227,6 +228,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache the connection associated with this server, otherwise
+ * remove it at the end of the xact. Default is true.
+ */
+ {"keep_connection", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index b66985e279..28dcd747b1 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2849,6 +2849,23 @@ SELECT 1 FROM ft1 LIMIT 1;
-- Should output loopback.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- ===================================================================
+-- Test foreign server level option keep_connection
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connection option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connection 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connection was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output nothing.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connection 'on');
+-- loopback server connection should get cached.
+SELECT 1 FROM ft1 LIMIT 1;
+-- Should output loopback.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index f23b77e847..6986001ee2 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -490,6 +490,35 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connection</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ server connection that is made with a specific foreign server. It can be
+ specified for a foreign server. Default is <literal>on</literal>. When
+ set to <literal>off</literal>, the associated foreign server connection
+ is discarded at the end of the transaction.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -594,6 +623,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ overrides the server level <literal>keep_connection</literal> option.
+ Which means that when the configuration parameter is set to
+ <literal>on</literal>, irrespective of the server option
+ <literal>keep_connection</literal> setting, the connections are discarded.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -646,6 +683,13 @@ postgres=# SELECT postgres_fdw_disconnect_all();
set to <literal>off</literal>, each connection is discarded at the end of
local transaction in which it is used.
</para>
+
+ <para>
+ Use <xref linkend="sql-createserver"/> level option <literal>keep_connection</literal>
+ so that the local session doesn't keep the remote connection that is made to
+ the foreign server. Default being <literal>on</literal>, when set to <literal>off</literal>,
+ the connection is discarded at the end of local transaction in which it is used.
+ </para>
</sect2>
<sect2>
--
2.25.1
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Tue, Jan 26, 2021 at 1:55 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Thanks for the patch! I also created that patch, confirmed that the test
successfully passed with -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS,
and pushed the patch.
Thanks a lot!
Seems you're not out of the woods yet:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.
regards, tom lane
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Tue, Jan 26, 2021 at 1:55 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Thanks for the patch! I also created that patch, confirmed that the test
successfully passed with -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS,
and pushed the patch.Thanks a lot!
Seems you're not out of the woods yet:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.
Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.
Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.
I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.
Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?
I fear this patch needs to be reverted and redesigned.
regards, tom lane
On 2021/01/29 11:09, Tom Lane wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?
If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.
The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.
BTW, even if we change pgfdw_inval_callback() so that it doesn't close
the connection at all, ISTM that the results of postgres_fdw_get_connections()
would not be stable because entry->invalidated would vary based on
whether CLOBBER_CACHE_ALWAYS is used or not.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 29, 2021 at 10:28 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/29 11:09, Tom Lane wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.
I think we can remove disconnect_pg_server in pgfdw_inval_callback and
make entries only invalidated. Anyways, those connections can get
closed at the end of main txn in pgfdw_xact_callback. Thoughts?
If okay, I can make a patch for this.
BTW, even if we change pgfdw_inval_callback() so that it doesn't close
the connection at all, ISTM that the results of postgres_fdw_get_connections()
would not be stable because entry->invalidated would vary based on
whether CLOBBER_CACHE_ALWAYS is used or not.
Yes, after the above change (removing disconnect_pg_server in
pgfdw_inval_callback), our tests don't get stable because
postgres_fdw_get_connections shows the valid state of the connections.
I think we can change postgres_fdw_get_connections so that it only
shows the active connections server name but not valid state. Because,
the valid state is something dependent on the internal state change
and is not consistent with the user expectation but we are exposing it
to the user. Thoughts?
If okay, I can work on the patch for this.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jan 29, 2021 at 10:42 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.I think we can remove disconnect_pg_server in pgfdw_inval_callback and
make entries only invalidated. Anyways, those connections can get
closed at the end of main txn in pgfdw_xact_callback. Thoughts?If okay, I can make a patch for this.
Attaching a patch for this, which can be back patched.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v1-0001-Fix-connection-closure-issue-in-pgfdw_inval_callb.patchapplication/octet-stream; name=v1-0001-Fix-connection-closure-issue-in-pgfdw_inval_callb.patchDownload
From 52c8c5639d156763881660c872ad28cf63b491fa Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 29 Jan 2021 10:48:29 +0530
Subject: [PATCH v1] Fix connection closure issue in pgfdw_inval_callback
Do not close cached connections immediately in pgfdw_inval_callback
even though it's not being used, just mark it as invalidated so
that they get closed at the end of main txn in pgfdw_xact_callback.
---
contrib/postgres_fdw/connection.c | 16 +---------------
1 file changed, 1 insertion(+), 15 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ee0b4acf0b..3ef2c54e7d 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1119,21 +1119,7 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue)
entry->server_hashvalue == hashvalue) ||
(cacheid == USERMAPPINGOID &&
entry->mapping_hashvalue == hashvalue))
- {
- /*
- * Close the connection immediately if it's not used yet in this
- * transaction. Otherwise mark it as invalid so that
- * pgfdw_xact_callback() can close it at the end of this
- * transaction.
- */
- if (entry->xact_depth == 0)
- {
- elog(DEBUG3, "discarding connection %p", entry->conn);
- disconnect_pg_server(entry);
- }
- else
- entry->invalidated = true;
- }
+ entry->invalidated = true;
}
}
--
2.25.1
On 2021/01/29 14:12, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 10:28 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 11:09, Tom Lane wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.I think we can remove disconnect_pg_server in pgfdw_inval_callback and
make entries only invalidated. Anyways, those connections can get
closed at the end of main txn in pgfdw_xact_callback. Thoughts?
But this revives the connection leak issue. So isn't it better to
to do that after we confirm that the current code is really unsafe?
If okay, I can make a patch for this.
BTW, even if we change pgfdw_inval_callback() so that it doesn't close
the connection at all, ISTM that the results of postgres_fdw_get_connections()
would not be stable because entry->invalidated would vary based on
whether CLOBBER_CACHE_ALWAYS is used or not.Yes, after the above change (removing disconnect_pg_server in
pgfdw_inval_callback), our tests don't get stable because
postgres_fdw_get_connections shows the valid state of the connections.
I think we can change postgres_fdw_get_connections so that it only
shows the active connections server name but not valid state. Because,
the valid state is something dependent on the internal state change
and is not consistent with the user expectation but we are exposing it
to the user. Thoughts?
I don't think that's enough because even the following simple
queries return the different results, depending on whether
CLOBBER_CACHE_ALWAYS is used or not.
SELECT * FROM ft6; -- ft6 is the foreign table
SELECT server_name FROM postgres_fdw_get_connections();
When CLOBBER_CACHE_ALWAYS is used, postgres_fdw_get_connections()
returns no records because the connection is marked as invalidated,
and then closed at xact callback in SELECT query. Otherwise,
postgres_fdw_get_connections() returns at least one connection that
was established in the SELECT query.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 29, 2021 at 10:55 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/29 14:12, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 10:28 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 11:09, Tom Lane wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.I think we can remove disconnect_pg_server in pgfdw_inval_callback and
make entries only invalidated. Anyways, those connections can get
closed at the end of main txn in pgfdw_xact_callback. Thoughts?But this revives the connection leak issue. So isn't it better to
to do that after we confirm that the current code is really unsafe?
IMO, connections will not leak, because the invalidated connections
eventually will get closed in pgfdw_xact_callback at the main txn end.
IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:
1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown below
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}
2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.
We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.
Hope that's fine.
I will respond to postgres_fdw_get_connections issue separately.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jan 29, 2021 at 11:08 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Fri, Jan 29, 2021 at 10:55 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 14:12, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 10:28 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 11:09, Tom Lane wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.I think we can remove disconnect_pg_server in pgfdw_inval_callback and
make entries only invalidated. Anyways, those connections can get
closed at the end of main txn in pgfdw_xact_callback. Thoughts?But this revives the connection leak issue. So isn't it better to
to do that after we confirm that the current code is really unsafe?IMO, connections will not leak, because the invalidated connections
eventually will get closed in pgfdw_xact_callback at the main txn end.IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown belowif (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.
Also, see the original patch for the connection leak issue just does
option (1), see [1]/messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com. But in [2]/messages/by-id/f57dd9c3-0664-5f4c-41f0-0713047ae7b7@oss.nttdata.com and [3]/messages/by-id/CALj2ACVNjV1+72f3nVCngC7RsGSiGXZQ2mAzYx_Dij7oJpV8iA@mail.gmail.com, we chose option (2).
I feel, we can go for option (1), with the patch attached in [1]/messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com i.e.
having have_invalid_connections whenever any connection gets invalided
so that we don't quickly exit in pgfdw_xact_callback and the
invalidated connections get closed properly. Thoughts?
static void
pgfdw_xact_callback(XactEvent event, void *arg)
{
HASH_SEQ_STATUS scan;
ConnCacheEntry *entry;
/* Quick exit if no connections were touched in this transaction. */
if (!xact_got_connection)
return;
[1]: /messages/by-id/CALj2ACVNcGH_6qLY-4_tXz8JLvA+4yeBThRfxMz7Oxbk1aHcpQ@mail.gmail.com
[2]: /messages/by-id/f57dd9c3-0664-5f4c-41f0-0713047ae7b7@oss.nttdata.com
[3]: /messages/by-id/CALj2ACVNjV1+72f3nVCngC7RsGSiGXZQ2mAzYx_Dij7oJpV8iA@mail.gmail.com
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jan 29, 2021 at 10:55 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
BTW, even if we change pgfdw_inval_callback() so that it doesn't close
the connection at all, ISTM that the results of postgres_fdw_get_connections()
would not be stable because entry->invalidated would vary based on
whether CLOBBER_CACHE_ALWAYS is used or not.Yes, after the above change (removing disconnect_pg_server in
pgfdw_inval_callback), our tests don't get stable because
postgres_fdw_get_connections shows the valid state of the connections.
I think we can change postgres_fdw_get_connections so that it only
shows the active connections server name but not valid state. Because,
the valid state is something dependent on the internal state change
and is not consistent with the user expectation but we are exposing it
to the user. Thoughts?I don't think that's enough because even the following simple
queries return the different results, depending on whether
CLOBBER_CACHE_ALWAYS is used or not.SELECT * FROM ft6; -- ft6 is the foreign table
SELECT server_name FROM postgres_fdw_get_connections();When CLOBBER_CACHE_ALWAYS is used, postgres_fdw_get_connections()
returns no records because the connection is marked as invalidated,
and then closed at xact callback in SELECT query. Otherwise,
postgres_fdw_get_connections() returns at least one connection that
was established in the SELECT query.
Right. In that case, after changing postgres_fdw_get_connections() so
that it doesn't output the valid state of the connections at all, we
can have all the new function test cases inside an explicit txn block.
So even if the clobber cache invalidates the connections, they don't
get closed until the end of main xact, the tests will be stable.
Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/29 14:53, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 10:55 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:BTW, even if we change pgfdw_inval_callback() so that it doesn't close
the connection at all, ISTM that the results of postgres_fdw_get_connections()
would not be stable because entry->invalidated would vary based on
whether CLOBBER_CACHE_ALWAYS is used or not.Yes, after the above change (removing disconnect_pg_server in
pgfdw_inval_callback), our tests don't get stable because
postgres_fdw_get_connections shows the valid state of the connections.
I think we can change postgres_fdw_get_connections so that it only
shows the active connections server name but not valid state. Because,
the valid state is something dependent on the internal state change
and is not consistent with the user expectation but we are exposing it
to the user. Thoughts?I don't think that's enough because even the following simple
queries return the different results, depending on whether
CLOBBER_CACHE_ALWAYS is used or not.SELECT * FROM ft6; -- ft6 is the foreign table
SELECT server_name FROM postgres_fdw_get_connections();When CLOBBER_CACHE_ALWAYS is used, postgres_fdw_get_connections()
returns no records because the connection is marked as invalidated,
and then closed at xact callback in SELECT query. Otherwise,
postgres_fdw_get_connections() returns at least one connection that
was established in the SELECT query.Right. In that case, after changing postgres_fdw_get_connections() so
that it doesn't output the valid state of the connections at all, we
You're thinking to get rid of "valid" column? Or hide it from the test query
(e.g., SELECT server_name from postgres_fdw_get_connections())?
can have all the new function test cases inside an explicit txn block.
So even if the clobber cache invalidates the connections, they don't
get closed until the end of main xact, the tests will be stable.
Thoughts?
Also if there are cached connections before starting that transaction,
they should be closed or established again before executing
postgres_fdw_get_connections(). Otherwise, those connections are
returned from postgres_fdw_get_connections() when
CLOBBER_CACHE_ALWAYS is not used, but not when it's used.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 29, 2021 at 11:38 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/29 14:53, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 10:55 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:BTW, even if we change pgfdw_inval_callback() so that it doesn't close
the connection at all, ISTM that the results of postgres_fdw_get_connections()
would not be stable because entry->invalidated would vary based on
whether CLOBBER_CACHE_ALWAYS is used or not.Yes, after the above change (removing disconnect_pg_server in
pgfdw_inval_callback), our tests don't get stable because
postgres_fdw_get_connections shows the valid state of the connections.
I think we can change postgres_fdw_get_connections so that it only
shows the active connections server name but not valid state. Because,
the valid state is something dependent on the internal state change
and is not consistent with the user expectation but we are exposing it
to the user. Thoughts?I don't think that's enough because even the following simple
queries return the different results, depending on whether
CLOBBER_CACHE_ALWAYS is used or not.SELECT * FROM ft6; -- ft6 is the foreign table
SELECT server_name FROM postgres_fdw_get_connections();When CLOBBER_CACHE_ALWAYS is used, postgres_fdw_get_connections()
returns no records because the connection is marked as invalidated,
and then closed at xact callback in SELECT query. Otherwise,
postgres_fdw_get_connections() returns at least one connection that
was established in the SELECT query.Right. In that case, after changing postgres_fdw_get_connections() so
that it doesn't output the valid state of the connections at all, weYou're thinking to get rid of "valid" column? Or hide it from the test query
(e.g., SELECT server_name from postgres_fdw_get_connections())?
I'm thinking we can get rid of the "valid" column from the
postgres_fdw_get_connections() function, not from the tests. Seems
like we are exposing some internal state(connection is valid or not)
which can change because of internal events. And also with the
existing postgres_fdw_get_connections(), the valid will always be true
if the user calls postgres_fdw_get_connections() outside an explicit
xact block, it can become false only when it's used in an explicit txn
block. So, the valid column may not be much useful for the user.
Thoughts?
can have all the new function test cases inside an explicit txn block.
So even if the clobber cache invalidates the connections, they don't
get closed until the end of main xact, the tests will be stable.
Thoughts?Also if there are cached connections before starting that transaction,
they should be closed or established again before executing
postgres_fdw_get_connections(). Otherwise, those connections are
returned from postgres_fdw_get_connections() when
CLOBBER_CACHE_ALWAYS is not used, but not when it's used.
Yes, we need to move the test to the place where cache wouldn't have
been initialized yet or no foreign connection has been made yet in the
session.
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
<<<<<<<<<<<<MAY BE HERE>>>>>>>>>>>>
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
\set VERBOSITY terse
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work
ALTER SERVER loopback OPTIONS (SET dbname 'no such database');
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail
DO $d$
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/29 14:46, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 11:08 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Fri, Jan 29, 2021 at 10:55 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 14:12, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 10:28 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 11:09, Tom Lane wrote:
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> writes:
On Fri, Jan 29, 2021 at 1:52 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=trilobite&dt=2021-01-26%2019%3A59%3A40
This is a CLOBBER_CACHE_ALWAYS build, so I suspect what it's
telling us is that the patch's behavior is unstable in the face
of unexpected cache flushes.Thanks a lot! It looks like the syscache invalidation messages are
generated too frequently with -DCLOBBER_CACHE_ALWAYS build due to
which pgfdw_inval_callback gets called many times in which the cached
entries are marked as invalid and closed if they are not used in the
txn. The new function postgres_fdw_get_connections outputs the
information of the cached connections such as name if the connection
is still open and their validity. Hence the output of the
postgres_fdw_get_connections became unstable in the buildfarm member.
I will further analyze making tests stable, meanwhile any suggestions
are welcome.I do not think you should regard this as "we need to hack the test
to make it stable". I think you should regard this as "this is a
bug". A cache flush should not cause user-visible state changes.
In particular, the above analysis implies that you think a cache
flush is equivalent to end-of-transaction, which it absolutely
is not.Also, now that I've looked at pgfdw_inval_callback, it scares
the heck out of me. Actually disconnecting a connection during
a cache inval callback seems quite unsafe --- what if that happens
while we're using the connection?If the connection is still used in the transaction, pgfdw_inval_callback()
marks it as invalidated and doesn't close it. So I was not thinking that
this is so unsafe.The disconnection code in pgfdw_inval_callback() was added in commit
e3ebcca843 to fix connection leak issue, and it's back-patched. If this
change is really unsafe, we need to revert it immediately at least from back
branches because the next minor release is scheduled soon.I think we can remove disconnect_pg_server in pgfdw_inval_callback and
make entries only invalidated. Anyways, those connections can get
closed at the end of main txn in pgfdw_xact_callback. Thoughts?But this revives the connection leak issue. So isn't it better to
to do that after we confirm that the current code is really unsafe?IMO, connections will not leak, because the invalidated connections
eventually will get closed in pgfdw_xact_callback at the main txn end.IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown belowif (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.Also, see the original patch for the connection leak issue just does
option (1), see [1]. But in [2] and [3], we chose option (2).I feel, we can go for option (1), with the patch attached in [1] i.e.
having have_invalid_connections whenever any connection gets invalided
so that we don't quickly exit in pgfdw_xact_callback and the
invalidated connections get closed properly. Thoughts?
Before going for (1) or something, I'd like to understand what the actual
issue of (2), i.e., the current code is. Otherwise other approaches might
have the same issue.
Regarding (1), as far as I understand correctly, even when the transaction
doesn't use foreign tables at all, it needs to scan the connection cache
entries if necessary. I was thinking to avoid this. I guess that this doesn't
work with at least the postgres_fdw 2PC patch that Sawada-san is proposing
because with the patch the commit/rollback callback is performed only
for the connections used in the transaction.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 29, 2021 at 11:54 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown belowif (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.Also, see the original patch for the connection leak issue just does
option (1), see [1]. But in [2] and [3], we chose option (2).I feel, we can go for option (1), with the patch attached in [1] i.e.
having have_invalid_connections whenever any connection gets invalided
so that we don't quickly exit in pgfdw_xact_callback and the
invalidated connections get closed properly. Thoughts?Before going for (1) or something, I'd like to understand what the actual
issue of (2), i.e., the current code is. Otherwise other approaches might
have the same issue.
The problem with option (2) is that because of CLOBBER_CACHE_ALWAYS,
pgfdw_inval_callback is getting called many times and the connections
that are not used i..e xact_depth == 0, are getting disconnected
there, so we are not seeing the consistent results for
postgres_fdw_get_connectionstest cases. If the connections are being
used within the xact, then the valid option for those connections are
being shown as false again making postgres_fdw_get_connections output
inconsistent. This is what happened on the build farm member with
CLOBBER_CACHE_ALWAYS build.
So if we go with option (1), get rid of valid state from
postgres_fdw_get_connectionstest and having the test cases inside an
explicit xact block at the beginning of the postgres_fdw.sql test
file, we don't see CLOBBER_CACHE_ALWAYS inconsistencies. I'm not sure
if this is the correct way.
Regarding (1), as far as I understand correctly, even when the transaction
doesn't use foreign tables at all, it needs to scan the connection cache
entries if necessary. I was thinking to avoid this. I guess that this doesn't
work with at least the postgres_fdw 2PC patch that Sawada-san is proposing
because with the patch the commit/rollback callback is performed only
for the connections used in the transaction.
You mean to say, pgfdw_xact_callback will not get called when the xact
uses no foreign server connection or is it that pgfdw_xact_callback
gets called but exits quickly from it? I'm not sure what the 2PC patch
does.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/29 15:44, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 11:54 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown belowif (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.Also, see the original patch for the connection leak issue just does
option (1), see [1]. But in [2] and [3], we chose option (2).I feel, we can go for option (1), with the patch attached in [1] i.e.
having have_invalid_connections whenever any connection gets invalided
so that we don't quickly exit in pgfdw_xact_callback and the
invalidated connections get closed properly. Thoughts?Before going for (1) or something, I'd like to understand what the actual
issue of (2), i.e., the current code is. Otherwise other approaches might
have the same issue.The problem with option (2) is that because of CLOBBER_CACHE_ALWAYS,
pgfdw_inval_callback is getting called many times and the connections
that are not used i..e xact_depth == 0, are getting disconnected
there, so we are not seeing the consistent results for
postgres_fdw_get_connectionstest cases. If the connections are being
used within the xact, then the valid option for those connections are
being shown as false again making postgres_fdw_get_connections output
inconsistent. This is what happened on the build farm member with
CLOBBER_CACHE_ALWAYS build.
But if the issue is only the inconsistency of test results,
we can go with the option (2)? Even with (2), we can make the test
stable by removing "valid" column and executing
postgres_fdw_get_connections() within the transaction?
So if we go with option (1), get rid of valid state from
postgres_fdw_get_connectionstest and having the test cases inside an
explicit xact block at the beginning of the postgres_fdw.sql test
file, we don't see CLOBBER_CACHE_ALWAYS inconsistencies. I'm not sure
if this is the correct way.Regarding (1), as far as I understand correctly, even when the transaction
doesn't use foreign tables at all, it needs to scan the connection cache
entries if necessary. I was thinking to avoid this. I guess that this doesn't
work with at least the postgres_fdw 2PC patch that Sawada-san is proposing
because with the patch the commit/rollback callback is performed only
for the connections used in the transaction.You mean to say, pgfdw_xact_callback will not get called when the xact
uses no foreign server connection or is it that pgfdw_xact_callback
gets called but exits quickly from it? I'm not sure what the 2PC patch
does.
Maybe it's chance to review the patch! ;P
BTW his patch tries to add new callback interfaces for commit/rollback of
foreign transactions, and make postgres_fdw use them instead of
XactCallback. And those new interfaces are executed only when
the transaction has started the foreign transactions.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 29, 2021 at 12:36 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
On 2021/01/29 15:44, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 11:54 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown belowif (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.Also, see the original patch for the connection leak issue just does
option (1), see [1]. But in [2] and [3], we chose option (2).I feel, we can go for option (1), with the patch attached in [1] i.e.
having have_invalid_connections whenever any connection gets invalided
so that we don't quickly exit in pgfdw_xact_callback and the
invalidated connections get closed properly. Thoughts?Before going for (1) or something, I'd like to understand what the actual
issue of (2), i.e., the current code is. Otherwise other approaches might
have the same issue.The problem with option (2) is that because of CLOBBER_CACHE_ALWAYS,
pgfdw_inval_callback is getting called many times and the connections
that are not used i..e xact_depth == 0, are getting disconnected
there, so we are not seeing the consistent results for
postgres_fdw_get_connectionstest cases. If the connections are being
used within the xact, then the valid option for those connections are
being shown as false again making postgres_fdw_get_connections output
inconsistent. This is what happened on the build farm member with
CLOBBER_CACHE_ALWAYS build.But if the issue is only the inconsistency of test results,
we can go with the option (2)? Even with (2), we can make the test
stable by removing "valid" column and executing
postgres_fdw_get_connections() within the transaction?
Hmmm, and we should have the tests at the start of the file
postgres_fdw.sql before even we make any foreign server connections.
If okay, I can prepare the patch and run with clobber cache build locally.
So if we go with option (1), get rid of valid state from
postgres_fdw_get_connectionstest and having the test cases inside an
explicit xact block at the beginning of the postgres_fdw.sql test
file, we don't see CLOBBER_CACHE_ALWAYS inconsistencies. I'm not sure
if this is the correct way.Regarding (1), as far as I understand correctly, even when the transaction
doesn't use foreign tables at all, it needs to scan the connection cache
entries if necessary. I was thinking to avoid this. I guess that this doesn't
work with at least the postgres_fdw 2PC patch that Sawada-san is proposing
because with the patch the commit/rollback callback is performed only
for the connections used in the transaction.You mean to say, pgfdw_xact_callback will not get called when the xact
uses no foreign server connection or is it that pgfdw_xact_callback
gets called but exits quickly from it? I'm not sure what the 2PC patch
does.Maybe it's chance to review the patch! ;P
BTW his patch tries to add new callback interfaces for commit/rollback of
foreign transactions, and make postgres_fdw use them instead of
XactCallback. And those new interfaces are executed only when
the transaction has started the foreign transactions.
IMHO, it's better to keep it as a separate discussion. I will try to
review that patch later.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/29 16:12, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 12:36 PM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:On 2021/01/29 15:44, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 11:54 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:IIRC, when we were finding a way to close the invalidated connections
so that they don't leaked, we had two options:1) let those connections (whether currently being used in the xact or
not) get marked invalidated in pgfdw_inval_callback and closed in
pgfdw_xact_callback at the main txn end as shown belowif (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated). ----> by adding this
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
}2) close the unused connections right away in pgfdw_inval_callback
instead of marking them invalidated. Mark used connections as
invalidated in pgfdw_inval_callback and close them in
pgfdw_xact_callback at the main txn end.We went with option (2) because we thought this would ease some burden
on pgfdw_xact_callback closing a lot of invalid connections at once.Also, see the original patch for the connection leak issue just does
option (1), see [1]. But in [2] and [3], we chose option (2).I feel, we can go for option (1), with the patch attached in [1] i.e.
having have_invalid_connections whenever any connection gets invalided
so that we don't quickly exit in pgfdw_xact_callback and the
invalidated connections get closed properly. Thoughts?Before going for (1) or something, I'd like to understand what the actual
issue of (2), i.e., the current code is. Otherwise other approaches might
have the same issue.The problem with option (2) is that because of CLOBBER_CACHE_ALWAYS,
pgfdw_inval_callback is getting called many times and the connections
that are not used i..e xact_depth == 0, are getting disconnected
there, so we are not seeing the consistent results for
postgres_fdw_get_connectionstest cases. If the connections are being
used within the xact, then the valid option for those connections are
being shown as false again making postgres_fdw_get_connections output
inconsistent. This is what happened on the build farm member with
CLOBBER_CACHE_ALWAYS build.But if the issue is only the inconsistency of test results,
we can go with the option (2)? Even with (2), we can make the test
stable by removing "valid" column and executing
postgres_fdw_get_connections() within the transaction?Hmmm, and we should have the tests at the start of the file
postgres_fdw.sql before even we make any foreign server connections.
We don't need to move the test if we always call postgres_fdw_disconnect_all() just before starting new transaction and calling postgres_fdw_get_connections() as follows?
SELECT 1 FROM postgres_fdw_disconnect_all();
BEGIN;
...
SELECT * FROM postgres_fdw_get_connections();
...
If okay, I can prepare the patch and run with clobber cache build locally.
Many thanks!
So if we go with option (1), get rid of valid state from
postgres_fdw_get_connectionstest and having the test cases inside an
explicit xact block at the beginning of the postgres_fdw.sql test
file, we don't see CLOBBER_CACHE_ALWAYS inconsistencies. I'm not sure
if this is the correct way.Regarding (1), as far as I understand correctly, even when the transaction
doesn't use foreign tables at all, it needs to scan the connection cache
entries if necessary. I was thinking to avoid this. I guess that this doesn't
work with at least the postgres_fdw 2PC patch that Sawada-san is proposing
because with the patch the commit/rollback callback is performed only
for the connections used in the transaction.You mean to say, pgfdw_xact_callback will not get called when the xact
uses no foreign server connection or is it that pgfdw_xact_callback
gets called but exits quickly from it? I'm not sure what the 2PC patch
does.Maybe it's chance to review the patch! ;P
BTW his patch tries to add new callback interfaces for commit/rollback of
foreign transactions, and make postgres_fdw use them instead of
XactCallback. And those new interfaces are executed only when
the transaction has started the foreign transactions.IMHO, it's better to keep it as a separate discussion.
Yes, of course!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Fri, Jan 29, 2021 at 1:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
But if the issue is only the inconsistency of test results,
we can go with the option (2)? Even with (2), we can make the test
stable by removing "valid" column and executing
postgres_fdw_get_connections() within the transaction?Hmmm, and we should have the tests at the start of the file
postgres_fdw.sql before even we make any foreign server connections.We don't need to move the test if we always call postgres_fdw_disconnect_all() just before starting new transaction and calling postgres_fdw_get_connections() as follows?
SELECT 1 FROM postgres_fdw_disconnect_all();
BEGIN;
...
SELECT * FROM postgres_fdw_get_connections();
...
Yes, that works, but we cannot show true/false for the
postgres_fdw_disconnect_all output.
I will post the patch soon. Thanks a lot.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Jan 29, 2021 at 1:24 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Fri, Jan 29, 2021 at 1:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
But if the issue is only the inconsistency of test results,
we can go with the option (2)? Even with (2), we can make the test
stable by removing "valid" column and executing
postgres_fdw_get_connections() within the transaction?Hmmm, and we should have the tests at the start of the file
postgres_fdw.sql before even we make any foreign server connections.We don't need to move the test if we always call postgres_fdw_disconnect_all() just before starting new transaction and calling postgres_fdw_get_connections() as follows?
SELECT 1 FROM postgres_fdw_disconnect_all();
BEGIN;
...
SELECT * FROM postgres_fdw_get_connections();
...Yes, that works, but we cannot show true/false for the
postgres_fdw_disconnect_all output.I will post the patch soon. Thanks a lot.
Attaching a patch that has following changes: 1) Now,
postgres_fdw_get_connections will only return set of active
connections server names not their valid state 2) The functions
postgres_fdw_get_connections, postgres_fdw_disconnect and
postgres_fdw_disconnect_all are now being tested within an explicit
xact block, this way the tests are more stable even with clobber cache
always builds.
I tested the patch here on my development system with
-DCLOBBER_CACHE_ALWAYS configuration, the tests look consistent.
Please review the patch.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v1-0001-Don-t-return-valid-state-in-postgres_fdw_get_conn.patchapplication/octet-stream; name=v1-0001-Don-t-return-valid-state-in-postgres_fdw_get_conn.patchDownload
From 8e249f01bb50e5db303b7d14e6a3f3409001bee6 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Fri, 29 Jan 2021 15:27:04 +0530
Subject: [PATCH v1] Don't return valid state in postgres_fdw_get_connections
Don't show the the cached connection's valid state in
postgres_fdw_get_connections output, as it can be inconsistent
due to internal events such as invalidations.
Also, make tests added by 708d165 and 411ae64 so that they return
consistent results even when sys cache invalidations occur too
frequently and the cached connections get invalidated in
pgfdw_inval_callback.
---
contrib/postgres_fdw/connection.c | 26 +-
.../postgres_fdw/expected/postgres_fdw.out | 223 ++++++------------
.../postgres_fdw/postgres_fdw--1.0--1.1.sql | 7 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 89 +++----
doc/src/sgml/postgres-fdw.sgml | 22 +-
5 files changed, 123 insertions(+), 244 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ee0b4acf0b..436f9bfc4f 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1350,19 +1350,14 @@ exit: ;
* List active foreign server connections.
*
* This function takes no input parameter and returns setof record made of
- * following values:
- * - server_name - server name of active connection. In case the foreign server
- * is dropped but still the connection is active, then the server name will
- * be NULL in output.
- * - valid - true/false representing whether the connection is valid or not.
- * Note that the connections can get invalidated in pgfdw_inval_callback.
+ * server_name i.e. server name of active connection.
*
* No records are returned when there are no cached connections at all.
*/
Datum
postgres_fdw_get_connections(PG_FUNCTION_ARGS)
{
-#define POSTGRES_FDW_GET_CONNECTIONS_COLS 2
+#define POSTGRES_FDW_GET_CONNECTIONS_COLS 1
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
@@ -1430,9 +1425,11 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
*
* Even though the server is dropped in the current transaction, the
* cache can still have associated active connection entry, say we
- * call such connections dangling. Since we can not fetch the server
- * name from system catalogs for dangling connections, instead we show
- * NULL value for server name in output.
+ * call such connections dangling. We can not fetch the server name
+ * from system catalogs for dangling connections and the corresponding
+ * cache entries would have been invalidated in pgfdw_inval_callback at
+ * the end of drop server command. Hence the dangling connections are
+ * not worth showing in the output, we skip them.
*
* We could have done better by storing the server name in the cache
* entry instead of server oid so that it could be used in the output.
@@ -1457,14 +1454,15 @@ postgres_fdw_get_connections(PG_FUNCTION_ARGS)
*/
Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated);
- /* Show null, if no server name was found */
- nulls[0] = true;
+ /*
+ * It doesn't make sense to show this entry in the output with a
+ * NULL server_name as it will be closed at the xact end.
+ */
+ continue;
}
else
values[0] = CStringGetTextDatum(server->servername);
- values[1] = BoolGetDatum(!entry->invalidated);
-
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 07e06e5bf7..549649e7c0 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -17,10 +17,6 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
- EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
- OPTIONS (dbname '$$||current_database()||$$',
- port '$$||current_setting('port')||$$'
- )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
@@ -28,7 +24,6 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
-CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -144,11 +139,6 @@ CREATE FOREIGN TABLE ft7 (
c2 int NOT NULL,
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
-CREATE FOREIGN TABLE ft8 (
- c1 int NOT NULL,
- c2 int NOT NULL,
- c3 text
-) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -220,8 +210,7 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
- public | ft8 | loopback4 | (schema_name 'S 1', table_name 'T 4') |
-(7 rows)
+(6 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9066,15 +9055,21 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- =============================================================================
-- test connection invalidation cases and postgres_fdw_get_connections function
-- =============================================================================
--- This test case is for closing the connection in pgfdw_xact_callback
-BEGIN;
--- List all the existing cached connections. Only loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback2 | t
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+ ?column?
+----------
+ 1
(1 row)
+-- No cached connections, so no records should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
+-- This test case is for closing the connection in pgfdw_xact_callback
+BEGIN;
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
?column?
@@ -9088,56 +9083,44 @@ SELECT 1 FROM ft7 LIMIT 1;
1
(1 row)
--- List all the existing cached connections. loopback and loopback3
--- also should be output as valid connections.
+-- List all the existing cached connections. loopback and loopback3 should be
+-- output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback2 | t
- loopback3 | t
-(3 rows)
+ server_name
+-------------
+ loopback
+ loopback3
+(2 rows)
--- Connection is not closed at the end of the alter statement in
--- pgfdw_inval_callback. That's because the connection is in midst of this
--- xact, it is just marked as invalid.
+-- Connections are not closed at the end of the alter statement in
+-- pgfdw_inval_callback. That's because the connections are in midst of this
+-- xact, they are just marked as invalid.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
DROP SERVER loopback3 CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to user mapping for public on server loopback3
drop cascades to foreign table ft7
--- List all the existing cached connections. loopback and loopback3
--- should be output as invalid connections. Also the server name for
--- loopback3 should be NULL because the server was dropped.
+-- List all the existing cached connections. loopback should be output.
+-- loopback3 is not shown in the output because the server is dropped.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | f
- loopback2 | t
- | f
-(3 rows)
+ server_name
+-------------
+ loopback
+(1 row)
--- The invalid connection gets closed in pgfdw_xact_callback during commit.
+-- The above invalid connections get closed in pgfdw_xact_callback during commit.
COMMIT;
--- List all the existing cached connections. loopback and loopback3
--- should not be output because they should be closed at the end of
--- the above transaction.
+-- All cached connections were closed while committing above xact, so no
+-- records should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback2 | t
-(1 row)
+ server_name
+-------------
+(0 rows)
-- =======================================================================
-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
-- =======================================================================
--- Return true as all cached connections are closed.
-SELECT postgres_fdw_disconnect_all();
- postgres_fdw_disconnect_all
------------------------------
- t
-(1 row)
-
+BEGIN;
-- Ensure to cache loopback connection.
SELECT 1 FROM ft1 LIMIT 1;
?column?
@@ -9145,7 +9128,6 @@ SELECT 1 FROM ft1 LIMIT 1;
1
(1 row)
-BEGIN;
-- Ensure to cache loopback2 connection.
SELECT 1 FROM ft6 LIMIT 1;
?column?
@@ -9156,65 +9138,30 @@ SELECT 1 FROM ft6 LIMIT 1;
-- List all the existing cached connections. loopback and loopback2 should be
-- output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback2 | t
+ server_name
+-------------
+ loopback
+ loopback2
(2 rows)
--- Issue a warning and return false as loopback2 connection is still in use and
+-- Issue a warning and return false as loopback connection is still in use and
-- can not be closed.
-SELECT postgres_fdw_disconnect('loopback2');
-WARNING: cannot close connection for server "loopback2" because it is still in use
+SELECT postgres_fdw_disconnect('loopback');
+WARNING: cannot close connection for server "loopback" because it is still in use
postgres_fdw_disconnect
-------------------------
f
(1 row)
--- Close loopback connection, return true and issue a warning as loopback2
--- connection is still in use and can not be closed.
-SELECT postgres_fdw_disconnect_all();
-WARNING: cannot close connection for server "loopback2" because it is still in use
- postgres_fdw_disconnect_all
------------------------------
- t
-(1 row)
-
--- List all the existing cached connections. loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback2 | t
-(1 row)
-
--- Ensure to cache loopback connection.
-SELECT 1 FROM ft1 LIMIT 1;
- ?column?
-----------
- 1
-(1 row)
-
--- Ensure to cache loopback4 connection.
-SELECT 1 FROM ft8 LIMIT 1;
- ?column?
-----------
- 1
-(1 row)
-
--- List all the existing cached connections. loopback, loopback2, loopback4
--- should be output.
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback2 | t
- loopback4 | t
-(3 rows)
+ server_name
+-------------
+ loopback
+ loopback2
+(2 rows)
-DROP SERVER loopback4 CASCADE;
-NOTICE: drop cascades to 2 other objects
-DETAIL: drop cascades to user mapping for public on server loopback4
-drop cascades to foreign table ft8
-- Return false as connections are still in use, warnings are issued.
-- But disable warnings temporarily because the order of them is not stable.
SET client_min_messages = 'ERROR';
@@ -9226,42 +9173,17 @@ SELECT postgres_fdw_disconnect_all();
RESET client_min_messages;
COMMIT;
--- Close loopback2 connection and return true.
-SELECT postgres_fdw_disconnect('loopback2');
- postgres_fdw_disconnect
--------------------------
- t
-(1 row)
-
--- List all the existing cached connections. loopback should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
-(1 row)
-
--- Return false as loopback2 connectin is closed already.
-SELECT postgres_fdw_disconnect('loopback2');
- postgres_fdw_disconnect
--------------------------
- f
-(1 row)
-
--- Return an error as there is no foreign server with given name.
-SELECT postgres_fdw_disconnect('unknownserver');
-ERROR: server "unknownserver" does not exist
--- Close loopback connection and return true.
-SELECT postgres_fdw_disconnect_all();
- postgres_fdw_disconnect_all
------------------------------
- t
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+ ?column?
+----------
+ 1
(1 row)
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
+-- No cached connections, so no records should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
+ server_name
+-------------
(0 rows)
-- =============================================================================
@@ -9271,6 +9193,7 @@ CREATE ROLE regress_multi_conn_user1 SUPERUSER;
CREATE ROLE regress_multi_conn_user2 SUPERUSER;
CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+BEGIN;
-- Will cache loopback connection with user mapping for regress_multi_conn_user1
SET ROLE regress_multi_conn_user1;
SELECT 1 FROM ft1 LIMIT 1;
@@ -9291,24 +9214,24 @@ SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
-- Should output two connections for loopback server
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback | t
+ server_name
+-------------
+ loopback
+ loopback
(2 rows)
--- Close loopback connections and return true.
-SELECT postgres_fdw_disconnect('loopback');
- postgres_fdw_disconnect
--------------------------
- t
+COMMIT;
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+ ?column?
+----------
+ 1
(1 row)
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
+-- No cached connections, so no records should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
+ server_name
+-------------
(0 rows)
-- Clean up
diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
index ed4ca378d4..58b8e54147 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql
@@ -3,9 +3,10 @@
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit
-CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text,
- OUT valid boolean)
-RETURNS SETOF record
+CREATE TYPE postgres_fdw_get_connections_result_type AS (server_name text);
+
+CREATE FUNCTION postgres_fdw_get_connections ()
+RETURNS SETOF postgres_fdw_get_connections_result_type
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 647192cf6a..6ef965d4bd 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -19,10 +19,6 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
- EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
- OPTIONS (dbname '$$||current_database()||$$',
- port '$$||current_setting('port')||$$'
- )$$;
END;
$d$;
@@ -31,7 +27,6 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
-CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -158,12 +153,6 @@ CREATE FOREIGN TABLE ft7 (
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
-CREATE FOREIGN TABLE ft8 (
- c1 int NOT NULL,
- c2 int NOT NULL,
- c3 text
-) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2723,79 +2712,58 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- =============================================================================
-- test connection invalidation cases and postgres_fdw_get_connections function
-- =============================================================================
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+-- No cached connections, so no records should be output.
+SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
--- List all the existing cached connections. Only loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
SELECT 1 FROM ft7 LIMIT 1;
--- List all the existing cached connections. loopback and loopback3
--- also should be output as valid connections.
+-- List all the existing cached connections. loopback and loopback3 should be
+-- output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Connection is not closed at the end of the alter statement in
--- pgfdw_inval_callback. That's because the connection is in midst of this
--- xact, it is just marked as invalid.
+-- Connections are not closed at the end of the alter statement in
+-- pgfdw_inval_callback. That's because the connections are in midst of this
+-- xact, they are just marked as invalid.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
DROP SERVER loopback3 CASCADE;
--- List all the existing cached connections. loopback and loopback3
--- should be output as invalid connections. Also the server name for
--- loopback3 should be NULL because the server was dropped.
+-- List all the existing cached connections. loopback should be output.
+-- loopback3 is not shown in the output because the server is dropped.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- The invalid connection gets closed in pgfdw_xact_callback during commit.
+-- The above invalid connections get closed in pgfdw_xact_callback during commit.
COMMIT;
--- List all the existing cached connections. loopback and loopback3
--- should not be output because they should be closed at the end of
--- the above transaction.
+-- All cached connections were closed while committing above xact, so no
+-- records should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- =======================================================================
-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
-- =======================================================================
--- Return true as all cached connections are closed.
-SELECT postgres_fdw_disconnect_all();
+BEGIN;
-- Ensure to cache loopback connection.
SELECT 1 FROM ft1 LIMIT 1;
-BEGIN;
-- Ensure to cache loopback2 connection.
SELECT 1 FROM ft6 LIMIT 1;
-- List all the existing cached connections. loopback and loopback2 should be
-- output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Issue a warning and return false as loopback2 connection is still in use and
+-- Issue a warning and return false as loopback connection is still in use and
-- can not be closed.
-SELECT postgres_fdw_disconnect('loopback2');
--- Close loopback connection, return true and issue a warning as loopback2
--- connection is still in use and can not be closed.
-SELECT postgres_fdw_disconnect_all();
--- List all the existing cached connections. loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Ensure to cache loopback connection.
-SELECT 1 FROM ft1 LIMIT 1;
--- Ensure to cache loopback4 connection.
-SELECT 1 FROM ft8 LIMIT 1;
--- List all the existing cached connections. loopback, loopback2, loopback4
--- should be output.
+SELECT postgres_fdw_disconnect('loopback');
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-DROP SERVER loopback4 CASCADE;
-- Return false as connections are still in use, warnings are issued.
-- But disable warnings temporarily because the order of them is not stable.
SET client_min_messages = 'ERROR';
SELECT postgres_fdw_disconnect_all();
RESET client_min_messages;
COMMIT;
--- Close loopback2 connection and return true.
-SELECT postgres_fdw_disconnect('loopback2');
--- List all the existing cached connections. loopback should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Return false as loopback2 connectin is closed already.
-SELECT postgres_fdw_disconnect('loopback2');
--- Return an error as there is no foreign server with given name.
-SELECT postgres_fdw_disconnect('unknownserver');
--- Close loopback connection and return true.
-SELECT postgres_fdw_disconnect_all();
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+-- No cached connections, so no records should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- =============================================================================
@@ -2806,24 +2774,21 @@ CREATE ROLE regress_multi_conn_user2 SUPERUSER;
CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+BEGIN;
-- Will cache loopback connection with user mapping for regress_multi_conn_user1
SET ROLE regress_multi_conn_user1;
SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
-
-- Will cache loopback connection with user mapping for regress_multi_conn_user2
SET ROLE regress_multi_conn_user2;
SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
-
-- Should output two connections for loopback server
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-
--- Close loopback connections and return true.
-SELECT postgres_fdw_disconnect('loopback');
-
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
+COMMIT;
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+-- No cached connections, so no records should be output.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Clean up
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 8d6abd4c54..82e4f13995 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -497,27 +497,19 @@ OPTIONS (ADD password_required 'false');
<variablelist>
<varlistentry>
- <term><function>postgres_fdw_get_connections(OUT server_name text, OUT valid boolean) returns setof record</function></term>
+ <term><function>postgres_fdw_get_connections(OUT server_name text) returns setof record</function></term>
<listitem>
<para>
This function returns the foreign server names of all the open
connections that <filename>postgres_fdw</filename> established from
- the local session to the foreign servers. It also returns whether
- each connection is valid or not. <literal>false</literal> is returned
- if the foreign server connection is used in the current local
- transaction but its foreign server or user mapping is changed or
- dropped (Note that server name of an invalid connection will be
- <literal>NULL</literal> if the server is dropped),
- and then such invalid connection will be closed at
- the end of that transaction. <literal>true</literal> is returned
- otherwise. If there are no open connections, no record is returned.
- Example usage of the function:
+ the local session to the foreign servers. If there are no open
+ connections, no record is returned. Example usage of the function:
<screen>
postgres=# SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback1 | t
- loopback2 | f
+ server_name
+-------------
+ loopback1
+ loopback2
</screen>
</para>
</listitem>
--
2.25.1
On 2021/01/29 19:45, Bharath Rupireddy wrote:
On Fri, Jan 29, 2021 at 1:24 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Fri, Jan 29, 2021 at 1:17 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
But if the issue is only the inconsistency of test results,
we can go with the option (2)? Even with (2), we can make the test
stable by removing "valid" column and executing
postgres_fdw_get_connections() within the transaction?Hmmm, and we should have the tests at the start of the file
postgres_fdw.sql before even we make any foreign server connections.We don't need to move the test if we always call postgres_fdw_disconnect_all() just before starting new transaction and calling postgres_fdw_get_connections() as follows?
SELECT 1 FROM postgres_fdw_disconnect_all();
BEGIN;
...
SELECT * FROM postgres_fdw_get_connections();
...Yes, that works, but we cannot show true/false for the
postgres_fdw_disconnect_all output.I will post the patch soon. Thanks a lot.
Attaching a patch that has following changes: 1) Now,
postgres_fdw_get_connections will only return set of active
connections server names not their valid state 2) The functions
postgres_fdw_get_connections, postgres_fdw_disconnect and
postgres_fdw_disconnect_all are now being tested within an explicit
xact block, this way the tests are more stable even with clobber cache
always builds.I tested the patch here on my development system with
-DCLOBBER_CACHE_ALWAYS configuration, the tests look consistent.Please review the patch.
Thanks for the patch!
--- Return false as loopback2 connectin is closed already.
-SELECT postgres_fdw_disconnect('loopback2');
- postgres_fdw_disconnect
--------------------------
- f
-(1 row)
-
--- Return an error as there is no foreign server with given name.
-SELECT postgres_fdw_disconnect('unknownserver');
-ERROR: server "unknownserver" does not exist
Why do we need to remove these? These seem to work fine even in
CLOBBER_CACHE_ALWAYS.
+ /*
+ * It doesn't make sense to show this entry in the output with a
+ * NULL server_name as it will be closed at the xact end.
+ */
+ continue;
-1 with this change because I still think that it's more useful to list
all the open connections.
This makes me think that more discussion would be necessary before
changing the interface of postgres_fdw_get_connections(). On the other
hand, we should address the issue ASAP to make the buildfarm member fine.
So at first I'd like to push only the change of regression test.
Patch attached. I tested it both with CLOBBER_CACHE_ALWAYS set and unset,
and the results were stable.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attachments:
postgres_fdw.patchtext/plain; charset=UTF-8; name=postgres_fdw.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 07e06e5bf7..b09dce63f5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -17,10 +17,6 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
- EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
- OPTIONS (dbname '$$||current_database()||$$',
- port '$$||current_setting('port')||$$'
- )$$;
END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
@@ -28,7 +24,6 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
-CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
@@ -144,11 +139,6 @@ CREATE FOREIGN TABLE ft7 (
c2 int NOT NULL,
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
-CREATE FOREIGN TABLE ft8 (
- c1 int NOT NULL,
- c2 int NOT NULL,
- c3 text
-) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -220,8 +210,7 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
- public | ft8 | loopback4 | (schema_name 'S 1', table_name 'T 4') |
-(7 rows)
+(6 rows)
-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -9066,15 +9055,21 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- =============================================================================
-- test connection invalidation cases and postgres_fdw_get_connections function
-- =============================================================================
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
--- List all the existing cached connections. Only loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback2 | t
-(1 row)
-
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
?column?
@@ -9088,19 +9083,18 @@ SELECT 1 FROM ft7 LIMIT 1;
1
(1 row)
--- List all the existing cached connections. loopback and loopback3
--- also should be output as valid connections.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback2 | t
- loopback3 | t
-(3 rows)
+-- List all the existing cached connections. loopback and loopback3 should be
+-- output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+ loopback
+ loopback3
+(2 rows)
--- Connection is not closed at the end of the alter statement in
--- pgfdw_inval_callback. That's because the connection is in midst of this
--- xact, it is just marked as invalid.
+-- Connections are not closed at the end of the alter and drop statements.
+-- That's because the connections are in midst of this xact,
+-- they are just marked as invalid in pgfdw_inval_callback.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
DROP SERVER loopback3 CASCADE;
NOTICE: drop cascades to 2 other objects
@@ -9113,31 +9107,22 @@ SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
server_name | valid
-------------+-------
loopback | f
- loopback2 | t
| f
-(3 rows)
+(2 rows)
--- The invalid connection gets closed in pgfdw_xact_callback during commit.
+-- The invalid connections get closed in pgfdw_xact_callback during commit.
COMMIT;
--- List all the existing cached connections. loopback and loopback3
--- should not be output because they should be closed at the end of
--- the above transaction.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback2 | t
-(1 row)
+-- All cached connections were closed while committing above xact, so no
+-- records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
-- =======================================================================
-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
-- =======================================================================
--- Return true as all cached connections are closed.
-SELECT postgres_fdw_disconnect_all();
- postgres_fdw_disconnect_all
------------------------------
- t
-(1 row)
-
+BEGIN;
-- Ensure to cache loopback connection.
SELECT 1 FROM ft1 LIMIT 1;
?column?
@@ -9145,7 +9130,6 @@ SELECT 1 FROM ft1 LIMIT 1;
1
(1 row)
-BEGIN;
-- Ensure to cache loopback2 connection.
SELECT 1 FROM ft6 LIMIT 1;
?column?
@@ -9155,66 +9139,31 @@ SELECT 1 FROM ft6 LIMIT 1;
-- List all the existing cached connections. loopback and loopback2 should be
-- output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback2 | t
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+ loopback
+ loopback2
(2 rows)
--- Issue a warning and return false as loopback2 connection is still in use and
+-- Issue a warning and return false as loopback connection is still in use and
-- can not be closed.
-SELECT postgres_fdw_disconnect('loopback2');
-WARNING: cannot close connection for server "loopback2" because it is still in use
+SELECT postgres_fdw_disconnect('loopback');
+WARNING: cannot close connection for server "loopback" because it is still in use
postgres_fdw_disconnect
-------------------------
f
(1 row)
--- Close loopback connection, return true and issue a warning as loopback2
--- connection is still in use and can not be closed.
-SELECT postgres_fdw_disconnect_all();
-WARNING: cannot close connection for server "loopback2" because it is still in use
- postgres_fdw_disconnect_all
------------------------------
- t
-(1 row)
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+ loopback
+ loopback2
+(2 rows)
--- List all the existing cached connections. loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback2 | t
-(1 row)
-
--- Ensure to cache loopback connection.
-SELECT 1 FROM ft1 LIMIT 1;
- ?column?
-----------
- 1
-(1 row)
-
--- Ensure to cache loopback4 connection.
-SELECT 1 FROM ft8 LIMIT 1;
- ?column?
-----------
- 1
-(1 row)
-
--- List all the existing cached connections. loopback, loopback2, loopback4
--- should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback2 | t
- loopback4 | t
-(3 rows)
-
-DROP SERVER loopback4 CASCADE;
-NOTICE: drop cascades to 2 other objects
-DETAIL: drop cascades to user mapping for public on server loopback4
-drop cascades to foreign table ft8
-- Return false as connections are still in use, warnings are issued.
-- But disable warnings temporarily because the order of them is not stable.
SET client_min_messages = 'ERROR';
@@ -9226,21 +9175,19 @@ SELECT postgres_fdw_disconnect_all();
RESET client_min_messages;
COMMIT;
--- Close loopback2 connection and return true.
-SELECT postgres_fdw_disconnect('loopback2');
- postgres_fdw_disconnect
--------------------------
- t
+-- Ensure that loopback2 connection is closed.
+SELECT 1 FROM postgres_fdw_disconnect('loopback2');
+ ?column?
+----------
+ 1
(1 row)
--- List all the existing cached connections. loopback should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
-(1 row)
+SELECT server_name FROM postgres_fdw_get_connections() WHERE server_name = 'loopback2';
+ server_name
+-------------
+(0 rows)
--- Return false as loopback2 connectin is closed already.
+-- Return false as loopback2 connection is closed already.
SELECT postgres_fdw_disconnect('loopback2');
postgres_fdw_disconnect
-------------------------
@@ -9250,18 +9197,17 @@ SELECT postgres_fdw_disconnect('loopback2');
-- Return an error as there is no foreign server with given name.
SELECT postgres_fdw_disconnect('unknownserver');
ERROR: server "unknownserver" does not exist
--- Close loopback connection and return true.
-SELECT postgres_fdw_disconnect_all();
- postgres_fdw_disconnect_all
------------------------------
- t
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+ ?column?
+----------
+ 1
(1 row)
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
(0 rows)
-- =============================================================================
@@ -9271,6 +9217,7 @@ CREATE ROLE regress_multi_conn_user1 SUPERUSER;
CREATE ROLE regress_multi_conn_user2 SUPERUSER;
CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+BEGIN;
-- Will cache loopback connection with user mapping for regress_multi_conn_user1
SET ROLE regress_multi_conn_user1;
SELECT 1 FROM ft1 LIMIT 1;
@@ -9290,25 +9237,25 @@ SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
-- Should output two connections for loopback server
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
- loopback | t
- loopback | t
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+ loopback
+ loopback
(2 rows)
--- Close loopback connections and return true.
-SELECT postgres_fdw_disconnect('loopback');
- postgres_fdw_disconnect
--------------------------
- t
+COMMIT;
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+ ?column?
+----------
+ 1
(1 row)
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid
--------------+-------
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
(0 rows)
-- Clean up
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 647192cf6a..319c15d635 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -19,10 +19,6 @@ DO $d$
OPTIONS (dbname '$$||current_database()||$$',
port '$$||current_setting('port')||$$'
)$$;
- EXECUTE $$CREATE SERVER loopback4 FOREIGN DATA WRAPPER postgres_fdw
- OPTIONS (dbname '$$||current_database()||$$',
- port '$$||current_setting('port')||$$'
- )$$;
END;
$d$;
@@ -31,7 +27,6 @@ CREATE USER MAPPING FOR public SERVER testserver1
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
CREATE USER MAPPING FOR public SERVER loopback3;
-CREATE USER MAPPING FOR public SERVER loopback4;
-- ===================================================================
-- create objects used through FDW loopback server
@@ -158,12 +153,6 @@ CREATE FOREIGN TABLE ft7 (
c3 text
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
-CREATE FOREIGN TABLE ft8 (
- c1 int NOT NULL,
- c2 int NOT NULL,
- c3 text
-) SERVER loopback4 OPTIONS (schema_name 'S 1', table_name 'T 4');
-
-- ===================================================================
-- tests for validator
-- ===================================================================
@@ -2723,80 +2712,67 @@ DROP PROCEDURE terminate_backend_and_wait(text);
-- =============================================================================
-- test connection invalidation cases and postgres_fdw_get_connections function
-- =============================================================================
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-- This test case is for closing the connection in pgfdw_xact_callback
BEGIN;
--- List all the existing cached connections. Only loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact.
SELECT 1 FROM ft1 LIMIT 1;
SELECT 1 FROM ft7 LIMIT 1;
--- List all the existing cached connections. loopback and loopback3
--- also should be output as valid connections.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Connection is not closed at the end of the alter statement in
--- pgfdw_inval_callback. That's because the connection is in midst of this
--- xact, it is just marked as invalid.
+-- List all the existing cached connections. loopback and loopback3 should be
+-- output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Connections are not closed at the end of the alter and drop statements.
+-- That's because the connections are in midst of this xact,
+-- they are just marked as invalid in pgfdw_inval_callback.
ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
DROP SERVER loopback3 CASCADE;
-- List all the existing cached connections. loopback and loopback3
-- should be output as invalid connections. Also the server name for
-- loopback3 should be NULL because the server was dropped.
SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- The invalid connection gets closed in pgfdw_xact_callback during commit.
+-- The invalid connections get closed in pgfdw_xact_callback during commit.
COMMIT;
--- List all the existing cached connections. loopback and loopback3
--- should not be output because they should be closed at the end of
--- the above transaction.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- All cached connections were closed while committing above xact, so no
+-- records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-- =======================================================================
-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions
-- =======================================================================
--- Return true as all cached connections are closed.
-SELECT postgres_fdw_disconnect_all();
+BEGIN;
-- Ensure to cache loopback connection.
SELECT 1 FROM ft1 LIMIT 1;
-BEGIN;
-- Ensure to cache loopback2 connection.
SELECT 1 FROM ft6 LIMIT 1;
-- List all the existing cached connections. loopback and loopback2 should be
-- output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Issue a warning and return false as loopback2 connection is still in use and
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Issue a warning and return false as loopback connection is still in use and
-- can not be closed.
-SELECT postgres_fdw_disconnect('loopback2');
--- Close loopback connection, return true and issue a warning as loopback2
--- connection is still in use and can not be closed.
-SELECT postgres_fdw_disconnect_all();
--- List all the existing cached connections. loopback2 should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Ensure to cache loopback connection.
-SELECT 1 FROM ft1 LIMIT 1;
--- Ensure to cache loopback4 connection.
-SELECT 1 FROM ft8 LIMIT 1;
--- List all the existing cached connections. loopback, loopback2, loopback4
--- should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-DROP SERVER loopback4 CASCADE;
+SELECT postgres_fdw_disconnect('loopback');
+-- List all the existing cached connections. loopback and loopback2 should be
+-- output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-- Return false as connections are still in use, warnings are issued.
-- But disable warnings temporarily because the order of them is not stable.
SET client_min_messages = 'ERROR';
SELECT postgres_fdw_disconnect_all();
RESET client_min_messages;
COMMIT;
--- Close loopback2 connection and return true.
-SELECT postgres_fdw_disconnect('loopback2');
--- List all the existing cached connections. loopback should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
--- Return false as loopback2 connectin is closed already.
+-- Ensure that loopback2 connection is closed.
+SELECT 1 FROM postgres_fdw_disconnect('loopback2');
+SELECT server_name FROM postgres_fdw_get_connections() WHERE server_name = 'loopback2';
+-- Return false as loopback2 connection is closed already.
SELECT postgres_fdw_disconnect('loopback2');
-- Return an error as there is no foreign server with given name.
SELECT postgres_fdw_disconnect('unknownserver');
--- Close loopback connection and return true.
-SELECT postgres_fdw_disconnect_all();
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-- =============================================================================
-- test case for having multiple cached connections for a foreign server
@@ -2806,6 +2782,7 @@ CREATE ROLE regress_multi_conn_user2 SUPERUSER;
CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
+BEGIN;
-- Will cache loopback connection with user mapping for regress_multi_conn_user1
SET ROLE regress_multi_conn_user1;
SELECT 1 FROM ft1 LIMIT 1;
@@ -2817,14 +2794,12 @@ SELECT 1 FROM ft1 LIMIT 1;
RESET ROLE;
-- Should output two connections for loopback server
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
-
--- Close loopback connections and return true.
-SELECT postgres_fdw_disconnect('loopback');
-
--- List all the existing cached connections. No connection exists, so NULL
--- should be output.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+COMMIT;
+-- Let's ensure to close all the existing cached connections.
+SELECT 1 FROM postgres_fdw_disconnect_all();
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-- Clean up
DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
On Sat, Jan 30, 2021 at 12:14 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:
+ /* + * It doesn't make sense to show this entry in the output with a + * NULL server_name as it will be closed at the xact end. + */ + continue;-1 with this change because I still think that it's more useful to list
all the open connections.
If postgres_fdw_get_connections doesn't have a "valid" column, then I
thought it's better not showing server_name NULL in the output. Do you
think that we need to output some fixed strings for such connections
like "<unknown server>" or "<server doesn't exist>" or "<dropped
server>" or "<server information not available>"? I'm not sure whether
we are allowed to have fixed strings as column output.
This makes me think that more discussion would be necessary before
changing the interface of postgres_fdw_get_connections(). On the other
hand, we should address the issue ASAP to make the buildfarm member fine.
So at first I'd like to push only the change of regression test.
Patch attached. I tested it both with CLOBBER_CACHE_ALWAYS set and unset,
and the results were stable.
Thanks, the postgres_fdw.patch looks good to me.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/01/30 9:28, Bharath Rupireddy wrote:
On Sat, Jan 30, 2021 at 12:14 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:+ /* + * It doesn't make sense to show this entry in the output with a + * NULL server_name as it will be closed at the xact end. + */ + continue;-1 with this change because I still think that it's more useful to list
all the open connections.If postgres_fdw_get_connections doesn't have a "valid" column, then I
thought it's better not showing server_name NULL in the output.
Or if we don't have strong reason to remove "valid" column,
the current design is enough?
Do you
think that we need to output some fixed strings for such connections
like "<unknown server>" or "<server doesn't exist>" or "<dropped
server>" or "<server information not available>"? I'm not sure whether
we are allowed to have fixed strings as column output.This makes me think that more discussion would be necessary before
changing the interface of postgres_fdw_get_connections(). On the other
hand, we should address the issue ASAP to make the buildfarm member fine.
So at first I'd like to push only the change of regression test.
Patch attached. I tested it both with CLOBBER_CACHE_ALWAYS set and unset,
and the results were stable.Thanks, the postgres_fdw.patch looks good to me.
Thanks for checking the patch! I pushed that.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/01/27 10:06, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 8:38 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I will post "keep_connections" GUC and "keep_connection" server level
option patches later.Attaching v19 patch set for "keep_connections" GUC and
"keep_connection" server level option. Please review them further.
These options are no longer necessary because we now support idle_session_timeout? If we want to disconnect the foreign server connections that sit on idle to prevent them from eating up the connection capacities in the foriegn servers, we can just set idle_session_timeout in those foreign servers. If we want to avoid the cluster-wide setting of idle_session_timeout, we can set that per role. One issue for this approach is that the connection entry remains even after idle_session_timeout happens. So postgres_fdw_get_connections() returns that connection even though it's actually closed by the timeout. Which is confusing. But which doesn't cause any actual problem, right? When the foreign table is accessed the next time, that connection entry is dropped, an error is detected, and then new connection will be remade.
Sorry I've not read the past long discussion about this feature. If there is the consensus that these options are still necessary and useful even when we have idle_session_timeout, please correct me.
ISTM that it's intuitive (at least for me) to add this kind of option into the foreign server. But I'm not sure if it's good idea to expose the option as GUC. Also if there is the consensus about this, please correct me.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, Feb 1, 2021 at 12:29 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/30 9:28, Bharath Rupireddy wrote:
On Sat, Jan 30, 2021 at 12:14 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:+ /* + * It doesn't make sense to show this entry in the output with a + * NULL server_name as it will be closed at the xact end. + */ + continue;-1 with this change because I still think that it's more useful to list
all the open connections.If postgres_fdw_get_connections doesn't have a "valid" column, then I
thought it's better not showing server_name NULL in the output.Or if we don't have strong reason to remove "valid" column,
the current design is enough?
My only worry was that the statement from [1]/messages/by-id/2724627.1611886184@sss.pgh.pa.us "A cache flush should
not cause user-visible state changes." But the newly added function
postgres_fdw_get_connections is VOLATILE which means that the results
returned by postgres_fdw_get_connections() is also VOLATILE. Isn't
this enough, so that users will not get surprised with different
results in case invalidations occur within the server by the time they
run the query subsequent times and see different results than what
they saw in the first run?
Thoughts?
[1]: /messages/by-id/2724627.1611886184@sss.pgh.pa.us
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Feb 1, 2021 at 12:43 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/27 10:06, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 8:38 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I will post "keep_connections" GUC and "keep_connection" server level
option patches later.Attaching v19 patch set for "keep_connections" GUC and
"keep_connection" server level option. Please review them further.These options are no longer necessary because we now support idle_session_timeout? If we want to disconnect the foreign server connections that sit on idle to prevent them from eating up the connection capacities in the foriegn servers, we can just set idle_session_timeout in those foreign servers. If we want to avoid the cluster-wide setting of idle_session_timeout, we can set that per role. One issue for this approach is that the connection entry remains even after idle_session_timeout happens. So postgres_fdw_get_connections() returns that connection even though it's actually closed by the timeout. Which is confusing. But which doesn't cause any actual problem, right? When the foreign table is accessed the next time, that connection entry is dropped, an error is detected, and then new connection will be remade.
First of all, idle_session_timeout is by default 0 i.e. disabled,
there are chances that users may not use that and don't want to set it
just for not caching any foreign server connection. A simple use case
where server level option can be useful is that, users are accessing
foreign tables (may be not that frequently, once in a while) from a
long running local session using foreign servers and they don't want
to keep the local session cache those connections, then setting this
server level option, keep_connections to false makes their life
easier, without having to depend on setting idle_session_timeout on
the remote server.
And, just using idle_session_timeout on a remote server may not help
us completely. Because the remote session may go away, while we are
still using that cached connection in an explicit txn on the local
session. Our connection retry will also not work because we are in the
middle of an xact, so the local explicit txn gets aborted.
So, IMO, we can still have both server level option as well as
postgres_fdw contrib level GUC (to tell the local session that "I
don't want to keep any foreign connections active" instead of setting
keep_connection server level option for each foreign server).
Sorry I've not read the past long discussion about this feature. If there is the consensus that these options are still necessary and useful even when we have idle_session_timeout, please correct me.
ISTM that it's intuitive (at least for me) to add this kind of option into the foreign server. But I'm not sure if it's good idea to expose the option as GUC. Also if there is the consensus about this, please correct me.
See here [1]/messages/by-id/f58d1df4ae58f6cf3bfa560f923462e0@postgrespro.ru.
[1]: /messages/by-id/f58d1df4ae58f6cf3bfa560f923462e0@postgrespro.ru
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/02/01 16:13, Bharath Rupireddy wrote:
On Mon, Feb 1, 2021 at 12:29 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/30 9:28, Bharath Rupireddy wrote:
On Sat, Jan 30, 2021 at 12:14 AM Fujii Masao
<masao.fujii@oss.nttdata.com> wrote:+ /* + * It doesn't make sense to show this entry in the output with a + * NULL server_name as it will be closed at the xact end. + */ + continue;-1 with this change because I still think that it's more useful to list
all the open connections.If postgres_fdw_get_connections doesn't have a "valid" column, then I
thought it's better not showing server_name NULL in the output.Or if we don't have strong reason to remove "valid" column,
the current design is enough?My only worry was that the statement from [1] "A cache flush should
not cause user-visible state changes."
If we follow this strictly, I'm afraid that postgres_fdw_get_connections()
itself would also be a problem because the cached connections are affected
by cache flush and postgres_fdw_get_connections() shows that to users.
I'm not sure if removing "valid" column is actually helpful for that statement.
Anyway, for now we have the following options;
(1) keep the feature as it is
(2) remove "valid" column
(2-1) show NULL for the connection whose server was dropped
(2-2) show fixed value (e.g., <dropped>) for the connection whose server was dropped
(3) remove "valid" column and don't display connection whose server was dropped
(4) remove postgres_fdw_get_connections()
For now I like (1), but if others think "valid" column should be dropped,
I'm fine with (2). But I'd like to avoid (3) because I think that
postgres_fdw_get_connections() should list all the connections that
are actually being established. I have no strong opinion about whether
(2-1) or (2-2) is better, for now.
But the newly added function
postgres_fdw_get_connections is VOLATILE which means that the results
returned by postgres_fdw_get_connections() is also VOLATILE. Isn't
this enough, so that users will not get surprised with different
results in case invalidations occur within the server by the time they
run the query subsequent times and see different results than what
they saw in the first run?
I'm not sure about this...
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/02/01 16:39, Bharath Rupireddy wrote:
On Mon, Feb 1, 2021 at 12:43 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2021/01/27 10:06, Bharath Rupireddy wrote:
On Tue, Jan 26, 2021 at 8:38 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I will post "keep_connections" GUC and "keep_connection" server level
option patches later.Attaching v19 patch set for "keep_connections" GUC and
"keep_connection" server level option. Please review them further.These options are no longer necessary because we now support idle_session_timeout? If we want to disconnect the foreign server connections that sit on idle to prevent them from eating up the connection capacities in the foriegn servers, we can just set idle_session_timeout in those foreign servers. If we want to avoid the cluster-wide setting of idle_session_timeout, we can set that per role. One issue for this approach is that the connection entry remains even after idle_session_timeout happens. So postgres_fdw_get_connections() returns that connection even though it's actually closed by the timeout. Which is confusing. But which doesn't cause any actual problem, right? When the foreign table is accessed the next time, that connection entry is dropped, an error is detected, and then new connection will be remade.
First of all, idle_session_timeout is by default 0 i.e. disabled,
there are chances that users may not use that and don't want to set it
just for not caching any foreign server connection. A simple use case
where server level option can be useful is that, users are accessing
foreign tables (may be not that frequently, once in a while) from a
long running local session using foreign servers and they don't want
to keep the local session cache those connections, then setting this
server level option, keep_connections to false makes their life
easier, without having to depend on setting idle_session_timeout on
the remote server.
Thanks for explaining this!
I understand that use case. But I still think that we can use
idle_session_timeout for that use case without keep_connections.
Per the past discussion, Robert seems to prefer controling the cached
connection by timeout rather than boolean, at [1]/messages/by-id/CA+Tgmob_nF7NkBfVLUhmQ+t8JGVV4hXy+zkuMUtTSd-=HPBeuA@mail.gmail.com. Bruce seems to think
that idle_session_timeout is enough for the use case, at [2]/messages/by-id/20200714165822.GE7628@momjian.us. So I'm not
sure what the current consensus is...
Also Alexey seems to have thought that idle_session_timeout is not
suitable for cached connection because it's the cluster-wide option, at [3]/messages/by-id/6df6525ca7a4b54a4a39f55e4dd6b3e9@postgrespro.ru.
But since it's marked as PGC_USERSET, we can set it per-role, e.g.,
by using ALTER ROLE SET, so that it can affect only the foreign server
connections.
One merit of keep_connections that I found is that we can use it even
when connecting to the older PostgreSQL that doesn't support
idle_session_timeout. Also it seems simpler to use keep_connections
rather than setting idle_session_timeout in multiple remote servers.
So I'm inclined to add this feature, but I'd like to hear more opinions.
[1]: /messages/by-id/CA+Tgmob_nF7NkBfVLUhmQ+t8JGVV4hXy+zkuMUtTSd-=HPBeuA@mail.gmail.com
/messages/by-id/CA+Tgmob_nF7NkBfVLUhmQ+t8JGVV4hXy+zkuMUtTSd-=HPBeuA@mail.gmail.com
[2]: /messages/by-id/20200714165822.GE7628@momjian.us
/messages/by-id/20200714165822.GE7628@momjian.us
[3]: /messages/by-id/6df6525ca7a4b54a4a39f55e4dd6b3e9@postgrespro.ru
/messages/by-id/6df6525ca7a4b54a4a39f55e4dd6b3e9@postgrespro.ru
And, just using idle_session_timeout on a remote server may not help
us completely. Because the remote session may go away, while we are
still using that cached connection in an explicit txn on the local
session. Our connection retry will also not work because we are in the
middle of an xact, so the local explicit txn gets aborted.
Regarding idle_in_transaction_session_timeout, this seems true. But
I was thinking that idle_session_timeout doesn't cause this issue because
it doesn't close the connection in the middle of transaction. No?
So, IMO, we can still have both server level option as well as
postgres_fdw contrib level GUC (to tell the local session that "I
don't want to keep any foreign connections active" instead of setting
keep_connection server level option for each foreign server).Sorry I've not read the past long discussion about this feature. If there is the consensus that these options are still necessary and useful even when we have idle_session_timeout, please correct me.
ISTM that it's intuitive (at least for me) to add this kind of option into the foreign server. But I'm not sure if it's good idea to expose the option as GUC. Also if there is the consensus about this, please correct me.
See here [1].
[1] - /messages/by-id/f58d1df4ae58f6cf3bfa560f923462e0@postgrespro.ru
Thanks!
Here are some review comments.
- (used_in_current_xact && !keep_connections))
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
The names of GUC and server-level option should be the same,
to make the thing less confusing?
IMO the server-level option should override GUC. IOW, GUC setting
should be used only when the server-level option is not specified.
But the above code doesn't seem to do that. Thought?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Tue, Feb 2, 2021 at 9:45 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
One merit of keep_connections that I found is that we can use it even
when connecting to the older PostgreSQL that doesn't support
idle_session_timeout. Also it seems simpler to use keep_connections
rather than setting idle_session_timeout in multiple remote servers.
So I'm inclined to add this feature, but I'd like to hear more opinions.
Thanks.
And, just using idle_session_timeout on a remote server may not help
us completely. Because the remote session may go away, while we are
still using that cached connection in an explicit txn on the local
session. Our connection retry will also not work because we are in the
middle of an xact, so the local explicit txn gets aborted.Regarding idle_in_transaction_session_timeout, this seems true. But
I was thinking that idle_session_timeout doesn't cause this issue because
it doesn't close the connection in the middle of transaction. No?
You are right. idle_session_timeout doesn't take effect when in the
middle of an explicit txn. I missed this point.
Here are some review comments.
- (used_in_current_xact && !keep_connections)) + (used_in_current_xact && + (!keep_connections || !entry->keep_connection)))The names of GUC and server-level option should be the same,
to make the thing less confusing?
We can have GUC name keep_connections as there can be multiple
connections within a local session and I can change the server level
option keep_connection to keep_connections because a single foreign
server can have multiple connections as we have seen that in the use
case identified by you. I will change that in the next patch set.
IMO the server-level option should override GUC. IOW, GUC setting
should be used only when the server-level option is not specified.
But the above code doesn't seem to do that. Thought?
Note that default values for GUC and server level option are on i.e.
connections are cached.
The main intention of the GUC is to not set server level options to
false for all the foreign servers in case users don't want to keep any
foreign server connections. If the server level option overrides GUC,
then even if users set GUC to off, they have to set the server level
option to false for all the foreign servers.
So, the below code in the patch, first checks the GUC. If the GUC is
off, then discards the connections. If the GUC is on, then it further
checks the server level option. If it's off discards the connection,
otherwise not.
I would like it to keep this behaviour as is. Thoughts?
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
+ (used_in_current_xact &&
+ (!keep_connections || !entry->keep_connection)))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On 2021/02/03 13:56, Bharath Rupireddy wrote:
On Tue, Feb 2, 2021 at 9:45 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
One merit of keep_connections that I found is that we can use it even
when connecting to the older PostgreSQL that doesn't support
idle_session_timeout. Also it seems simpler to use keep_connections
rather than setting idle_session_timeout in multiple remote servers.
So I'm inclined to add this feature, but I'd like to hear more opinions.Thanks.
And, just using idle_session_timeout on a remote server may not help
us completely. Because the remote session may go away, while we are
still using that cached connection in an explicit txn on the local
session. Our connection retry will also not work because we are in the
middle of an xact, so the local explicit txn gets aborted.Regarding idle_in_transaction_session_timeout, this seems true. But
I was thinking that idle_session_timeout doesn't cause this issue because
it doesn't close the connection in the middle of transaction. No?You are right. idle_session_timeout doesn't take effect when in the
middle of an explicit txn. I missed this point.Here are some review comments.
- (used_in_current_xact && !keep_connections)) + (used_in_current_xact && + (!keep_connections || !entry->keep_connection)))The names of GUC and server-level option should be the same,
to make the thing less confusing?We can have GUC name keep_connections as there can be multiple
connections within a local session and I can change the server level
option keep_connection to keep_connections because a single foreign
server can have multiple connections as we have seen that in the use
case identified by you. I will change that in the next patch set.IMO the server-level option should override GUC. IOW, GUC setting
should be used only when the server-level option is not specified.
But the above code doesn't seem to do that. Thought?Note that default values for GUC and server level option are on i.e.
connections are cached.The main intention of the GUC is to not set server level options to
false for all the foreign servers in case users don't want to keep any
foreign server connections. If the server level option overrides GUC,
then even if users set GUC to off, they have to set the server level
option to false for all the foreign servers.
Maybe my explanation in the previous email was unclear. What I think is; If the server-level option is explicitly specified, its setting is used whatever GUC is. On the other hand, if the server-level option is NOT specified, GUC setting is used. For example, if we define the server as follows, GUC setting is used because the server-level option is NOT specified.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres;
If we define the server as follows, the server-level setting is used.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres OPTIONS (keep_connections 'on');
For example, log_autovacuum_min_duration GUC and reloption work in the similar way. That is, reloption setting overrides GUC. If reltion is not specified, GUC is used.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Wed, Feb 3, 2021 at 4:22 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Maybe my explanation in the previous email was unclear. What I think is; If the server-level option is explicitly specified, its setting is used whatever GUC is. On the other hand, if the server-level option is NOT specified, GUC setting is used. For example, if we define the server as follows, GUC setting is used because the server-level option is NOT specified.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres;
If we define the server as follows, the server-level setting is used.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres OPTIONS (keep_connections 'on');
Attaching v20 patch set. Now, server level option if provided
overrides the GUC.The GUC will be used only if server level option is
not provided. And also, both server level option and GUC are named the
same - "keep_connections".
Please have a look.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v20-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v20-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 517a81388d4f81c777f22c82ada2a8f66e05306f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 3 Feb 2021 18:26:47 +0530
Subject: [PATCH v20] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 28 ++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 15 +++++++
doc/src/sgml/postgres-fdw.sgml | 45 +++++++++++++++++++
6 files changed, 116 insertions(+), 3 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ee0b4acf0b..3325ce00f2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b09dce63f5..f89c189a48 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9263,6 +9263,34 @@ DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2ce42ce3f1..c5a76b7ad0 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -312,6 +312,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -527,6 +529,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_i);
static int get_batch_size_option(Relation rel);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 1f67b4d9fd..ceea46b304 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 319c15d635..b4234e19cf 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2807,6 +2807,21 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 8d6abd4c54..838b9a0084 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -571,6 +571,43 @@ postgres=# SELECT postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. Use <function>postgres_fdw_disconnect_all</function>
+ function or <function>postgres_fdw_disconnect</function> function to
+ close all or specific foreign server connections respectively.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -602,6 +639,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
the connections that are no longer necessary and then preventing them
from consuming the foreign server connections capacity too much.
</para>
+
+ <para>
+ Use configuration parameter <varname>postgres_fdw.keep_connections</varname>,
+ so that the local session doesn't keep any remote connections that are made
+ to the foreign servers within it. Default being <literal>on</literal>, when
+ set to <literal>off</literal>, each connection is discarded at the end of
+ local transaction in which it is used.
+ </para>
</sect2>
<sect2>
--
2.25.1
v20-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v20-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From b982e76f0a6cfde40da776eb54d345fc6ce35d8a Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 4 Feb 2021 09:34:06 +0530
Subject: [PATCH v20] postgres_fdw server level option, keep_connections to not
cache connection
This patch adds a new server level option, keep_connections,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 58 +++++++++++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 24 +++++++-
contrib/postgres_fdw/option.c | 9 ++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 13 +++++
doc/src/sgml/postgres-fdw.sgml | 47 +++++++++++++++
5 files changed, 145 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 3325ce00f2..0c5408dfe2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -48,6 +48,17 @@
*/
typedef Oid ConnCacheKey;
+/*
+ * Represents whether or not the keep_connections server level option is
+ * provided by users with the foreign server.
+ */
+typedef enum KeepConnSrvOpt
+{
+ KEEP_CONN_NOT_SPECFIEID = 0, /* option not provided */
+ KEEP_CONN_SPECIFIED_TRUE = 1, /* option provided with value true */
+ KEEP_CONN_SPECIFIED_FALSE = 2 /* option provided with value false */
+} KeepConnSrvOpt;
+
typedef struct ConnCacheEntry
{
ConnCacheKey key; /* hash key (must be first) */
@@ -62,6 +73,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Holds the server level keep_connections option info. */
+ KeepConnSrvOpt keep_conn_srv_opt;
} ConnCacheEntry;
/*
@@ -124,6 +137,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +276,24 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ entry->keep_conn_srv_opt = KEEP_CONN_NOT_SPECFIEID;
+
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connections") == 0)
+ {
+ bool opt_val = defGetBoolean(def);
+
+ if (opt_val)
+ entry->keep_conn_srv_opt = KEEP_CONN_SPECIFIED_TRUE;
+ else
+ entry->keep_conn_srv_opt = KEEP_CONN_SPECIFIED_FALSE;
+ }
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +318,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_conn_srv_opt = KEEP_CONN_NOT_SPECFIEID;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -810,6 +844,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
PGresult *res;
bool used_in_current_xact = false;
+ bool discard_conn = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -954,21 +989,36 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/* Reset state to show we're out of a transaction */
entry->xact_depth = 0;
+ /*
+ * Check if user wants to discard open connection used in this xact.
+ * See keep_connections server level option if specified, otherwise use
+ * keep_connections GUC. Note that the server level option overrides
+ * the GUC.
+ */
+ if (entry->conn && used_in_current_xact)
+ {
+ if ((entry->keep_conn_srv_opt == KEEP_CONN_NOT_SPECFIEID &&
+ !keep_connections) ||
+ entry->keep_conn_srv_opt == KEEP_CONN_SPECIFIED_FALSE)
+ discard_conn = true;
+
+ used_in_current_xact = false;
+ }
+
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
+ * invalid or user wants to discard open connection used in this xact,
+ * then discard it to recover. Next GetConnection will
* open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ discard_conn)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
- used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f89c189a48..9537417c8c 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8928,7 +8928,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9291,6 +9291,28 @@ SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-------------
(0 rows)
+RESET postgres_fdw.keep_connections;
+-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..ecd12b47c1 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connections") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -227,6 +228,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache connections associated with this server, otherwise
+ * remove them at the end of the xact. Default is true.
+ */
+ {"keep_connections", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index b4234e19cf..0c3a9c0b35 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2822,6 +2822,19 @@ SELECT 1 FROM ft1 LIMIT 1;
-- No cached connections, so no records should be output.
SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+RESET postgres_fdw.keep_connections;
+-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 838b9a0084..8543565450 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -490,6 +490,38 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connections</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ foreign server connections that are made within a local session. It can
+ be specified for each foreign server. If specified as <literal>off</literal>,
+ the associated foreign server connections are discarded at the end of
+ the transaction, otherwise the connections are kept by the local session.
+ If it's not specified at all, then the <varname>postgres_fdw.keep_connections</varname>
+ configuration parameter is used to decide whether to keep or discard
+ the foreign server connections.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -594,6 +626,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ The server level <literal>keep_connections</literal> option overrides the
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ that is, the <varname>postgres_fdw.keep_connections</varname>
+ configuration parameter will be checked only if the server level option
+ is not specified by the user for a foreign server.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -647,6 +687,13 @@ postgres=# SELECT postgres_fdw_disconnect_all();
set to <literal>off</literal>, each connection is discarded at the end of
local transaction in which it is used.
</para>
+
+ <para>
+ Use <xref linkend="sql-createserver"/> level option <literal>keep_connections</literal>
+ so that the local session doesn't keep the remote connection that is made to
+ the foreign server. Default being <literal>on</literal>, when set to <literal>off</literal>,
+ the connection is discarded at the end of local transaction in which it is used.
+ </para>
</sect2>
<sect2>
--
2.25.1
On Thu, Feb 4, 2021 at 9:36 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Wed, Feb 3, 2021 at 4:22 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Maybe my explanation in the previous email was unclear. What I think is; If the server-level option is explicitly specified, its setting is used whatever GUC is. On the other hand, if the server-level option is NOT specified, GUC setting is used. For example, if we define the server as follows, GUC setting is used because the server-level option is NOT specified.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres;
If we define the server as follows, the server-level setting is used.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres OPTIONS (keep_connections 'on');
Attaching v20 patch set. Now, server level option if provided
overrides the GUC.The GUC will be used only if server level option is
not provided. And also, both server level option and GUC are named the
same - "keep_connections".Please have a look.
Attaching v21 patch set, rebased onto the latest master.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v21-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchapplication/octet-stream; name=v21-0002-postgres_fdw-add-keep_connections-GUC-to-not-cac.patchDownload
From 517a81388d4f81c777f22c82ada2a8f66e05306f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Wed, 3 Feb 2021 18:26:47 +0530
Subject: [PATCH v21] postgres_fdw add keep_connections GUC to not cache
connections
This patch adds a new GUC postgres_fdw.keep_connections, default
being on, when set to off no remote connections are cached by the
local session.
---
contrib/postgres_fdw/connection.c | 12 +++--
.../postgres_fdw/expected/postgres_fdw.out | 28 ++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 16 +++++++
contrib/postgres_fdw/postgres_fdw.h | 3 ++
contrib/postgres_fdw/sql/postgres_fdw.sql | 15 +++++++
doc/src/sgml/postgres-fdw.sgml | 45 +++++++++++++++++++
6 files changed, 116 insertions(+), 3 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index ee0b4acf0b..3325ce00f2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -809,6 +809,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
{
PGresult *res;
+ bool used_in_current_xact = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -819,6 +820,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
bool abort_cleanup_failure = false;
+ used_in_current_xact = true;
+
elog(DEBUG3, "closing remote transaction on connection %p",
entry->conn);
@@ -953,16 +956,19 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * invalid or keep_connections GUC is false and this connection is used
+ * in current xact, then discard it to recover. Next GetConnection will
+ * open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ (used_in_current_xact && !keep_connections))
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
+ used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b09dce63f5..f89c189a48 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9263,6 +9263,34 @@ DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback;
DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+ postgres_fdw.keep_connections
+-------------------------------
+ on
+(1 row)
+
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 2ce42ce3f1..c5a76b7ad0 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -312,6 +312,8 @@ typedef struct
List *already_used; /* expressions already dealt with */
} ec_member_foreign_arg;
+bool keep_connections = true;
+
/*
* SQL functions
*/
@@ -527,6 +529,20 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
const PgFdwRelationInfo *fpinfo_i);
static int get_batch_size_option(Relation rel);
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("postgres_fdw.keep_connections",
+ "Enables postgres_fdw connection caching.",
+ "When off postgres_fdw will close connections at the end of transaction.",
+ &keep_connections,
+ true,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
/*
* Foreign-data wrapper handler function: return a struct with pointers
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 1f67b4d9fd..ceea46b304 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -124,9 +124,12 @@ typedef struct PgFdwRelationInfo
int relation_index;
} PgFdwRelationInfo;
+extern bool keep_connections;
+
/* in postgres_fdw.c */
extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
+extern void _PG_init(void);
/* in connection.c */
extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 319c15d635..b4234e19cf 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2807,6 +2807,21 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test postgres_fdw.keep_connections GUC
+-- ===================================================================
+-- By default, keep_connections GUC is on i.e. local session caches all the
+-- foreign server connections.
+SHOW postgres_fdw.keep_connections;
+-- Set it off i.e. the cached connections which are used after this setting are
+-- disconnected at the end of respective xacts.
+SET postgres_fdw.keep_connections TO off;
+-- Make connection using loopback server, connection should not be cached as
+-- the GUC is off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 8d6abd4c54..838b9a0084 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -571,6 +571,43 @@ postgres=# SELECT postgres_fdw_disconnect_all();
</sect2>
+<sect2>
+ <title>Configuration Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+
+ <term>
+ <varname>postgres_fdw.keep_connections</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>postgres_fdw.keep_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+
+ <para>
+ Allows <filename>postgres_fdw</filename> to keep or discard the
+ connection made to the foreign server by the local session. Default is
+ <literal>on</literal>. When set to <literal>off</literal> the local
+ session doesn't keep the connections made to the foreign servers. Each
+ connection is discarded at the end of transaction in which it is used.
+ </para>
+
+ <para>
+ Note that setting <varname>postgres_fdw.keep_connections</varname> to
+ <literal>off</literal> does not discard any previously made and still open
+ connections immediately. They will be closed only at the end of future
+ transactions, which operated on them. Use <function>postgres_fdw_disconnect_all</function>
+ function or <function>postgres_fdw_disconnect</function> function to
+ close all or specific foreign server connections respectively.
+ </para>
+
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
<sect2>
<title>Connection Management</title>
@@ -602,6 +639,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
the connections that are no longer necessary and then preventing them
from consuming the foreign server connections capacity too much.
</para>
+
+ <para>
+ Use configuration parameter <varname>postgres_fdw.keep_connections</varname>,
+ so that the local session doesn't keep any remote connections that are made
+ to the foreign servers within it. Default being <literal>on</literal>, when
+ set to <literal>off</literal>, each connection is discarded at the end of
+ local transaction in which it is used.
+ </para>
</sect2>
<sect2>
--
2.25.1
v21-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v21-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From e5f5f8a10b163093abf96dda4d64ddbe9691afbf Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Mon, 22 Feb 2021 11:21:10 +0530
Subject: [PATCH v21] postgres_fdw server level option, keep_connections to not
cache connection
This patch adds a new server level option, keep_connections,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 58 +++++++++++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 24 +++++++-
contrib/postgres_fdw/option.c | 9 ++-
contrib/postgres_fdw/sql/postgres_fdw.sql | 13 +++++
doc/src/sgml/postgres-fdw.sgml | 47 +++++++++++++++
5 files changed, 145 insertions(+), 6 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 3325ce00f2..0c5408dfe2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -48,6 +48,17 @@
*/
typedef Oid ConnCacheKey;
+/*
+ * Represents whether or not the keep_connections server level option is
+ * provided by users with the foreign server.
+ */
+typedef enum KeepConnSrvOpt
+{
+ KEEP_CONN_NOT_SPECFIEID = 0, /* option not provided */
+ KEEP_CONN_SPECIFIED_TRUE = 1, /* option provided with value true */
+ KEEP_CONN_SPECIFIED_FALSE = 2 /* option provided with value false */
+} KeepConnSrvOpt;
+
typedef struct ConnCacheEntry
{
ConnCacheKey key; /* hash key (must be first) */
@@ -62,6 +73,8 @@ typedef struct ConnCacheEntry
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ /* Holds the server level keep_connections option info. */
+ KeepConnSrvOpt keep_conn_srv_opt;
} ConnCacheEntry;
/*
@@ -124,6 +137,8 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
ConnCacheEntry *entry;
ConnCacheKey key;
MemoryContext ccxt = CurrentMemoryContext;
+ ListCell *lc;
+ ForeignServer *server;
/* First time through, initialize connection cache hashtable */
if (ConnectionHash == NULL)
@@ -261,6 +276,24 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
begin_remote_xact(entry);
}
+ server = GetForeignServer(user->serverid);
+ entry->keep_conn_srv_opt = KEEP_CONN_NOT_SPECFIEID;
+
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connections") == 0)
+ {
+ bool opt_val = defGetBoolean(def);
+
+ if (opt_val)
+ entry->keep_conn_srv_opt = KEEP_CONN_SPECIFIED_TRUE;
+ else
+ entry->keep_conn_srv_opt = KEEP_CONN_SPECIFIED_FALSE;
+ }
+ }
+
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
@@ -285,6 +318,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
entry->changing_xact_state = false;
entry->invalidated = false;
entry->serverid = server->serverid;
+ entry->keep_conn_srv_opt = KEEP_CONN_NOT_SPECFIEID;
entry->server_hashvalue =
GetSysCacheHashValue1(FOREIGNSERVEROID,
ObjectIdGetDatum(server->serverid));
@@ -810,6 +844,7 @@ pgfdw_xact_callback(XactEvent event, void *arg)
{
PGresult *res;
bool used_in_current_xact = false;
+ bool discard_conn = false;
/* Ignore cache entry if no open connection right now */
if (entry->conn == NULL)
@@ -954,21 +989,36 @@ pgfdw_xact_callback(XactEvent event, void *arg)
/* Reset state to show we're out of a transaction */
entry->xact_depth = 0;
+ /*
+ * Check if user wants to discard open connection used in this xact.
+ * See keep_connections server level option if specified, otherwise use
+ * keep_connections GUC. Note that the server level option overrides
+ * the GUC.
+ */
+ if (entry->conn && used_in_current_xact)
+ {
+ if ((entry->keep_conn_srv_opt == KEEP_CONN_NOT_SPECFIEID &&
+ !keep_connections) ||
+ entry->keep_conn_srv_opt == KEEP_CONN_SPECIFIED_FALSE)
+ discard_conn = true;
+
+ used_in_current_xact = false;
+ }
+
/*
* If the connection isn't in a good idle state or it is marked as
- * invalid or keep_connections GUC is false and this connection is used
- * in current xact, then discard it to recover. Next GetConnection will
+ * invalid or user wants to discard open connection used in this xact,
+ * then discard it to recover. Next GetConnection will
* open a new connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
entry->invalidated ||
- (used_in_current_xact && !keep_connections))
+ discard_conn)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
- used_in_current_xact = false;
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index fd0c3109f1..995f474db4 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9309,6 +9309,28 @@ SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
-------------
(0 rows)
+RESET postgres_fdw.keep_connections;
+-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..ecd12b47c1 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -107,7 +107,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
* Validate option value, when we can do so without any context.
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
- strcmp(def->defname, "updatable") == 0)
+ strcmp(def->defname, "updatable") == 0 ||
+ strcmp(def->defname, "keep_connections") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -227,6 +228,12 @@ InitPgFdwOptions(void)
{"sslcert", UserMappingRelationId, true},
{"sslkey", UserMappingRelationId, true},
+ /*
+ * If true, cache connections associated with this server, otherwise
+ * remove them at the end of the xact. Default is true.
+ */
+ {"keep_connections", ForeignServerRelationId, false},
+
{NULL, InvalidOid, false}
};
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 310f89288a..6a998f895c 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2834,6 +2834,19 @@ SELECT 1 FROM ft1 LIMIT 1;
-- No cached connections, so no records should be output.
SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+RESET postgres_fdw.keep_connections;
+-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- loopback server connection is closed by the local session at the end of xact
+-- as the keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 838b9a0084..8543565450 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -490,6 +490,38 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default the foreign server connections made with
+ <filename>postgres_fdw</filename> are kept in local session for re-use.
+ This may be overridden using the following
+ <xref linkend="sql-createserver"/> option:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connections</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps the
+ foreign server connections that are made within a local session. It can
+ be specified for each foreign server. If specified as <literal>off</literal>,
+ the associated foreign server connections are discarded at the end of
+ the transaction, otherwise the connections are kept by the local session.
+ If it's not specified at all, then the <varname>postgres_fdw.keep_connections</varname>
+ configuration parameter is used to decide whether to keep or discard
+ the foreign server connections.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -594,6 +626,14 @@ postgres=# SELECT postgres_fdw_disconnect_all();
connection is discarded at the end of transaction in which it is used.
</para>
+ <para>
+ The server level <literal>keep_connections</literal> option overrides the
+ <varname>postgres_fdw.keep_connections</varname> configuration parameter
+ that is, the <varname>postgres_fdw.keep_connections</varname>
+ configuration parameter will be checked only if the server level option
+ is not specified by the user for a foreign server.
+ </para>
+
<para>
Note that setting <varname>postgres_fdw.keep_connections</varname> to
<literal>off</literal> does not discard any previously made and still open
@@ -647,6 +687,13 @@ postgres=# SELECT postgres_fdw_disconnect_all();
set to <literal>off</literal>, each connection is discarded at the end of
local transaction in which it is used.
</para>
+
+ <para>
+ Use <xref linkend="sql-createserver"/> level option <literal>keep_connections</literal>
+ so that the local session doesn't keep the remote connection that is made to
+ the foreign server. Default being <literal>on</literal>, when set to <literal>off</literal>,
+ the connection is discarded at the end of local transaction in which it is used.
+ </para>
</sect2>
<sect2>
--
2.25.1
On 2021/02/22 14:55, Bharath Rupireddy wrote:
On Thu, Feb 4, 2021 at 9:36 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Wed, Feb 3, 2021 at 4:22 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Maybe my explanation in the previous email was unclear. What I think is; If the server-level option is explicitly specified, its setting is used whatever GUC is. On the other hand, if the server-level option is NOT specified, GUC setting is used. For example, if we define the server as follows, GUC setting is used because the server-level option is NOT specified.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres;
If we define the server as follows, the server-level setting is used.
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres OPTIONS (keep_connections 'on');
Attaching v20 patch set. Now, server level option if provided
overrides the GUC.The GUC will be used only if server level option is
not provided. And also, both server level option and GUC are named the
same - "keep_connections".Please have a look.
Attaching v21 patch set, rebased onto the latest master.
I agree to add the server-level option. But I'm still not sure if it's good idea to also expose that option as GUC. Isn't the server-level option enough for most cases?
Also it's strange to expose only this option as GUC while there are other many postgres_fdw options?
With v21-002 patch, even when keep_connections GUC is disabled, the existing open connections are not close immediately. Only connections used in the transaction are closed at the end of that transaction. That is, the existing connections that no transactions use will never be closed. I'm not sure if this behavior is intuitive for users.
Therefore for now I'm thinking to support the server-level option at first... Then if we find it's not enough for most cases in practice, I'd like to consider to expose postgres_fdw options including keep_connections as GUC.
Thought?
BTW these patches fail to be applied to the master because of commit 27e1f14563. I updated and simplified the 003 patch. Patch attached.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
Attachments:
v22-0003-postgres_fdw-server-level-option-keep_connection.patchtext/plain; charset=UTF-8; name=v22-0003-postgres_fdw-server-level-option-keep_connection.patch; x-mac-creator=0; x-mac-type=0Download
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 54ab8edfab..6a61d83862 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -59,6 +59,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ bool keep_connections; /* setting value of keep_connections
+ * server option */
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
@@ -286,6 +288,7 @@ static void
make_new_connection(ConnCacheEntry *entry, UserMapping *user)
{
ForeignServer *server = GetForeignServer(user->serverid);
+ ListCell *lc;
Assert(entry->conn == NULL);
@@ -304,6 +307,26 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
ObjectIdGetDatum(user->umid));
memset(&entry->state, 0, sizeof(entry->state));
+ /*
+ * Determine whether to keep the connection that we're about to make here
+ * open even after the transaction using it ends, so that the subsequent
+ * transactions can re-use it.
+ *
+ * It's enough to determine this only when making new connection because
+ * all the connections to the foreign server whose keep_connections option
+ * is changed will be closed and re-made later.
+ *
+ * By default, all the connections to any foreign servers are kept open.
+ */
+ entry->keep_connections = true;
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connections") == 0)
+ entry->keep_connections = defGetBoolean(def);
+ }
+
/* Now try to make the connection */
entry->conn = connect_pg_server(server, user);
@@ -970,14 +993,16 @@ pgfdw_xact_callback(XactEvent event, void *arg)
entry->xact_depth = 0;
/*
- * If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * If the connection isn't in a good idle state, it is marked as
+ * invalid or keep_connections option of its server is disabled, then
+ * discard it to recover. Next GetConnection will open a new
+ * connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ !entry->keep_connections)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index eff7b04f11..5da68415ed 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8908,7 +8908,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9244,6 +9244,27 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- connection to loopback server is closed at the end of xact
+-- as keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
+-- ===================================================================
-- batch insert
-- ===================================================================
BEGIN;
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 530d7a66d4..f1d0c8bd41 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -108,7 +108,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
strcmp(def->defname, "updatable") == 0 ||
- strcmp(def->defname, "async_capable") == 0)
+ strcmp(def->defname, "async_capable") == 0 ||
+ strcmp(def->defname, "keep_connections") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -221,6 +222,7 @@ InitPgFdwOptions(void)
/* async_capable is available on both server and table */
{"async_capable", ForeignServerRelationId, false},
{"async_capable", ForeignTableRelationId, false},
+ {"keep_connections", ForeignServerRelationId, false},
{"password_required", UserMappingRelationId, false},
/*
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 806a5bca28..85a968f7f0 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2819,6 +2819,19 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- connection to loopback server is closed at the end of xact
+-- as keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index c21e9be209..a0c6b8f0b1 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -518,6 +518,33 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default all the open connections that <filename>postgres_fdw</filename>
+ established to the foreign servers are kept in local session for re-use.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connections</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps
+ the connections to the foreign server open so that the subsequent
+ queries can re-use them. It can only be specified for a foreign server.
+ The default is <literal>true</literal>. If set to <literal>off</literal>,
+ all connections to this foreign server will be discarded at the end of
+ transaction.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -605,8 +632,10 @@ postgres=# SELECT postgres_fdw_disconnect_all();
<para>
<filename>postgres_fdw</filename> establishes a connection to a
foreign server during the first query that uses a foreign table
- associated with the foreign server. This connection is kept and
- re-used for subsequent queries in the same session. However, if
+ associated with the foreign server. By default this connection
+ is kept and re-used for subsequent queries in the same session.
+ This behavior can be controlled using
+ <literal>keep_connections</literal> option for a foreign server. If
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
@@ -622,8 +651,10 @@ postgres=# SELECT postgres_fdw_disconnect_all();
<para>
Once a connection to a foreign server has been established,
- it's usually kept until the local or corresponding remote
+ it's by default kept until the local or corresponding remote
session exits. To disconnect a connection explicitly,
+ <literal>keep_connections</literal> option for a foreign server
+ may be disabled, or
<function>postgres_fdw_disconnect</function> and
<function>postgres_fdw_disconnect_all</function> functions
may be used. For example, these are useful to close
On Thu, Apr 1, 2021 at 8:56 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching v21 patch set, rebased onto the latest master.
I agree to add the server-level option. But I'm still not sure if it's good idea to also expose that option as GUC. Isn't the server-level option enough for most cases?
Also it's strange to expose only this option as GUC while there are other many postgres_fdw options?
With v21-002 patch, even when keep_connections GUC is disabled, the existing open connections are not close immediately. Only connections used in the transaction are closed at the end of that transaction. That is, the existing connections that no transactions use will never be closed. I'm not sure if this behavior is intuitive for users.
Therefore for now I'm thinking to support the server-level option at first... Then if we find it's not enough for most cases in practice, I'd like to consider to expose postgres_fdw options including keep_connections as GUC.
Thought?
+1 to have only a server-level option for now and if the need arises
we could expose it as a GUC.
BTW these patches fail to be applied to the master because of commit 27e1f14563. I updated and simplified the 003 patch. Patch attached.
Thanks for updating the patch. It looks good to me. Just a minor
change, instead of using "true" and "off" for the option, I used "on"
and "off" in the docs. Attaching v23.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
v23-0003-postgres_fdw-server-level-option-keep_connection.patchapplication/octet-stream; name=v23-0003-postgres_fdw-server-level-option-keep_connection.patchDownload
From ca79c7292f08d6636c58435964df6233913bc675 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddy@enterprisedb.com>
Date: Thu, 1 Apr 2021 21:39:55 +0530
Subject: [PATCH v23] postgres_fdw server level option, keep_connections to not
cache connection
This patch adds a new server level option, keep_connections,
default being on, when set to off, the local session doesn't cache
the connections associated with the foreign server.
---
contrib/postgres_fdw/connection.c | 33 +++++++++++++++--
.../postgres_fdw/expected/postgres_fdw.out | 23 +++++++++++-
contrib/postgres_fdw/option.c | 4 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 13 +++++++
doc/src/sgml/postgres-fdw.sgml | 37 +++++++++++++++++--
5 files changed, 101 insertions(+), 9 deletions(-)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 54ab8edfab..6a61d83862 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -59,6 +59,8 @@ typedef struct ConnCacheEntry
bool have_error; /* have any subxacts aborted in this xact? */
bool changing_xact_state; /* xact state change in process */
bool invalidated; /* true if reconnect is pending */
+ bool keep_connections; /* setting value of keep_connections
+ * server option */
Oid serverid; /* foreign server OID used to get server name */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
@@ -286,6 +288,7 @@ static void
make_new_connection(ConnCacheEntry *entry, UserMapping *user)
{
ForeignServer *server = GetForeignServer(user->serverid);
+ ListCell *lc;
Assert(entry->conn == NULL);
@@ -304,6 +307,26 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
ObjectIdGetDatum(user->umid));
memset(&entry->state, 0, sizeof(entry->state));
+ /*
+ * Determine whether to keep the connection that we're about to make here
+ * open even after the transaction using it ends, so that the subsequent
+ * transactions can re-use it.
+ *
+ * It's enough to determine this only when making new connection because
+ * all the connections to the foreign server whose keep_connections option
+ * is changed will be closed and re-made later.
+ *
+ * By default, all the connections to any foreign servers are kept open.
+ */
+ entry->keep_connections = true;
+ foreach(lc, server->options)
+ {
+ DefElem *def = (DefElem *) lfirst(lc);
+
+ if (strcmp(def->defname, "keep_connections") == 0)
+ entry->keep_connections = defGetBoolean(def);
+ }
+
/* Now try to make the connection */
entry->conn = connect_pg_server(server, user);
@@ -970,14 +993,16 @@ pgfdw_xact_callback(XactEvent event, void *arg)
entry->xact_depth = 0;
/*
- * If the connection isn't in a good idle state or it is marked as
- * invalid, then discard it to recover. Next GetConnection will open a
- * new connection.
+ * If the connection isn't in a good idle state, it is marked as
+ * invalid or keep_connections option of its server is disabled, then
+ * discard it to recover. Next GetConnection will open a new
+ * connection.
*/
if (PQstatus(entry->conn) != CONNECTION_OK ||
PQtransactionStatus(entry->conn) != PQTRANS_IDLE ||
entry->changing_xact_state ||
- entry->invalidated)
+ entry->invalidated ||
+ !entry->keep_connections)
{
elog(DEBUG3, "discarding connection %p", entry->conn);
disconnect_pg_server(entry);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index eff7b04f11..5da68415ed 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8908,7 +8908,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
@@ -9244,6 +9244,27 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- connection to loopback server is closed at the end of xact
+-- as keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name
+-------------
+(0 rows)
+
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
+-- ===================================================================
-- batch insert
-- ===================================================================
BEGIN;
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 530d7a66d4..f1d0c8bd41 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -108,7 +108,8 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
*/
if (strcmp(def->defname, "use_remote_estimate") == 0 ||
strcmp(def->defname, "updatable") == 0 ||
- strcmp(def->defname, "async_capable") == 0)
+ strcmp(def->defname, "async_capable") == 0 ||
+ strcmp(def->defname, "keep_connections") == 0)
{
/* these accept only boolean values */
(void) defGetBoolean(def);
@@ -221,6 +222,7 @@ InitPgFdwOptions(void)
/* async_capable is available on both server and table */
{"async_capable", ForeignServerRelationId, false},
{"async_capable", ForeignTableRelationId, false},
+ {"keep_connections", ForeignServerRelationId, false},
{"password_required", UserMappingRelationId, false},
/*
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 806a5bca28..85a968f7f0 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2819,6 +2819,19 @@ DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback;
DROP ROLE regress_multi_conn_user1;
DROP ROLE regress_multi_conn_user2;
+-- ===================================================================
+-- Test foreign server level option keep_connections
+-- ===================================================================
+-- By default, the connections associated with foreign server are cached i.e.
+-- keep_connections option is on. Set it to off.
+ALTER SERVER loopback OPTIONS (keep_connections 'off');
+-- connection to loopback server is closed at the end of xact
+-- as keep_connections was set to off.
+SELECT 1 FROM ft1 LIMIT 1;
+-- No cached connections, so no records should be output.
+SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
+ALTER SERVER loopback OPTIONS (SET keep_connections 'on');
+
-- ===================================================================
-- batch insert
-- ===================================================================
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index c21e9be209..a7c695b000 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -518,6 +518,33 @@ OPTIONS (ADD password_required 'false');
</para>
</sect3>
+
+ <sect3>
+ <title>Connection Management Options</title>
+
+ <para>
+ By default all the open connections that <filename>postgres_fdw</filename>
+ established to the foreign servers are kept in local session for re-use.
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>keep_connections</literal></term>
+ <listitem>
+ <para>
+ This option controls whether <filename>postgres_fdw</filename> keeps
+ the connections to the foreign server open so that the subsequent
+ queries can re-use them. It can only be specified for a foreign server.
+ The default is <literal>on</literal>. If set to <literal>off</literal>,
+ all connections to this foreign server will be discarded at the end of
+ transaction.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </sect3>
</sect2>
<sect2>
@@ -605,8 +632,10 @@ postgres=# SELECT postgres_fdw_disconnect_all();
<para>
<filename>postgres_fdw</filename> establishes a connection to a
foreign server during the first query that uses a foreign table
- associated with the foreign server. This connection is kept and
- re-used for subsequent queries in the same session. However, if
+ associated with the foreign server. By default this connection
+ is kept and re-used for subsequent queries in the same session.
+ This behavior can be controlled using
+ <literal>keep_connections</literal> option for a foreign server. If
multiple user identities (user mappings) are used to access the foreign
server, a connection is established for each user mapping.
</para>
@@ -622,8 +651,10 @@ postgres=# SELECT postgres_fdw_disconnect_all();
<para>
Once a connection to a foreign server has been established,
- it's usually kept until the local or corresponding remote
+ it's by default kept until the local or corresponding remote
session exits. To disconnect a connection explicitly,
+ <literal>keep_connections</literal> option for a foreign server
+ may be disabled, or
<function>postgres_fdw_disconnect</function> and
<function>postgres_fdw_disconnect_all</function> functions
may be used. For example, these are useful to close
--
2.25.1
On 2021/04/02 1:13, Bharath Rupireddy wrote:
On Thu, Apr 1, 2021 at 8:56 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
Attaching v21 patch set, rebased onto the latest master.
I agree to add the server-level option. But I'm still not sure if it's good idea to also expose that option as GUC. Isn't the server-level option enough for most cases?
Also it's strange to expose only this option as GUC while there are other many postgres_fdw options?
With v21-002 patch, even when keep_connections GUC is disabled, the existing open connections are not close immediately. Only connections used in the transaction are closed at the end of that transaction. That is, the existing connections that no transactions use will never be closed. I'm not sure if this behavior is intuitive for users.
Therefore for now I'm thinking to support the server-level option at first... Then if we find it's not enough for most cases in practice, I'd like to consider to expose postgres_fdw options including keep_connections as GUC.
Thought?
+1 to have only a server-level option for now and if the need arises
we could expose it as a GUC.BTW these patches fail to be applied to the master because of commit 27e1f14563. I updated and simplified the 003 patch. Patch attached.
Thanks for updating the patch. It looks good to me. Just a minor
change, instead of using "true" and "off" for the option, I used "on"
and "off" in the docs. Attaching v23.
Thanks a lot! Barring any objection, I will commit this version.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On 2021/04/02 2:22, Fujii Masao wrote:
Thanks a lot! Barring any objection, I will commit this version.
Pushed. Thanks!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION