From 2d15e0dae65fb13c2a41c676e3a2ead223be1088 Mon Sep 17 00:00:00 2001 From: Michael Banck Date: Thu, 10 Feb 2022 15:50:14 +0100 Subject: [PATCH] Add target_session_attrs=read-write to standby_leader primary_conninfo (#2193) This allows to have multiple hosts in a standby_cluster and ensures that the standby leader follows the main cluster's new leader after a switchover. Partially addresses #2189 --- patroni/postgresql/config.py | 9 ++++++++- patroni/postgresql/rewind.py | 4 ++++ tests/test_postgresql.py | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/patroni/postgresql/config.py b/patroni/postgresql/config.py index a7d46db3..6afb9e77 100644 --- a/patroni/postgresql/config.py +++ b/patroni/postgresql/config.py @@ -486,7 +486,8 @@ class ConfigHandler(object): # A list of keywords that can be found in a conninfo string. Follows what is acceptable by libpq keywords = ('dbname', 'user', 'passfile' if params.get('passfile') else 'password', 'host', 'port', 'sslmode', 'sslcompression', 'sslcert', 'sslkey', 'sslpassword', 'sslrootcert', 'sslcrl', - 'sslcrldir', 'application_name', 'krbsrvname', 'gssencmode', 'channel_binding') + 'sslcrldir', 'application_name', 'krbsrvname', 'gssencmode', 'channel_binding', + 'target_session_attrs') if include_dbname: params = params.copy() if 'dbname' not in params: @@ -542,6 +543,12 @@ class ConfigHandler(object): if use_slots and not (is_remote_master and member.no_replication_slot): primary_slot_name = member.primary_slot_name if is_remote_master else self._postgresql.name recovery_params['primary_slot_name'] = slot_name_from_member_name(primary_slot_name) + # We are a standby leader and are using a replication slot. Make sure we connect to + # the leader of the main cluster (in case more than one host is specified in the + # connstr) by adding 'target_session_attrs=read-write' to primary_conninfo. + if is_remote_master and 'target_sesions_attrs' not in primary_conninfo and\ + self._postgresql.major_version >= 100000: + primary_conninfo['target_session_attrs'] = 'read-write' recovery_params['primary_conninfo'] = primary_conninfo # standby_cluster config might have different parameters, we want to override them diff --git a/patroni/postgresql/rewind.py b/patroni/postgresql/rewind.py index 6c5bd59b..3d072444 100644 --- a/patroni/postgresql/rewind.py +++ b/patroni/postgresql/rewind.py @@ -154,6 +154,10 @@ class Rewind(object): ret = member.conn_kwargs(auth) if not ret.get('dbname'): ret['dbname'] = self._postgresql.database + # Add target_session_attrs in case more than one hostname is specified + # (libpq client-side failover) making sure we hit the primary + if 'target_session_attrs' not in ret and self._postgresql.major_version >= 100000: + ret['target_session_attrs'] = 'read-write' return ret def _check_timeline_and_lsn(self, leader): diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index f037fd4d..523072c2 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -260,8 +260,8 @@ class TestPostgresql(BaseTestPostgresql): with patch('patroni.postgresql.config.ConfigHandler.primary_conninfo_params', Mock(return_value=conninfo)): mock_get_pg_settings.return_value['recovery_min_apply_delay'][1] = '1' self.assertEqual(self.p.config.check_recovery_conf(None), (True, True)) - mock_get_pg_settings.return_value['primary_conninfo'][1] = 'host=1 passfile='\ - + re.sub(r'([\'\\ ])', r'\\\1', self.p.config._pgpass) + mock_get_pg_settings.return_value['primary_conninfo'][1] = 'host=1 target_session_attrs=read-write'\ + + ' passfile=' + re.sub(r'([\'\\ ])', r'\\\1', self.p.config._pgpass) mock_get_pg_settings.return_value['recovery_min_apply_delay'][1] = '0' self.assertEqual(self.p.config.check_recovery_conf(None), (True, True)) self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': conninfo.copy()})