Skip to content

Commit 29544a9

Browse files
committed
Use smaller quorum size in proxy for even numbers of replicas
Requiring 2/2 backends for PUT requests means that the cluster can't tolerate a single failure. Likewise, if you have 4 replicas in 2 regions, requiring 3/4 on a POST request means you cannot POST with your inter-region link down or congested. This changes the (replication) quorum size in the proxy to be at least half the nodes instead of a majority of the nodes. Daemons that were looking for a majority remain unchanged. The container reconciler, replicator, and updater still require majorities so their functioning is unchanged. Odd numbers of replicas are unaffected by this commit. Change-Id: I3b07ff0222aba6293ad7d60afe1747acafbe6ce4
1 parent ce6c850 commit 29544a9

File tree

9 files changed

+65
-48
lines changed

9 files changed

+65
-48
lines changed

swift/common/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2937,6 +2937,10 @@ def public(func):
29372937
return func
29382938

29392939

2940+
def majority_size(n):
2941+
return (n // 2) + 1
2942+
2943+
29402944
def quorum_size(n):
29412945
"""
29422946
quorum size as it applies to services that use 'replication' for data
@@ -2946,7 +2950,7 @@ def quorum_size(n):
29462950
Number of successful backend requests needed for the proxy to consider
29472951
the client request successful.
29482952
"""
2949-
return (n // 2) + 1
2953+
return (n + 1) // 2
29502954

29512955

29522956
def rsync_ip(ip):

swift/container/reconciler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
direct_head_container, direct_delete_container_object,
2626
direct_put_container_object, ClientException)
2727
from swift.common.internal_client import InternalClient, UnexpectedResponse
28-
from swift.common.utils import get_logger, split_path, quorum_size, \
28+
from swift.common.utils import get_logger, split_path, majority_size, \
2929
FileLikeIter, Timestamp, last_modified_date_to_timestamp, \
3030
LRUCache, decode_timestamps
3131

@@ -194,7 +194,7 @@ def add_to_reconciler_queue(container_ring, account, container, obj,
194194
server
195195
196196
:returns: .misplaced_object container name, False on failure. "Success"
197-
means a quorum of containers got the update.
197+
means a majority of containers got the update.
198198
"""
199199
container_name = get_reconciler_container_name(obj_timestamp)
200200
object_name = get_reconciler_obj_name(obj_policy_index, account,
@@ -232,7 +232,7 @@ def _check_success(*args, **kwargs):
232232
response_timeout=response_timeout)
233233

234234
successes = sum(pile)
235-
if successes >= quorum_size(len(nodes)):
235+
if successes >= majority_size(len(nodes)):
236236
return container_name
237237
else:
238238
return False
@@ -289,7 +289,7 @@ def direct_get_container_policy_index(container_ring, account_name,
289289
:param container_ring: ring in which to look up the container locations
290290
:param account_name: name of the container's account
291291
:param container_name: name of the container
292-
:returns: storage policy index, or None if it couldn't get a quorum
292+
:returns: storage policy index, or None if it couldn't get a majority
293293
"""
294294
def _eat_client_exception(*args):
295295
try:
@@ -307,7 +307,7 @@ def _eat_client_exception(*args):
307307
container_name)
308308

309309
headers = [x for x in pile if x is not None]
310-
if len(headers) < quorum_size(len(nodes)):
310+
if len(headers) < majority_size(len(nodes)):
311311
return
312312
return best_policy_index(headers)
313313

swift/container/replicator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from swift.common.http import is_success
3232
from swift.common.db import DatabaseAlreadyExists
3333
from swift.common.utils import (Timestamp, hash_path,
34-
storage_directory, quorum_size)
34+
storage_directory, majority_size)
3535

3636

3737
class ContainerReplicator(db_replicator.Replicator):
@@ -202,9 +202,9 @@ def _post_replicate_hook(self, broker, info, responses):
202202
broker.update_reconciler_sync(info['max_row'])
203203
return
204204
max_sync = self.dump_to_reconciler(broker, point)
205-
success = responses.count(True) >= quorum_size(len(responses))
205+
success = responses.count(True) >= majority_size(len(responses))
206206
if max_sync > point and success:
207-
# to be safe, only slide up the sync point with a quorum on
207+
# to be safe, only slide up the sync point with a majority on
208208
# replication
209209
broker.update_reconciler_sync(max_sync)
210210

swift/container/updater.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from swift.common.exceptions import ConnectionTimeout
3232
from swift.common.ring import Ring
3333
from swift.common.utils import get_logger, config_true_value, ismount, \
34-
dump_recon_cache, quorum_size, Timestamp
34+
dump_recon_cache, majority_size, Timestamp
3535
from swift.common.daemon import Daemon
3636
from swift.common.http import is_success, HTTP_INTERNAL_SERVER_ERROR
3737

