A few fixes in synchronous_mode (#2741)

- make sure that physical replication slots are created even before the promote happened (when async executor is busy with promote).
- execute `txid_current()` with `synchronous_commit=off` so it doesn't accidentally wait for absent synchronous standbys when `synchronous_mode_strict` is enable and `synchronous_standby_names=*`. These standbys can't connect because replication slots weren't there.
- `synchronous_standby_names` wasn't set to `*` after bootstrap with `synchronous_mode` and `synchronous_mode_strict`.
- add `-c statement_timeout=0` to `PGOPTIONS` when executing `post_bootstrap` script.

Close https://github.com/zalando/patroni/issues/2738
This commit is contained in:
Alexander Kukushkin
2023-07-12 09:43:40 +02:00
committed by GitHub
parent 6e96db173f
commit e4fe239a9d
4 changed files with 23 additions and 9 deletions
+14 -6
View File
@@ -1541,6 +1541,9 @@ class Ha(object):
self.dcs.set_config_value(json.dumps(self.patroni.config.dynamic_configuration, separators=(',', ':')))
self.dcs.take_leader()
self.set_is_leader(True)
if self.is_synchronous_mode():
self.state_handler.sync_handler.set_synchronous_standby_names(
CaseInsensitiveSet('*') if self.global_config.is_synchronous_mode_strict else CaseInsensitiveSet())
self.state_handler.call_nowait(CallbackAction.ON_START)
self.load_cluster_from_dcs()
@@ -1734,16 +1737,21 @@ class Ha(object):
msg = self.process_healthy_cluster()
ret = self.evaluate_scheduled_restart() or msg
# we might not have a valid PostgreSQL connection here if another thread
# stops PostgreSQL, therefore, we only reload replication slots if no
# asynchronous processes are running (should be always the case for the primary)
if not self._async_executor.busy and not self.state_handler.is_starting():
# We might not have a valid PostgreSQL connection here if AsyncExecutor is doing
# something with PostgreSQL. Therefore we will sync replication slots only if no
# asynchronous processes are running or we know that this is a standby being promoted.
# But, we don't want to run pg_rewind checks or copy logical slots from itself,
# therefore we have a couple additional `not is_promoting` checks.
is_promoting = self._async_executor.scheduled_action == 'promote'
if (not self._async_executor.busy or is_promoting) and not self.state_handler.is_starting():
create_slots = self._sync_replication_slots(False)
if not self.state_handler.cb_called:
if not self.state_handler.is_leader():
if not is_promoting and not self.state_handler.is_leader():
self._rewind.trigger_check_diverged_lsn()
self.state_handler.call_nowait(CallbackAction.ON_START)
if create_slots and self.cluster.leader:
if not is_promoting and create_slots and self.cluster.leader:
err = self._async_executor.try_run_async('copy_logical_slots',
self.state_handler.slots_handler.copy_logical_slots,
args=(self.cluster, create_slots))
+1 -1
View File
@@ -185,7 +185,7 @@ class Bootstrap(object):
r['host'] = 'localhost' # set it to localhost to write into pgpass
env = self._postgresql.config.write_pgpass(r)
env['PGOPTIONS'] = '-c synchronous_commit=local'
env['PGOPTIONS'] = '-c synchronous_commit=local -c statement_timeout=0'
try:
ret = self._postgresql.cancellable.call(shlex.split(cmd) + [connstring], env=env)
+7 -2
View File
@@ -193,7 +193,12 @@ class SyncHandler(object):
# Newly connected replicas will be counted as sync only when reached self._primary_flush_lsn
self._primary_flush_lsn = self._postgresql.last_operation()
self._postgresql.query('SELECT pg_catalog.txid_current()') # Ensure some WAL traffic to move replication
# Ensure some WAL traffic to move replication
self._postgresql.query("""DO $$
BEGIN
SET local synchronous_commit = 'off';
PERFORM * FROM pg_catalog.txid_current();
END;$$""")
self._postgresql.reset_cluster_info_state(None) # Reset internal cache to query fresh values
def current_state(self, cluster: Cluster) -> Tuple[CaseInsensitiveSet, CaseInsensitiveSet]:
@@ -289,6 +294,6 @@ class SyncHandler(object):
# Reset internal cache to query fresh values
self._postgresql.reset_cluster_info_state(None)
# timeline == 0 -- indicates that this is the replica, shoudn't ever happen
# timeline == 0 -- indicates that this is the replica
if self._postgresql.get_primary_timeline() > 0:
self._handle_synchronous_standby_names_change()
+1
View File
@@ -583,6 +583,7 @@ class TestHa(PostgresInit):
self.p.is_leader = false
self.assertEqual(self.ha.run_cycle(), 'waiting for end of recovery after bootstrap')
self.p.is_leader = true
self.ha.is_synchronous_mode = true
self.assertEqual(self.ha.run_cycle(), 'running post_bootstrap')
self.assertEqual(self.ha.run_cycle(), 'initialized a new cluster')