Skip to content

Commit

Permalink
Shard expiring object container
Browse files Browse the repository at this point in the history
All the expiring objects for a given X-Delete-At are funnelled into the
same expiring object container- this can act as a bottleneck.

Change-Id: I288a177a7ae3e213c727a2a81fa76d4ef9cf7eb3
  • Loading branch information
dpgoetz committed Aug 15, 2014
1 parent ac22c5e commit 0abd2cb
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 21 deletions.
10 changes: 10 additions & 0 deletions swift/common/utils.py
Expand Up @@ -2960,3 +2960,13 @@ def quote(value, safe='/'):
Patched version of urllib.quote that encodes utf-8 strings before quoting
"""
return _quote(get_valid_utf8_str(value), safe)


def get_expirer_container(x_delete_at, expirer_divisor, acc, cont, obj):
"""
Returns a expiring object container name for given X-Delete-At and
a/c/o.
"""
shard_int = int(hash_path(acc, cont, obj), 16) % 100
return normalize_delete_at_timestamp(
int(x_delete_at) / expirer_divisor * expirer_divisor - shard_int)
15 changes: 8 additions & 7 deletions swift/obj/server.py
Expand Up @@ -29,7 +29,8 @@

from swift.common.utils import public, get_logger, \
config_true_value, timing_stats, replication, \
normalize_delete_at_timestamp, get_log_line, Timestamp
normalize_delete_at_timestamp, get_log_line, Timestamp, \
get_expirer_container
from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_object_creation, \
valid_timestamp, check_utf8
Expand Down Expand Up @@ -284,9 +285,9 @@ def delete_at_update(self, op, delete_at, account, container, obj,
'best guess as to the container name for now.' % op)
# TODO(gholt): In a future release, change the above warning to
# a raised exception and remove the guess code below.
delete_at_container = (
int(delete_at) / self.expiring_objects_container_divisor *
self.expiring_objects_container_divisor)
delete_at_container = get_expirer_container(
delete_at, self.expiring_objects_container_divisor,
account, container, obj)
partition = headers_in.get('X-Delete-At-Partition', None)
hosts = headers_in.get('X-Delete-At-Host', '')
contdevices = headers_in.get('X-Delete-At-Device', '')
Expand All @@ -307,9 +308,9 @@ def delete_at_update(self, op, delete_at, account, container, obj,
# exist there and the original data is left where it is, where
# it will be ignored when the expirer eventually tries to issue the
# object DELETE later since the X-Delete-At value won't match up.
delete_at_container = str(
int(delete_at) / self.expiring_objects_container_divisor *
self.expiring_objects_container_divisor)
delete_at_container = get_expirer_container(
delete_at, self.expiring_objects_container_divisor,
account, container, obj)
delete_at_container = normalize_delete_at_timestamp(
delete_at_container)

Expand Down
12 changes: 7 additions & 5 deletions swift/proxy/controllers/obj.py
Expand Up @@ -38,7 +38,7 @@
from swift.common.utils import (
clean_content_type, config_true_value, ContextPool, csv_append,
GreenAsyncPile, GreenthreadSafeIterator, json, Timestamp,
normalize_delete_at_timestamp, public, quorum_size)
normalize_delete_at_timestamp, public, quorum_size, get_expirer_container)
from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_metadata, check_object_creation, \
check_copy_from_header
Expand Down Expand Up @@ -285,6 +285,7 @@ def POST(self, req):
req.headers['X-Backend-Storage-Policy-Index'] = policy_index
partition, nodes = obj_ring.get_nodes(
self.account_name, self.container_name, self.object_name)

req.headers['X-Timestamp'] = Timestamp(time.time()).internal

headers = self._backend_requests(
Expand Down Expand Up @@ -449,10 +450,11 @@ def _config_obj_expiration(self, req):

req.environ.setdefault('swift.log_info', []).append(
'x-delete-at:%s' % x_delete_at)
delete_at_container = normalize_delete_at_timestamp(
x_delete_at /
self.app.expiring_objects_container_divisor *
self.app.expiring_objects_container_divisor)

delete_at_container = get_expirer_container(
x_delete_at, self.app.expiring_objects_container_divisor,
self.account_name, self.container_name, self.object_name)

delete_at_part, delete_at_nodes = \
self.app.container_ring.get_nodes(
self.app.expiring_objects_account, delete_at_container)
Expand Down
8 changes: 7 additions & 1 deletion test/unit/obj/test_server.py
Expand Up @@ -3144,8 +3144,14 @@ def fake_async_update(*args):
'X-Trans-Id': '1234'})
self.object_controller.delete_at_update(
'DELETE', 12345678901, 'a', 'c', 'o', req, 'sda1', 0)
expiring_obj_container = given_args.pop(2)
expected_exp_cont = utils.get_expirer_container(
utils.normalize_delete_at_timestamp(12345678901),
86400, 'a', 'c', 'o')
self.assertEqual(expiring_obj_container, expected_exp_cont)

self.assertEquals(given_args, [
'DELETE', '.expiring_objects', '9999936000', '9999999999-a/c/o',
'DELETE', '.expiring_objects', '9999999999-a/c/o',
None, None, None,
HeaderKeyDict({
'X-Backend-Storage-Policy-Index': 0,
Expand Down
14 changes: 6 additions & 8 deletions test/unit/proxy/test_server.py
Expand Up @@ -4810,10 +4810,9 @@ def test_PUT_x_delete_at_with_fewer_container_replicas(self):
self.app.container_ring.set_replicas(2)

delete_at_timestamp = int(time.time()) + 100000
delete_at_container = str(
delete_at_timestamp /
self.app.expiring_objects_container_divisor *
self.app.expiring_objects_container_divisor)
delete_at_container = utils.get_expirer_container(
delete_at_timestamp, self.app.expiring_objects_container_divisor,
'a', 'c', 'o')
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
headers={'Content-Type': 'application/stuff',
'Content-Length': '0',
Expand Down Expand Up @@ -4847,10 +4846,9 @@ def test_PUT_x_delete_at_with_more_container_replicas(self):
self.app.expiring_objects_container_divisor = 60

delete_at_timestamp = int(time.time()) + 100000
delete_at_container = str(
delete_at_timestamp /
self.app.expiring_objects_container_divisor *
self.app.expiring_objects_container_divisor)
delete_at_container = utils.get_expirer_container(
delete_at_timestamp, self.app.expiring_objects_container_divisor,
'a', 'c', 'o')
req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
headers={'Content-Type': 'application/stuff',
'Content-Length': 0,
Expand Down

0 comments on commit 0abd2cb

Please sign in to comment.