mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Take into account current role when deciding on removal of member ZNode (#2884)
Patroni doesn't watch on all changes of member keys in order to not create too much load on ZooKeeper, but only subscribes to changes (ZNodes added or deleted) in the `/member` directory. Therefore when some important fields in the value are updated we remove and recreate ZNode in order to notify the leader or other members. The leader should remove the member key only when the `checkpoint_after_promote` value is changed and replicas when the `state` is changed to/from `running`. We don't care about the `version` field, because Patroni version can't be changed without restart, what will case ZooKeeper `session_id` to change it anyway. This fix hopefully will reduce failures of behave tests on GH Actions.
This commit is contained in:
@@ -68,6 +68,7 @@ Scenario: check API requests for the primary-replica pair in the pause mode
|
||||
When I kill postmaster on postgres1
|
||||
And I issue a GET request to http://127.0.0.1:8009/replica
|
||||
Then I receive a response code 503
|
||||
And "members/postgres1" key in DCS has state=stopped after 10 seconds
|
||||
When I run patronictl.py restart batman postgres1 --force
|
||||
Then I receive a response returncode 0
|
||||
Then replication works from postgres0 to postgres1 after 20 seconds
|
||||
@@ -76,7 +77,7 @@ Scenario: check API requests for the primary-replica pair in the pause mode
|
||||
Then I receive a response code 200
|
||||
And I receive a response state running
|
||||
And I receive a response role replica
|
||||
When I run patronictl.py reinit batman postgres1 --force
|
||||
When I run patronictl.py reinit batman postgres1 --force --wait
|
||||
Then I receive a response returncode 0
|
||||
And I receive a response output "Success: reinitialize for member postgres1"
|
||||
And postgres1 role is the secondary after 30 seconds
|
||||
|
||||
+22
-15
@@ -393,21 +393,28 @@ class ZooKeeper(AbstractDCS):
|
||||
cluster = self.cluster
|
||||
member = cluster and cluster.get_member(self._name, fallback_to_leader=False)
|
||||
member_data = self.__last_member_data or member and member.data
|
||||
# We want to notify leader if some important fields in the member key changed by removing ZNode
|
||||
if member and (self._client.client_id is not None and member.session != self._client.client_id[0]
|
||||
or not (member_data and deep_compare(member_data.get('tags', {}), data.get('tags', {}))
|
||||
and (member_data.get('state') == data.get('state')
|
||||
or 'running' not in (member_data.get('state'), data.get('state')))
|
||||
and member_data.get('version') == data.get('version')
|
||||
and member_data.get('checkpoint_after_promote')
|
||||
== data.get('checkpoint_after_promote'))):
|
||||
try:
|
||||
self._client.delete_async(self.member_path).get(timeout=1)
|
||||
except NoNodeError:
|
||||
pass
|
||||
except Exception:
|
||||
return False
|
||||
member = None
|
||||
if member and member_data:
|
||||
is_leader = data.get('role') in ('master', 'primary', 'standby_leader')
|
||||
checkpoint_after_promote_changed = member_data.get('checkpoint_after_promote') \
|
||||
!= data.get('checkpoint_after_promote')
|
||||
state_running_changed = member_data.get('state') != data.get('state') \
|
||||
and 'running' in (member_data.get('state'), data.get('state'))
|
||||
tags_changed = not deep_compare(member_data.get('tags', {}), data.get('tags', {}))
|
||||
|
||||
# We want delete the member ZNode if:
|
||||
# - our session doesn't match with session id on our member key; or
|
||||
# - we want to notify leader if some important fields in the member key changed; or
|
||||
# - if we are the leader and want to notify replicas about checkpoint_after_promote;
|
||||
if self._client.client_id is not None and member.session != self._client.client_id[0] \
|
||||
or is_leader and checkpoint_after_promote_changed \
|
||||
or not is_leader and (state_running_changed or tags_changed):
|
||||
try:
|
||||
self._client.delete_async(self.member_path).get(timeout=1)
|
||||
except NoNodeError:
|
||||
pass
|
||||
except Exception:
|
||||
return False
|
||||
member = None
|
||||
|
||||
encoded_data = json.dumps(data, separators=(',', ':')).encode('utf-8')
|
||||
if member and member_data:
|
||||
|
||||
@@ -276,6 +276,7 @@ class TestZooKeeper(unittest.TestCase):
|
||||
self.assertTrue(self.zk.delete_cluster())
|
||||
|
||||
def test_watch(self):
|
||||
self.zk.event.wait = Mock()
|
||||
self.zk.watch(None, 0)
|
||||
self.zk.event.is_set = Mock(return_value=True)
|
||||
self.zk._fetch_status = False
|
||||
|
||||
Reference in New Issue
Block a user