Libpq support to connect to standby server as priority

Started by Jing Wangabout 8 years ago4 messages
#1Jing Wang
jingwangian@gmail.com

Hi Hackers,

This is a proposal that let libpq support 'prefer-read' option in
target_session_attrs in pg_conn. The 'prefer-read' means the libpq will try
to connect to a 'read-only' server firstly from the multiple server
addresses. If failed to connect to the 'read-only' server then it will try
to connect to the 'read-write' server.

By providing this feature the application can have the opportunity to
connect to the standby server firstly if failed then connect to master
server without caring the sequence of the server addresses provided to the
libpq.

The 'read-only' server means Standby Server
The 'read-write' server means Master Server support to 'read-write'

--
Regards,
Jing Wang
Fujitsu Australia

#2Tsunakawa, Takayuki
tsunakawa.takay@jp.fujitsu.com
In reply to: Jing Wang (#1)
RE: Libpq support to connect to standby server as priority

From: Jing Wang [mailto:jingwangian@gmail.com]

This is a proposal that let libpq support 'prefer-read' option in
target_session_attrs in pg_conn. The 'prefer-read' means the libpq will
try to connect to a 'read-only' server firstly from the multiple server
addresses. If failed to connect to the 'read-only' server then it will try
to connect to the 'read-write' server.

There's a pending patch I started. I'd be happy if you could continue this.

https://commitfest.postgresql.org/15/1148/

Regards
Takayuki Tsunakawa

#3Jing Wang
jingwangian@gmail.com
In reply to: Jing Wang (#1)
1 attachment(s)
Re: Libpq support to connect to standby server as priority

Hi,

Enclosed please find the patch that the libpq support 'prefer-read'
feature.

If the *target_session_attrs* is set to 'prefer-read', the patch will
connect to server and send 'SHOW transaction_read_only' query to check the
server being 'read-only' or not. If server is 'read-write' then it will try
next server address. If all connections for 'read-only' get failed it will
try to connect to the master server.

--
Regards,
Jing Wang
Fujitsu Australia

Attachments:

libpq_support_perfer-read_001.patchapplication/octet-stream; name=libpq_support_perfer-read_001.patchDownload
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8d54333..6d37428 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -327,7 +327,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 
 	{"target_session_attrs", "PGTARGETSESSIONATTRS",
 		DefaultTargetSessionAttrs, NULL,
-		"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
+		"Target-Session-Attrs", "", 12, /* sizeof("prefer-read") = 12 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
 	/* Terminating entry --- MUST BE LAST */
@@ -1184,7 +1184,8 @@ connectOptions2(PGconn *conn)
 	if (conn->target_session_attrs)
 	{
 		if (strcmp(conn->target_session_attrs, "any") != 0
-			&& strcmp(conn->target_session_attrs, "read-write") != 0)
+			&& strcmp(conn->target_session_attrs, "read-write") != 0
+			&& strcmp(conn->target_session_attrs, "prefer-read") != 0)
 		{
 			conn->status = CONNECTION_BAD;
 			printfPQExpBuffer(&conn->errorMessage,
@@ -2086,8 +2087,22 @@ keep_going:						/* We will come back to here until there is
 					{
 						if (++conn->whichhost >= conn->nconnhost)
 						{
-							conn->whichhost = 0;
-							break;
+							if (conn->primary_host_index > 0 &&
+									strcmp(conn->target_session_attrs,"prefer-read") ==0 )
+							{
+								/*
+								 * Go to here means failed to connect to read-only servers
+								 * and now try connect to read-write server again.
+								 * Only under the 'prefer-read' scenario will go to here.
+								 */
+								conn->addr_cur = conn->connhost[conn->primary_host_index].addrlist;
+								conn->whichhost = conn->primary_host_index;
+							}
+							else
+							{
+								conn->whichhost = 0;
+								break;
+							}
 						}
 						conn->addr_cur =
 							conn->connhost[conn->whichhost].addrlist;
@@ -2341,6 +2356,14 @@ keep_going:						/* We will come back to here until there is
 						conn->status = CONNECTION_NEEDED;
 						goto keep_going;
 					}
+					else if (conn->primary_host_index >= 0  &&
+							strcmp(conn->target_session_attrs, "prefer-read") == 0)
+					{
+						conn->addr_cur = conn->connhost[conn->primary_host_index].addrlist;
+						conn->whichhost = conn->primary_host_index;
+						conn->status = CONNECTION_NEEDED;
+						goto keep_going;
+					}
 					goto error_return;
 				}
 
@@ -2978,10 +3001,12 @@ keep_going:						/* We will come back to here until there is
 				}
 
 				/*
-				 * If a read-write connection is required, see if we have one.
+				 * If a read-write or prefer-read connection is required,
+				 * see if we have one.
 				 */
 				if (conn->target_session_attrs != NULL &&
-					strcmp(conn->target_session_attrs, "read-write") == 0)
+					(strcmp(conn->target_session_attrs, "read-write") == 0 ||
+					 strcmp(conn->target_session_attrs, "prefer-read") == 0))
 				{
 					/*
 					 * We are yet to make a connection. Save all existing
@@ -3042,10 +3067,12 @@ keep_going:						/* We will come back to here until there is
 			}
 
 			/*
-			 * If a read-write connection is requested check for same.
+			 * If a read-write or prefer-read connection is requested
+			 * check for same.
 			 */
 			if (conn->target_session_attrs != NULL &&
-				strcmp(conn->target_session_attrs, "read-write") == 0)
+				(strcmp(conn->target_session_attrs, "read-write") == 0 ||
+				 strcmp(conn->target_session_attrs, "prefer-read") == 0))
 			{
 				if (!saveErrorMessage(conn, &savedMessage))
 					goto error_return;
@@ -3124,57 +3151,130 @@ keep_going:						/* We will come back to here until there is
 					PQntuples(res) == 1)
 				{
 					char	   *val;
+					bool		readonly_server = false;
 
 					val = PQgetvalue(res, 0, 0);
 					if (strncmp(val, "on", 2) == 0)
+						readonly_server = true;
+
+					if (strcmp(conn->target_session_attrs, "read-write") == 0)
 					{
-						const char *displayed_host;
-						const char *displayed_port;
+						if(readonly_server)
+						{
+							if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+								displayed_host = conn->connhost[conn->whichhost].hostaddr;
+							else
+								displayed_host = conn->connhost[conn->whichhost].host;
+							displayed_port = conn->connhost[conn->whichhost].port;
+							if (displayed_port == NULL || displayed_port[0] == '\0')
+								displayed_port = DEF_PGPORT_STR;
 
-						if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
-							displayed_host = conn->connhost[conn->whichhost].hostaddr;
-						else
-							displayed_host = conn->connhost[conn->whichhost].host;
-						displayed_port = conn->connhost[conn->whichhost].port;
-						if (displayed_port == NULL || displayed_port[0] == '\0')
-							displayed_port = DEF_PGPORT_STR;
+							PQclear(res);
+							restoreErrorMessage(conn, &savedMessage);
 
-						PQclear(res);
-						restoreErrorMessage(conn, &savedMessage);
+							/* Not writable; close connection. */
+							appendPQExpBuffer(&conn->errorMessage,
+											  libpq_gettext("could not make a writable "
+															"connection to server "
+															"\"%s:%s\"\n"),
+											  displayed_host, displayed_port);
+							conn->status = CONNECTION_OK;
+							sendTerminateConn(conn);
+							pqDropConnection(conn, true);
 
-						/* Not writable; close connection. */
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not make a writable "
-														"connection to server "
-														"\"%s:%s\"\n"),
-										  displayed_host, displayed_port);
-						conn->status = CONNECTION_OK;
-						sendTerminateConn(conn);
-						pqDropConnection(conn, true);
+							/* Skip any remaining addresses for this host. */
+							conn->addr_cur = NULL;
+							if (conn->whichhost + 1 < conn->nconnhost)
+							{
+								conn->status = CONNECTION_NEEDED;
+								goto keep_going;
+							}
 
-						/* Skip any remaining addresses for this host. */
-						conn->addr_cur = NULL;
-						if (conn->whichhost + 1 < conn->nconnhost)
+							/* No more addresses to try. So we fail. */
+							goto error_return;
+						}
+						else /* server support read-write */
 						{
-							conn->status = CONNECTION_NEEDED;
+							PQclear(res);
+							termPQExpBuffer(&savedMessage);
+
+							/* We can release the address lists now. */
+							release_all_addrinfo(conn);
+
+							/*
+							 * Finish reading any remaining messages before being
+							 * considered as ready.
+							 */
+							conn->status = CONNECTION_CONSUME;
 							goto keep_going;
 						}
