Skip to content

Commit bd42c7c

Browse files
committed
multiregion: allow zone config extensions to remove read replicas
Previously, zone config extensions validated that num_replicas could not be set lower than the total number of replicas required for the database's survival goal (which includes both voters and non-voting read replicas). This prevented users from removing read replicas to reduce storage costs while maintaining the required number of voters for the survival goal. The survival goal only depends on the number of voting replicas (num_voters), not the total number of replicas. Non-voting replicas are used for follower reads and do not participate in Raft consensus or affect availability guarantees. This commit changes the validation in ExtendZoneConfigWithGlobal() and ExtendZoneConfigWithRegionalIn() to check that num_replicas is not set lower than num_voters instead of the total numReplicas. This allows users to set num_replicas equal to num_voters, effectively removing read replicas while maintaining the required voter count for their survival goal. For example, in a SURVIVE ZONE FAILURE database with 3 regions, users can now reduce num_replicas from 5 (default: 3 voters + 2 read replicas) to 3 (just the required voters), saving storage while maintaining zone failure tolerance. Also fixes a typo in the error message ("num_replica" -> "num_replicas"). Resolves: #105210 Release note (bug fix): Fixed a bug where zone config extensions incorrectly prevented users from removing non-voting read replicas from multi-region databases. Users can now set num_replicas equal to num_voters to remove read replicas while maintaining the required number of voting replicas for their database's survival goal. This allows reducing storage costs without compromising availability guarantees.
1 parent 1a4ff79 commit bd42c7c

File tree

2 files changed

+109
-25
lines changed

2 files changed

+109
-25
lines changed