@@ -238,7 +238,7 @@ def process_container(self, dbfile):
238238
for event in events:
239239
if is_success(event.wait()):
240240
successes += 1
241-
if successes >= quorum_size(len(events)):
241+
if successes >= majority_size(len(events)):
242242
self.logger.increment('successes')
243243
self.successes += 1
244244
self.logger.debug(

test/unit/common/test_storage_policy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,9 +1119,9 @@ def test_singleton_passthrough(self):
11191119

11201120
def test_quorum_size_replication(self):
11211121
expected_sizes = {1: 1,
1122-
2: 2,
1122+
2: 1,
11231123
3: 2,
1124-
4: 3,
1124+
4: 2,
11251125
5: 3}
11261126
for n, expected in expected_sizes.items():
11271127
policy = StoragePolicy(0, 'zero',

test/unit/common/test_utils.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2504,13 +2504,23 @@ def test_streq_const_time(self):
25042504
self.assertFalse(utils.streq_const_time('a', 'aaaaa'))
25052505
self.assertFalse(utils.streq_const_time('ABC123', 'abc123'))
25062506

2507-
def test_replication_quorum_size(self):
2507+
def test_quorum_size(self):
2508+
expected_sizes = {1: 1,
2509+
2: 1,
2510+
3: 2,
2511+
4: 2,
2512+
5: 3}
2513+
got_sizes = dict([(n, utils.quorum_size(n))
2514+
for n in expected_sizes])
2515+
self.assertEqual(expected_sizes, got_sizes)
2516+
2517+
def test_majority_size(self):
25082518
expected_sizes = {1: 1,
25092519
2: 2,
25102520
3: 2,
25112521
4: 3,
25122522
5: 3}
2513-
got_sizes = dict([(n, utils.quorum_size(n))
2523+
got_sizes = dict([(n, utils.majority_size(n))
25142524
for n in expected_sizes])
25152525
self.assertEqual(expected_sizes, got_sizes)
25162526

test/unit/proxy/controllers/test_account.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -320,16 +320,16 @@ def test_response_code_for_PUT(self):
320320
((201, 201, 201, 201), 201),
321321
((201, 201, 201, 404), 201),
322322
((201, 201, 201, 503), 201),
323-
((201, 201, 404, 404), 503),
324-
((201, 201, 404, 503), 503),
325-
((201, 201, 503, 503), 503),
323+
((201, 201, 404, 404), 201),
324+
((201, 201, 404, 503), 201),
325+
((201, 201, 503, 503), 201),
326326
((201, 404, 404, 404), 404),
327-
((201, 404, 404, 503), 503),
327+
((201, 404, 404, 503), 404),
328328
((201, 404, 503, 503), 503),
329329
((201, 503, 503, 503), 503),
330330
((404, 404, 404, 404), 404),
331331
((404, 404, 404, 503), 404),
332-
((404, 404, 503, 503), 503),
332+
((404, 404, 503, 503), 404),
333333
((404, 503, 503, 503), 503),
334334
((503, 503, 503, 503), 503)
335335
]
@@ -340,16 +340,16 @@ def test_response_code_for_DELETE(self):
340340
((204, 204, 204, 204), 204),
341341
((204, 204, 204, 404), 204),
342342
((204, 204, 204, 503), 204),
343-
((204, 204, 404, 404), 503),
344-
((204, 204, 404, 503), 503),
345-
((204, 204, 503, 503), 503),
343+
((204, 204, 404, 404), 204),
344+
((204, 204, 404, 503), 204),
345+
((204, 204, 503, 503), 204),
346346
((204, 404, 404, 404), 404),
347-
((204, 404, 404, 503), 503),
347+
((204, 404, 404, 503), 404),
348348
((204, 404, 503, 503), 503),
349349
((204, 503, 503, 503), 503),
350350
((404, 404, 404, 404), 404),
351351
((404, 404, 404, 503), 404),
352-
((404, 404, 503, 503), 503),
352+
((404, 404, 503, 503), 404),
353353
((404, 503, 503, 503), 503),
354354
((503, 503, 503, 503), 503)
355355
]
@@ -360,16 +360,16 @@ def test_response_code_for_POST(self):
360360
((204, 204, 204, 204), 204),
361361
((204, 204, 204, 404), 204),
362362
((204, 204, 204, 503), 204),
363-
((204, 204, 404, 404), 503),
364-
((204, 204, 404, 503), 503),
365-
((204, 204, 503, 503), 503),
363+
((204, 204, 404, 404), 204),
364+
((204, 204, 404, 503), 204),
365+
((204, 204, 503, 503), 204),
366366
((204, 404, 404, 404), 404),
367-
((204, 404, 404, 503), 503),
367+
((204, 404, 404, 503), 404),
368368
((204, 404, 503, 503), 503),
369369
((204, 503, 503, 503), 503),
370370
((404, 404, 404, 404), 404),
371371
((404, 404, 404, 503), 404),
372-
((404, 404, 503, 503), 503),
372+
((404, 404, 503, 503), 404),
373373
((404, 503, 503, 503), 503),
374374
((503, 503, 503, 503), 503)
375375
]

test/unit/proxy/controllers/test_base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -662,12 +662,15 @@ def test_base_have_quorum(self):
662662
base = Controller(self.app)
663663
# just throw a bunch of test cases at it
664664
self.assertEqual(base.have_quorum([201, 404], 3), False)
665-
self.assertEqual(base.have_quorum([201, 201], 4), False)
666-
self.assertEqual(base.have_quorum([201, 201, 404, 404], 4), False)
667-
self.assertEqual(base.have_quorum([201, 503, 503, 201], 4), False)
665+
self.assertEqual(base.have_quorum([201, 201], 4), True)
666+
self.assertEqual(base.have_quorum([201], 4), False)
667+
self.assertEqual(base.have_quorum([201, 201, 404, 404], 4), True)
668+
self.assertEqual(base.have_quorum([201, 302, 418, 503], 4), False)
669+
self.assertEqual(base.have_quorum([201, 503, 503, 201], 4), True)
668670
self.assertEqual(base.have_quorum([201, 201], 3), True)
669671
self.assertEqual(base.have_quorum([404, 404], 3), True)
670672
self.assertEqual(base.have_quorum([201, 201], 2), True)
673+
self.assertEqual(base.have_quorum([201, 404], 2), True)
671674
self.assertEqual(base.have_quorum([404, 404], 2), True)
672675
self.assertEqual(base.have_quorum([201, 404, 201, 201], 4), True)
673676

test/unit/proxy/controllers/test_container.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -278,16 +278,16 @@ def test_response_code_for_PUT(self):
278278
((201, 201, 201, 201), 201),
279279
((201, 201, 201, 404), 201),
280280
((201, 201, 201, 503), 201),
281-
((201, 201, 404, 404), 503),
282-
((201, 201, 404, 503), 503),
283-
((201, 201, 503, 503), 503),
281+
((201, 201, 404, 404), 201),
282+
((201, 201, 404, 503), 201),
283+
((201, 201, 503, 503), 201),
284284
((201, 404, 404, 404), 404),
285-
((201, 404, 404, 503), 503),
285+
((201, 404, 404, 503), 404),
286286
((201, 404, 503, 503), 503),
287287
((201, 503, 503, 503), 503),
288288
((404, 404, 404, 404), 404),
289289
((404, 404, 404, 503), 404),
290-
((404, 404, 503, 503), 503),
290+
((404, 404, 503, 503), 404),
291291
((404, 503, 503, 503), 503),
292292
((503, 503, 503, 503), 503)
293293
]
@@ -298,16 +298,16 @@ def test_response_code_for_DELETE(self):
298298
((204, 204, 204, 204), 204),
299299
((204, 204, 204, 404), 204),
300300
((204, 204, 204, 503), 204),
301-
((204, 204, 404, 404), 503),
302-
((204, 204, 404, 503), 503),
303-
((204, 204, 503, 503), 503),
301+
((204, 204, 404, 404), 204),
302+
((204, 204, 404, 503), 204),
303+
((204, 204, 503, 503), 204),
304304
((204, 404, 404, 404), 404),
305-
((204, 404, 404, 503), 503),
305+
((204, 404, 404, 503), 404),
306306
((204, 404, 503, 503), 503),
307307
((204, 503, 503, 503), 503),
308308
((404, 404, 404, 404), 404),
309309
((404, 404, 404, 503), 404),
310-
((404, 404, 503, 503), 503),
310+
((404, 404, 503, 503), 404),
311311
((404, 503, 503, 503), 503),
312312
((503, 503, 503, 503), 503)
313313
]
@@ -318,16 +318,16 @@ def test_response_code_for_POST(self):
318318
((204, 204, 204, 204), 204),
319319
((204, 204, 204, 404), 204),
320320
((204, 204, 204, 503), 204),
321-
((204, 204, 404, 404), 503),
322-
((204, 204, 404, 503), 503),
323-
((204, 204, 503, 503), 503),
321+
((204, 204, 404, 404), 204),
322+
((204, 204, 404, 503), 204),
323+
((204, 204, 503, 503), 204),
324324
((204, 404, 404, 404), 404),
325-
((204, 404, 404, 503), 503),
325+
((204, 404, 404, 503), 404),
326326
((204, 404, 503, 503), 503),
327327
((204, 503, 503, 503), 503),
328328
((404, 404, 404, 404), 404),
329329
((404, 404, 404, 503), 404),
330-
((404, 404, 503, 503), 503),
330+
((404, 404, 503, 503), 404),
331331
((404, 503, 503, 503), 503),
332332
((503, 503, 503, 503), 503)
333333
]

0 commit comments

Comments
 (0)