-
-						/* No more addresses to try. So we fail. */
-						goto error_return;
 					}
-					PQclear(res);
-					termPQExpBuffer(&savedMessage);
+					else /* conn->target_session_attrs is prefer-read */
+					{
+						if(readonly_server)
+						{
+							PQclear(res);
+							termPQExpBuffer(&savedMessage);
 
-					/* We can release the address lists now. */
-					release_all_addrinfo(conn);
+							/* We can release the address lists now. */
+							release_all_addrinfo(conn);
 
-					/*
-					 * Finish reading any remaining messages before being
-					 * considered as ready.
-					 */
-					conn->status = CONNECTION_CONSUME;
-					goto keep_going;
+							/*
+							 * Finish reading any remaining messages before being
+							 * considered as ready.
+							 */
+							conn->status = CONNECTION_CONSUME;
+							goto keep_going;
+						}
+						else /* server support read-write */
+						{
+							if ((conn->primary_host_index < 0) && (conn->whichhost + 1 < conn->nconnhost))
+							{
+								if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+									displayed_host = conn->connhost[conn->whichhost].hostaddr;
+								else
+									displayed_host = conn->connhost[conn->whichhost].host;
+								displayed_port = conn->connhost[conn->whichhost].port;
+								if (displayed_port == NULL || displayed_port[0] == '\0')
+									displayed_port = DEF_PGPORT_STR;
+
+								PQclear(res);
+								restoreErrorMessage(conn, &savedMessage);
+
+								/*
+								 * Connecting to a writable server, close it
+								 * and try to connect to another one.
+								 */
+								conn->status = CONNECTION_OK;
+								sendTerminateConn(conn);
+								pqDropConnection(conn, true);
+
+								/* Skip any remaining addresses for this host. */
+								conn->addr_cur = NULL;
+
+								conn->status = CONNECTION_NEEDED;
+
+								/* Record primary host index */
+								conn->primary_host_index = conn->whichhost;
+								goto keep_going;
+							}
+							else /* No more host to connect, keep this connection */
+							{
+								PQclear(res);
+								termPQExpBuffer(&savedMessage);
+
+								/* We can release the address lists now. */
+								release_all_addrinfo(conn);
+
+								/*
+								 * Finish reading any remaining messages before being
+								 * considered as ready.
+								 */
+								conn->status = CONNECTION_CONSUME;
+								goto keep_going;
+							}
+						}
+					}
 				}
 
 				/*
@@ -3393,6 +3493,8 @@ makeEmptyPGconn(void)
 		conn = NULL;
 	}
 
+	conn->primary_host_index = -1;
+
 	return conn;
 }
 
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 516039e..3241246 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,7 +362,7 @@ struct pg_conn
 	char	   *krbsrvname;		/* Kerberos service name */
 #endif
 
