Skip to content

Commit 14d963c

Browse files
authored
Allow server-side compression for basebackup option (#3538)
The `compress` option was completely blocked for basebackup, but since PostgreSQL 15, server-side compression is useful and works transparently with plain format. - Removed `compress` from blocked options list - Added validation to allow only `--compress=server*` values (e.g., server-zstd, server-gzip) - Reject client-side compression with helpful error message
1 parent defaa92 commit 14d963c

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed

patroni/postgresql/bootstrap.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,25 @@ def basebackup(self, conn_url: str, env: Dict[str, str], options: Dict[str, Any]
258258
# supports additional user-supplied options, those are not validated
259259
maxfailures = 2
260260
ret = 1
261-
not_allowed_options = ('pgdata', 'format', 'wal-method', 'xlog-method', 'gzip',
262-
'version', 'compress', 'dbname', 'host', 'port', 'username', 'password')
263-
user_options = process_user_options('basebackup', options, not_allowed_options, logger.error)
261+
not_allowed_options = ['pgdata', 'format', 'wal-method', 'xlog-method', 'gzip',
262+
'version', 'dbname', 'host', 'port', 'username', 'password']
263+
pg_version = self._postgresql.config.pg_version
264+
if pg_version < 150000:
265+
not_allowed_options.append('compress')
266+
user_options = process_user_options('basebackup', options, tuple(not_allowed_options), logger.error)
267+
# Validate compress option on PG15+: only server-side compression is allowed
268+
if pg_version >= 150000:
269+
validated_options: List[str] = []
270+
for opt in user_options:
271+
if opt.startswith('--compress='):
272+
if opt.startswith('--compress=server'):
273+
validated_options.append(opt)
274+
else:
275+
logger.error('compress option for basebackup must use server-side compression '
276+
'(e.g., server-gzip, server-zstd). Client-side compression is not allowed.')
277+
else:
278+
validated_options.append(opt)
279+
user_options = validated_options
264280
cmd = [
265281
self._postgresql.pgcommand("pg_basebackup"),
266282
"--pgdata=" + self._postgresql.data_dir,

tests/test_bootstrap.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def test_create_replica_old_format(self, mock_cancellable_subprocess_call):
9595

9696
@patch.object(CancellableSubprocess, 'call', Mock(return_value=0))
9797
@patch.object(Postgresql, 'data_directory_empty', Mock(return_value=True))
98+
@patch.object(ConfigHandler, 'pg_version', PropertyMock(return_value=150000))
9899
def test_basebackup(self):
99100
with patch('patroni.postgresql.bootstrap.logger.debug') as mock_debug:
100101
self.p.cancellable.cancel()
@@ -108,6 +109,32 @@ def test_basebackup(self):
108109
['pg_basebackup', f'--pgdata={self.p.data_dir}', '-X', 'stream', '--dbname=', '--foo=bar'],
109110
)
110111

112+
# Test compress option: server-side compression should be allowed on PG15+ (issue #3532)
113+
with patch('patroni.postgresql.bootstrap.logger.debug') as mock_debug:
114+
self.p.cancellable.reset_is_cancelled()
115+
self.b.basebackup("", None, {'compress': 'server-zstd'})
116+
mock_debug.assert_called_with(
117+
'calling: %r',
118+
['pg_basebackup', f'--pgdata={self.p.data_dir}', '-X', 'stream', '--dbname=', '--compress=server-zstd'],
119+
)
120+
121+
# Test compress option: client-side compression should be rejected on PG15+
122+
with patch('patroni.postgresql.bootstrap.logger.error') as mock_error:
123+
self.p.cancellable.reset_is_cancelled()
124+
self.b.basebackup("", None, {'compress': 'gzip'})
125+
mock_error.assert_called_with(
126+
'compress option for basebackup must use server-side compression '
127+
'(e.g., server-gzip, server-zstd). Client-side compression is not allowed.'
128+
)
129+
130+
# Test compress option: should be rejected on PG < 15
131+
with patch('patroni.postgresql.bootstrap.logger.error') as mock_error, \
132+
patch.object(ConfigHandler, 'pg_version', PropertyMock(return_value=140000)):
133+
self.p.cancellable.reset_is_cancelled()
134+
self.b.basebackup("", None, {'compress': 'server-zstd'})
135+
# compress is in not_allowed_options for PG < 15, error logged via process_user_options
136+
mock_error.assert_any_call('compress option for basebackup is not allowed')
137+
111138
def test__initdb(self):
112139
self.assertRaises(Exception, self.b.bootstrap, {'initdb': [{'pgdata': 'bar'}]})
113140
self.assertRaises(Exception, self.b.bootstrap, {'initdb': [{'foo': 'bar', 1: 2}]})

tests/test_ha.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def run_async(self, func, args=()):
189189
'Latest checkpoint location': '0/12345678',
190190
"Latest checkpoint's TimeLineID": '2'}))
191191
@patch.object(SlotsHandler, 'load_replication_slots', Mock(side_effect=Exception))
192+
@patch.object(ConfigHandler, 'pg_version', PropertyMock(return_value=180000))
192193
@patch.object(ConfigHandler, 'append_pg_hba', Mock())
193194
@patch.object(ConfigHandler, 'write_pgpass', Mock(return_value={}))
194195
@patch.object(ConfigHandler, 'write_recovery_conf', Mock())

0 commit comments

Comments
 (0)