diff --git a/docs/replication_modes.rst b/docs/replication_modes.rst index b50a972e..676b9b5f 100644 --- a/docs/replication_modes.rst +++ b/docs/replication_modes.rst @@ -66,7 +66,7 @@ Note: Because of the way synchronous replication is implemented in PostgreSQL it Synchronous Replication Factor ============================== -The parameter ``synchronous_node_count`` is used by Patroni to manage number of synchronous standby databases. It is set to 1 by default. It has no effect when ``synchronous_mode`` is set to off. When enabled, Patroni manages precise number of synchronous standby databases based on parameter ``synchronous_node_count`` and adjusts the state in DCS & ``synchronous_standby_names`` in PostgreSQL as members join and leave. If the parameter is set to a value higher than the number of eligible nodes it will be automatically reduced by Patroni down. +The parameter ``synchronous_node_count`` is used by Patroni to manage the number of synchronous standby databases. It is set to ``1`` by default. It has no effect when ``synchronous_mode`` is set to ``off``. When enabled, Patroni manages the precise number of synchronous standby databases based on parameter ``synchronous_node_count`` and adjusts the state in DCS & ``synchronous_standby_names`` in PostgreSQL as members join and leave. If the parameter is set to a value higher than the number of eligible nodes it will be automatically reduced by Patroni. Maximum lag on synchronous node @@ -173,7 +173,7 @@ postgresql.conf synchronous_standby_names = 'ANY 2 (node1,node2,node3)' -If the primary (``node0``) failed, in the above example two of the ``node1``, ``node2``, ``node3`` will have the latest transaction received, but we don't know which ones. To figure out whether the node ``node1`` has received the latest transaction, we needs to compare its LSN with the LSN on **at least** one node (``quorum=1`` in the ``/sync`` key) of ``node2`` and ``node3``. If ``node1`` isn't behind of at least one of them, we can guaranty that there will be no user visible data loss if ``node1`` is promoted. +If the primary (``node0``) failed, in the above example two of the ``node1``, ``node2``, ``node3`` will have the latest transaction received, but we don't know which ones. To figure out whether the node ``node1`` has received the latest transaction, we need to compare its LSN with the LSN on **at least** one node (``quorum=1`` in the ``/sync`` key) among ``node2`` and ``node3``. If ``node1`` isn't behind of at least one of them, we can guarantee that there will be no user visible data loss if ``node1`` is promoted. .. [1] The data is still there, but recovering it requires a manual recovery effort by data recovery specialists. When Patroni is allowed to rewind with ``use_pg_rewind`` the forked timeline will be automatically erased to rejoin the failed primary with the cluster. diff --git a/patroni/postgresql/sync.py b/patroni/postgresql/sync.py index 1b9fda10..8716ad8c 100644 --- a/patroni/postgresql/sync.py +++ b/patroni/postgresql/sync.py @@ -158,7 +158,7 @@ class _SyncState(NamedTuple): :ivar sync_type: possible values: 'off', 'priority', 'quorum' :ivar numsync: how many nodes are required to be synchronous (according to ``synchronous_standby_names``). - Is ``0`` in case if synchronous_standby_names value is invalid or has ``*``. + Is ``0`` in case if ``synchronous_standby_names`` value is invalid or has ``*``. :ivar numsync_confirmed: how many nodes are known to be synchronous according to the ``pg_stat_replication`` view. Only nodes that caught up with the ``SyncHandler._primary_flush_lsn` are counted. :ivar sync: collection of synchronous node names. In case of quorum commit all nodes listed @@ -222,21 +222,21 @@ END;$$""") self._postgresql.reset_cluster_info_state(None) # Reset internal cache to query fresh values def _get_replica_list(self, cluster: Cluster) -> Iterator[Tuple[int, str, str, int, bool]]: - """Yields candidates based on higher replay/remote_write/flush lsn. + """Yields candidates based on higher replay/write/flush LSN. .. note:: - Tuples are reverse ordered by sync_state and LSN fields so nodes that already synchronous or having + Tuples are reverse ordered by ``sync_state`` and LSN fields so nodes that already synchronous or having higher LSN values are preferred. :param cluster: current cluster topology from DCS. :yields: tuples composed of: - * pid - a PID of walsender process - * member name - matches with the application_name - * sync_state - one of ("async", "potential", "quorum", "sync") - * LSN - write_lsn, flush_lsn, or replica_lsn, depending on the value of ``synchronous_commit`` GUC - * nofailover - whether the member has ``nofailover`` tag set + * ``pid`` - PID of the walsender process + * ``member name`` - matches with the ``application_name``` + * ``sync_state`` - one of (``async``, ``potential``, ``quorum``, ``sync``) + * ``LSN`` - write_lsn, flush_lsn, or replica_lsn, depending on the value of ``synchronous_commit`` GUC + * ``nofailover`` - whether the member has ``nofailover`` tag set """ # What column from pg_stat_replication we want to sort on? Choose based on ``synchronous_commit`` value. @@ -262,7 +262,7 @@ END;$$""") yield (pid, member.name, sync_state, replica_lsn, bool(member.nofailover)) def _process_replica_readiness(self, cluster: Cluster, replica_list: List[Tuple[int, str, str, int, bool]]) -> None: - """Flags replicas as truely "synchronous" when they caught up with "_primary_flush_lsn".""" + """Flags replicas as truly "synchronous" when they caught up with "_primary_flush_lsn".""" if TYPE_CHECKING: # pragma: no cover assert self._postgresql.global_config is not None for pid, app_name, sync_state, replica_lsn, _ in replica_list: diff --git a/patroni/utils.py b/patroni/utils.py index f976df3a..3916484a 100644 --- a/patroni/utils.py +++ b/patroni/utils.py @@ -769,7 +769,7 @@ def cluster_as_json(cluster: 'Cluster', global_config: Optional['GlobalConfig'] for m in cluster.members: if m.name == leader_name: role = 'standby_leader' if global_config.is_standby_cluster else 'leader' - elif cluster.sync.matches(m.name, global_config.is_quorum_commit_mode): + elif cluster.sync.matches(m.name): role = sync_role else: role = 'replica'