pkg/ccl/logictestccl/testdata/logic_test/multi_region_zone_config_extensions

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,100 @@ statement error pq: zone config extension cannot unset the home region \(ca-cent
290290
ALTER DATABASE "mr-zone-configs" ALTER LOCALITY REGIONAL IN "ca-central-1" CONFIGURE ZONE USING
291291
lease_preferences = '[[+region=ap-southeast-2]]'
292292

293-
statement error pq: zone config extension cannot set num_replicas 4 that is lower than the one required for the survival goal: 5 with goal REGION_FAILURE
293+
statement error pq: cannot set num_replicas below 5 \(num_voters required for survival goal REGION_FAILURE\), got 4
294294
ALTER DATABASE "mr-zone-configs" ALTER LOCALITY GLOBAL CONFIGURE ZONE USING
295295
num_replicas = 4
296296

297-
statement error pq: zone config extension cannot set num_voters 4 that is lower than the one required for the survival goal: 5 with goal REGION_FAILURE
297+
statement error pq: cannot set num_voters below 5 \(num_voters required for survival goal REGION_FAILURE\), got 4
298298
ALTER DATABASE "mr-zone-configs" ALTER LOCALITY GLOBAL CONFIGURE ZONE USING
299299
num_voters = 4
300+
301+
# Test that num_replicas can be set to num_voters (removing read replicas).
302+
# This is allowed because survival goals depend on num_voters, not total replicas.
303+
304+
# SURVIVE REGION FAILURE with num_voters=5 should allow num_replicas=5 (removing read replicas).
305+
statement ok
306+
ALTER DATABASE "mr-zone-configs" ALTER LOCALITY GLOBAL CONFIGURE ZONE USING
307+
num_replicas = 5
308+
309+
query TT
310+
SHOW ZONE CONFIGURATION FOR DATABASE "mr-zone-configs"
311+
----
312+
DATABASE "mr-zone-configs" ALTER DATABASE "mr-zone-configs" CONFIGURE ZONE USING
313+
range_min_bytes = 134217728,
314+
range_max_bytes = 536870912,
315+
gc.ttlseconds = 14400,
316+
num_replicas = 9,
317+
num_voters = 5,
318+
constraints = '{+region=ap-southeast-2: 1, +region=ca-central-1: 1, +region=us-east-1: 1}',
319+
voter_constraints = '{+region=ca-central-1: 2}',
320+
lease_preferences = '[[+region=ca-central-1], [+region=ap-southeast-2]]'
321+
322+
statement ok
323+
ALTER DATABASE "mr-zone-configs" ALTER LOCALITY REGIONAL CONFIGURE ZONE USING
324+
num_replicas = 5
325+
326+
query TT
327+
SHOW ZONE CONFIGURATION FOR DATABASE "mr-zone-configs"
328+
----
329+
DATABASE "mr-zone-configs" ALTER DATABASE "mr-zone-configs" CONFIGURE ZONE USING
330+
range_min_bytes = 134217728,
331+
range_max_bytes = 536870912,
332+
gc.ttlseconds = 14400,
333+
num_replicas = 5,
334+
num_voters = 5,
335+
constraints = '{+region=ap-southeast-2: 1, +region=ca-central-1: 1, +region=us-east-1: 1}',
336+
voter_constraints = '{+region=ca-central-1: 2}',
337+
lease_preferences = '[[+region=ca-central-1], [+region=ap-southeast-2]]'
338+
339+
# num_replicas below num_voters should still fail.
340+
statement error pq: cannot set num_replicas below 5 \(num_voters required for survival goal REGION_FAILURE\), got 3
341+
ALTER DATABASE "mr-zone-configs" ALTER LOCALITY REGIONAL CONFIGURE ZONE USING
342+
num_replicas = 3
343+
344+
# Test SURVIVE ZONE FAILURE case.
345+
statement ok
346+
CREATE DATABASE "mr-zone-survival"
347+
primary region "ca-central-1"
348+
regions "ap-southeast-2", "us-east-1"
349+
survive zone failure
350+
351+
statement ok
352+
use "mr-zone-survival"
353+
354+
# SURVIVE ZONE with num_voters=3, num_replicas=5 by default.
355+
query TT
356+
SHOW ZONE CONFIGURATION FOR DATABASE "mr-zone-survival"
357+
----
358+
DATABASE "mr-zone-survival" ALTER DATABASE "mr-zone-survival" CONFIGURE ZONE USING
359+
range_min_bytes = 134217728,
360+
range_max_bytes = 536870912,
361+
gc.ttlseconds = 14400,
362+
num_replicas = 5,
363+
num_voters = 3,
364+
constraints = '{+region=ap-southeast-2: 1, +region=ca-central-1: 1, +region=us-east-1: 1}',
365+
voter_constraints = '[+region=ca-central-1]',
366+
lease_preferences = '[[+region=ca-central-1]]'
367+
368+
# Reduce num_replicas to num_voters (3) - removing read replicas.
369+
statement ok
370+
ALTER DATABASE "mr-zone-survival" ALTER LOCALITY REGIONAL CONFIGURE ZONE USING
371+
num_replicas = 3
372+
373+
query TT
374+
SHOW ZONE CONFIGURATION FOR DATABASE "mr-zone-survival"
375+
----
376+
DATABASE "mr-zone-survival" ALTER DATABASE "mr-zone-survival" CONFIGURE ZONE USING
377+
range_min_bytes = 134217728,
378+
range_max_bytes = 536870912,
379+
gc.ttlseconds = 14400,
380+
num_replicas = 3,
381+
num_voters = 3,
382+
constraints = '{+region=ap-southeast-2: 1, +region=ca-central-1: 1, +region=us-east-1: 1}',
383+
voter_constraints = '[+region=ca-central-1]',
384+
lease_preferences = '[[+region=ca-central-1]]'
385+
386+
# num_replicas below num_voters should still fail.
387+
statement error pq: cannot set num_replicas below 3 \(num_voters required for survival goal ZONE_FAILURE\), got 2
388+
ALTER DATABASE "mr-zone-survival" ALTER LOCALITY REGIONAL CONFIGURE ZONE USING
389+
num_replicas = 2

pkg/sql/catalog/multiregion/region_config.go

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -183,27 +183,24 @@ func (r *RegionConfig) ZoneConfigExtensions() descpb.ZoneConfigExtensions {
183183
// extensions to the provided zone configuration, returning the updated config.
184184
func (r *RegionConfig) ExtendZoneConfigWithGlobal(zc zonepb.ZoneConfig) (zonepb.ZoneConfig, error) {
185185
if ext := r.zoneCfgExtensions.Global; ext != nil {
186-
var numVoters, numReplicas int32
186+
var numVoters int32
187187
if zc.NumVoters != nil {
188188
numVoters = *zc.NumVoters
189189
}
190-
if zc.NumReplicas != nil {
191-
numReplicas = *zc.NumReplicas
192-
}
193190
zc = extendZoneCfg(zc, *ext)
194191
// TODO(janexing): to ensure that the zone config extension won't break the
195192
// survival goal, here we only add restriction on the number of voters and
196193
// replicas. We may want to consider adding constraint to their distribution
197194
// across zones/regions as well.
198195
if zc.NumVoters != nil && *zc.NumVoters < numVoters {
199-
return zonepb.ZoneConfig{}, errors.Newf("zone config extension "+
200-
"cannot set num_voters %v that is lower than the one required for the "+
201-
"survival goal: %v with goal %v\n", *zc.NumVoters, numVoters, r.SurvivalGoal())
196+
return zonepb.ZoneConfig{}, errors.Newf(
197+
"cannot set num_voters below %v (num_voters required for survival goal %v), got %v",
198+
numVoters, r.SurvivalGoal(), *zc.NumVoters)
202199
}
203-
if zc.NumReplicas != nil && *zc.NumReplicas < numReplicas {
204-
return zonepb.ZoneConfig{}, errors.Newf("zone config extension "+
205-
"cannot set num_replicas %v that is lower than the one required for the "+
206-
"survival goal: %v with goal %v\n", *zc.NumReplicas, numReplicas, r.SurvivalGoal())
200+
if zc.NumReplicas != nil && *zc.NumReplicas < numVoters {
201+
return zonepb.ZoneConfig{}, errors.Newf(
202+
"cannot set num_replicas below %v (num_voters required for survival goal %v), got %v",
203+
numVoters, r.SurvivalGoal(), *zc.NumReplicas)
207204
}
208205
}
209206
return zc, nil
@@ -215,13 +212,10 @@ func (r *RegionConfig) ExtendZoneConfigWithGlobal(zc zonepb.ZoneConfig) (zonepb.
215212
func (r *RegionConfig) ExtendZoneConfigWithRegionalIn(
216213
zc zonepb.ZoneConfig, region catpb.RegionName,
217214
) (zonepb.ZoneConfig, error) {
218-
var numVoters, numReplicas int32
215+
var numVoters int32
219216
if zc.NumVoters != nil {
220217
numVoters = *zc.NumVoters
221218
}
222-
if zc.NumReplicas != nil {
223-
numReplicas = *zc.NumReplicas
224-
}
225219

226220
if ext := r.zoneCfgExtensions.Regional; ext != nil {
227221
if ext.LeasePreferences != nil {
@@ -242,14 +236,14 @@ func (r *RegionConfig) ExtendZoneConfigWithRegionalIn(
242236
// replicas. We may want to consider adding constraint to their distribution
243237
// across zones/regions as well.
244238
if zc.NumVoters != nil && *zc.NumVoters < numVoters {
245-
return zonepb.ZoneConfig{}, errors.Newf("zone config extension "+
246-
"cannot set num_voters %v that is lower than the one required for the "+
247-
"survival goal: %v with goal %v\n", *zc.NumVoters, numVoters, r.SurvivalGoal())
248-
}
249-
if zc.NumReplicas != nil && *zc.NumReplicas < numReplicas {
250-
return zonepb.ZoneConfig{}, errors.Newf("zone config extension "+
251-
"cannot set num_replica %v that is lower than the one required for the "+
252-
"survival goal: %v with goal %v\n", *zc.NumReplicas, numReplicas, r.SurvivalGoal())
239+
return zonepb.ZoneConfig{}, errors.Newf(
240+
"cannot set num_voters below %v (num_voters required for survival goal %v), got %v",
241+
numVoters, r.SurvivalGoal(), *zc.NumVoters)
242+
}
243+
if zc.NumReplicas != nil && *zc.NumReplicas < numVoters {
244+
return zonepb.ZoneConfig{}, errors.Newf(
245+
"cannot set num_replicas below %v (num_voters required for survival goal %v), got %v",
246+
numVoters, r.SurvivalGoal(), *zc.NumReplicas)
253247
}
254248
return zc, nil
255249
}

0 commit comments

Comments
 (0)