mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Archive possibly missing WALs before rewind (#2384)
There is currently a risk to lose some WAL segments entirely in case archive_mode was set to 'on' before a promotion and there are some WALs with .ready files on the former leader we are trying to rewind. It happens because of the pg_rewind's modus operandi: it simply syncs the content of pg_wal directory of the old leader with the new leader's one. Including deletion of all WALs that are not present on the current leader, regardless their archive status on the former one. Thus, in case the new leader has already recycled such files, we just remove them entirely. In case archive_mode was set to 'always' and the .ready WALs are acrually present in archive, it is for end user who writes the archive_command to avoid overwritting and to properly test it. Co-authored-by: Alexander Kukushkin <[email protected]>
This commit is contained in:
co-authored by
Alexander Kukushkin
parent
b6f057850a
commit
ae0ede6944
@@ -218,6 +218,62 @@ class TestRewind(BaseTestPostgresql):
|
||||
self.r.cleanup_archive_status()
|
||||
self.r.cleanup_archive_status()
|
||||
|
||||
@patch('os.path.isfile', Mock(return_value=True))
|
||||
@patch('shutil.move', Mock(side_effect=OSError))
|
||||
@patch('patroni.postgresql.rewind.logger.info')
|
||||
def test_archive_ready_wals(self, mock_logger_info):
|
||||
with patch('os.listdir', Mock(side_effect=OSError)), \
|
||||
patch.object(Postgresql, 'get_guc_value', Mock(side_effect=['on', 'command %f'])):
|
||||
self.r._archive_ready_wals()
|
||||
mock_logger_info.assert_not_called()
|
||||
|
||||
# each assert_not_called() calls get_guc_value('archive_mode') + get_guc_value('archive_command')
|
||||
get_guc_value_res = [
|
||||
'', 'command %f',
|
||||
'on', '',
|
||||
]
|
||||
with patch.object(Postgresql, 'get_guc_value', Mock(side_effect=get_guc_value_res)):
|
||||
for _ in range(len(get_guc_value_res)//2):
|
||||
self.r._archive_ready_wals()
|
||||
mock_logger_info.assert_not_called()
|
||||
|
||||
with patch('os.listdir', Mock(return_value=['000000000000000000000000.ready'])):
|
||||
# successful archive_command call
|
||||
with patch.object(CancellableSubprocess, 'call', Mock(return_value=0)):
|
||||
get_guc_value_res = [
|
||||
'on', 'command %f',
|
||||
'always', 'command %f',
|
||||
]
|
||||
with patch.object(Postgresql, 'get_guc_value', Mock(side_effect=get_guc_value_res)):
|
||||
for _ in range(len(get_guc_value_res)//2):
|
||||
self.r._archive_ready_wals()
|
||||
mock_logger_info.assert_called_once()
|
||||
self.assertEqual(('Trying to archive %s: %s',
|
||||
'000000000000000000000000', 'command 000000000000000000000000'),
|
||||
mock_logger_info.call_args[0])
|
||||
mock_logger_info.reset_mock()
|
||||
|
||||
# failed archive_command call
|
||||
with patch.object(CancellableSubprocess, 'call', Mock(return_value=1)):
|
||||
with patch.object(Postgresql, 'get_guc_value', Mock(side_effect=['on', 'command %f'])):
|
||||
self.r._archive_ready_wals()
|
||||
self.assertEqual(('Trying to archive %s: %s',
|
||||
'000000000000000000000000', 'command 000000000000000000000000'),
|
||||
mock_logger_info.call_args_list[0][0])
|
||||
self.assertEqual(('Failed to archive WAL segment %s', '000000000000000000000000'),
|
||||
mock_logger_info.call_args_list[1][0])
|
||||
mock_logger_info.reset_mock()
|
||||
|
||||
wal_files_to_skip = [
|
||||
'000000000000000000000000.done',
|
||||
'000000000000000000000001.partial.done',
|
||||
'002.ready',
|
||||
'U00000000000000000000001.ready',
|
||||
]
|
||||
with patch('os.listdir', Mock(return_value=wal_files_to_skip)):
|
||||
self.r._archive_ready_wals()
|
||||
mock_logger_info.assert_not_called()
|
||||
|
||||
@patch('os.unlink', Mock())
|
||||
@patch('os.listdir', Mock(return_value=[]))
|
||||
@patch('os.path.isfile', Mock(return_value=True))
|
||||
|
||||
Reference in New Issue
Block a user