Skip to content

Commit 62622e4

Browse files
committed
feat: implement full jitter retry strategy
1 parent 4015c90 commit 62622e4

File tree

4 files changed

+16
-36
lines changed

4 files changed

+16
-36
lines changed

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ The batching is configurable by ``write_options``\ :
265265
- the maximum delay between each retry attempt in milliseconds
266266
- ``180_000``
267267
* - **exponential_base**
268-
- the base for the exponential retry delay, the next delay is computed as ``retry_interval * exponential_base^(attempts-1) + random(jitter_interval)``
268+
- the base for the exponential retry delay, the next delay is computed using Full Jitter formula ``retry_interval * exponential_base^(attempts-1) * random()``
269269
- ``5``
270270

271271

influxdb_client/client/write/retry.py

+6-16
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,19 @@ class WritesRetry(Retry):
1515
"""
1616
Writes retry configuration.
1717
18-
:param int jitter_interval: random milliseconds when retrying writes
1918
:param int max_retry_delay: maximum delay when retrying write
2019
:param int exponential_base: base for the exponential retry delay, the next delay is computed as
21-
`backoff_factor * exponential_base^(attempts-1) + random(jitter_interval)`
20+
`backoff_factor * exponential_base^(attempts-1) * random()`
2221
"""
2322

24-
def __init__(self, jitter_interval=0, max_retry_delay=180, exponential_base=5, **kw):
23+
def __init__(self, max_retry_delay=180, exponential_base=5, **kw):
2524
"""Initialize defaults."""
2625
super().__init__(**kw)
27-
self.jitter_interval = jitter_interval
2826
self.max_retry_delay = max_retry_delay
2927
self.exponential_base = exponential_base
3028

3129
def new(self, **kw):
3230
"""Initialize defaults."""
33-
if 'jitter_interval' not in kw:
34-
kw['jitter_interval'] = self.jitter_interval
3531
if 'max_retry_delay' not in kw:
3632
kw['max_retry_delay'] = self.max_retry_delay
3733
if 'exponential_base' not in kw:
@@ -58,16 +54,10 @@ def get_backoff_time(self):
5854
if consecutive_errors_len < 0:
5955
return 0
6056

61-
backoff_value = self.backoff_factor * (self.exponential_base ** consecutive_errors_len) + self._jitter_delay()
57+
# Full Jitter strategy
58+
backoff_value = self.backoff_factor * (self.exponential_base ** consecutive_errors_len) * self._random()
6259
return min(self.max_retry_delay, backoff_value)
6360

64-
def get_retry_after(self, response):
65-
"""Get the value of Retry-After header and append random jitter delay."""
66-
retry_after = super().get_retry_after(response)
67-
if retry_after:
68-
retry_after += self._jitter_delay()
69-
return retry_after
70-
7161
def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None):
7262
"""Return a new Retry object with incremented retry counters."""
7363
new_retry = super().increment(method, url, response, error, _pool, _stacktrace)
@@ -87,5 +77,5 @@ def increment(self, method=None, url=None, response=None, error=None, _pool=None
8777

8878
return new_retry
8979

90-
def _jitter_delay(self):
91-
return self.jitter_interval * random()
80+
def _random(self):
81+
return random()

influxdb_client/client/write_api.py

-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def to_retry_strategy(self):
7272
return WritesRetry(
7373
total=self.max_retries,
7474
backoff_factor=self.retry_interval / 1_000,
75-
jitter_interval=self.jitter_interval / 1_000,
7675
max_retry_delay=self.max_retry_delay / 1_000,
7776
exponential_base=self.exponential_base,
7877
method_whitelist=["POST"])

tests/test_WritesRetry.py

+9-18
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,29 @@
66
from influxdb_client.client.write.retry import WritesRetry
77

88

9+
class NonRandomWritesRetry(WritesRetry):
10+
def _random(self):
11+
return 1
12+
913
class TestWritesRetry(unittest.TestCase):
1014
def test_copy(self):
11-
retry = WritesRetry(jitter_interval=123, exponential_base=3, max_retry_delay=145)
12-
self.assertEqual(retry.jitter_interval, 123)
15+
retry = WritesRetry(exponential_base=3, max_retry_delay=145)
1316
self.assertEqual(retry.max_retry_delay, 145)
1417
self.assertEqual(retry.exponential_base, 3)
1518
self.assertEqual(retry.total, 10)
1619

1720
retry = retry.increment()
18-
self.assertEqual(retry.jitter_interval, 123)
1921
self.assertEqual(retry.max_retry_delay, 145)
2022
self.assertEqual(retry.exponential_base, 3)
2123
self.assertEqual(retry.total, 9)
2224

2325
retry = retry.increment()
24-
self.assertEqual(retry.jitter_interval, 123)
2526
self.assertEqual(retry.max_retry_delay, 145)
2627
self.assertEqual(retry.exponential_base, 3)
2728
self.assertEqual(retry.total, 8)
2829

2930
def test_backoff(self):
30-
retry = WritesRetry(total=5, backoff_factor=1, max_retry_delay=550)
31+
retry = NonRandomWritesRetry(total=5, backoff_factor=1, max_retry_delay=550)
3132
self.assertEqual(retry.total, 5)
3233
self.assertEqual(retry.is_exhausted(), False)
3334
self.assertEqual(retry.get_backoff_time(), 0)
@@ -74,17 +75,16 @@ def test_backoff_max(self):
7475
self.assertEqual(retry.get_backoff_time(), 15)
7576

7677
def test_backoff_jitter(self):
77-
retry = WritesRetry(total=5, backoff_factor=4, jitter_interval=2).increment()
78+
retry = WritesRetry(total=5, backoff_factor=4).increment()
7879

7980
self.assertEqual(retry.total, 4)
8081
self.assertEqual(retry.is_exhausted(), False)
8182

8283
backoff_time = retry.get_backoff_time()
83-
self.assertGreater(backoff_time, 4)
84-
self.assertLessEqual(backoff_time, 6)
84+
self.assertLessEqual(backoff_time, 4)
8585

8686
def test_backoff_exponential_base(self):
87-
retry = WritesRetry(total=5, backoff_factor=2, exponential_base=2)
87+
retry = NonRandomWritesRetry(total=5, backoff_factor=2, exponential_base=2)
8888

8989
retry = retry.increment()
9090
self.assertEqual(retry.get_backoff_time(), 2)
@@ -105,15 +105,6 @@ def test_get_retry_after(self):
105105
retry = WritesRetry()
106106
self.assertEqual(retry.get_retry_after(response), 5)
107107

108-
def test_get_retry_after_jitter(self):
109-
response = HTTPResponse()
110-
response.headers.add('Retry-After', '5')
111-
112-
retry = WritesRetry(jitter_interval=2)
113-
retry_after = retry.get_retry_after(response)
114-
self.assertGreater(retry_after, 5)
115-
self.assertLessEqual(retry_after, 7)
116-
117108
def test_is_retry(self):
118109
retry = WritesRetry(method_whitelist=["POST"])
119110

0 commit comments

Comments
 (0)