Advance permanent slots for cascading nodes while in failsafe (#3100)

Lets consider a following replication setup:
```
primary->standby1->standby2(replicatefrom: standby1)
```

In this case the `primary` will not create a physical replication slot for standby2, because it is streaming from the `standby1`.

Things will look differently if we have the following dynamic configuration:
```yaml
slots:
    primary:
        type: physical
    standby1:
        type: physical
    standby2:
        type: physical
```

In this case `primary` will also have `standby2` physical replication slot, which periodically must be advanced. So far it was working by taking value of `xlog_location` from the `/members/standby2` key in DCS.

But, when DCS is down and failsafe mode is activate, the `standby2` physical slot on the `primary` will not not be moved, because there was not way to get the latest value of `xlog_location`.

This PR is addressing the problem by making replica nodes to return their `xlog_location` as `lsn` header in the response on `POST /failsafe` REST API request. The current primary will use these values to advance replication slots for nodes with `replicatefrom` tag.
This commit is contained in:
Alexander Kukushkin
2024-07-17 16:28:30 +02:00
committed by GitHub
parent b8b5518e8c
commit b1d442e7a4
6 changed files with 155 additions and 38 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ class MockResponse(object):
def __init__(self, status_code=200):
self.status_code = status_code
self.headers = {'content-type': 'json'}
self.headers = {'content-type': 'json', 'lsn': 100}
self.content = '{}'
self.reason = 'Not Found'
+7 -1
View File
@@ -536,6 +536,9 @@ class TestHa(PostgresInit):
def test_no_dcs_connection_primary_failsafe(self):
self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly'))
self.ha.cluster = get_cluster_initialized_with_leader_and_failsafe()
for m in self.ha.cluster.members:
if m.name != self.ha.cluster.leader.name:
m.data['tags']['replicatefrom'] = 'test'
global_config.update(self.ha.cluster)
self.ha.dcs._last_failsafe = self.ha.cluster.failsafe
self.ha.state_handler.name = self.ha.cluster.leader.name
@@ -551,13 +554,16 @@ class TestHa(PostgresInit):
'continue to run as a leader because failsafe mode is enabled and all members are accessible')
def test_no_dcs_connection_replica_failsafe(self):
self.p.last_operation = Mock(side_effect=PostgresConnectionException(''))
self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly'))
self.ha.cluster = get_cluster_initialized_with_leader_and_failsafe()
global_config.update(self.ha.cluster)
self.ha.update_failsafe({'name': 'leader', 'api_url': 'http://127.0.0.1:8008/patroni',
'conn_url': 'postgres://127.0.0.1:5432/postgres', 'slots': {'foo': 1000}})
self.p.is_primary = false
self.assertEqual(self.ha.run_cycle(), 'DCS is not accessible')
with patch('patroni.ha.logger.debug') as mock_logger:
self.assertEqual(self.ha.run_cycle(), 'DCS is not accessible')
self.assertEqual(mock_logger.call_args_list[0][0][0], 'Failed to fetch current wal lsn: %r')
def test_no_dcs_connection_replica_failsafe_not_enabled_but_active(self):
self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly'))