-	/* Type of connection to make.  Possible values: any, read-write. */
+	/* Type of connection to make.  Possible values: any, read-write, perfer-read. */
 	char	   *target_session_attrs;
 
 	/* Optional file to write trace info to */
@@ -396,6 +396,7 @@ struct pg_conn
 	int			nconnhost;		/* # of possible hosts */
 	int			whichhost;		/* host we're currently considering */
 	pg_conn_host *connhost;		/* details about each possible host */
+	int 		primary_host_index; /* index for primary host in connhost */
 
 	/* Connection data */
 	pgsocket	sock;			/* FD for socket, PGINVALID_SOCKET if
#4Jing Wang
jingwangian@gmail.com
In reply to: Tsunakawa, Takayuki (#2)
Re: Libpq support to connect to standby server as priority

Hi Takayuki,

Thanks your reminder.

I will have a look your patch and the review comments and update the patch
soon.

Please leave my current submitted patch now.

--
Regards,
Jing Wang
Fujitsu Australia

2018-01-04 17:40 GMT+11:00 Tsunakawa, Takayuki <
tsunakawa.takay@jp.fujitsu.com>:

From: Jing Wang [mailto:jingwangian@gmail.com]

This is a proposal that let libpq support 'prefer-read' option in
target_session_attrs in pg_conn. The 'prefer-read' means the libpq will
try to connect to a 'read-only' server firstly from the multiple server
addresses. If failed to connect to the 'read-only' server then it will

try

to connect to the 'read-write' server.

There's a pending patch I started. I'd be happy if you could continue
this.

https://commitfest.postgresql.org/15/1148/

Regards
Takayuki Tsunakawa

--
Kind regards
Jing