Skip to content

Commit de7044c

Browse files
feat: dynamic batch size recovery for storage operations (#181)
* feat(providers): add dynamic batch size recovery for storage operations After a timeout reduces batch size, subsequent successful requests with smaller payloads automatically double the batch size back toward the original. This prevents one large-storage account from permanently slowing all subsequent operations. Changes: - get_account_storage_with_fallback: tracks last_successful_avg_bytes and failure_avg_bytes_ref, doubles batch on smaller payload (measured as avg bytes/key) - set_state_with_batching: tracks failure_avg_bytes_ref from failed batch, doubles batch on smaller payload (measured as avg bytes/pair) - Both operations reset batch size per account (per-operation isolation, no cross-account state) - Increase is capped at original configured batch size, matching the symmetric ÷2 reduction rule - INFO log on each batch size increase with old/new values and payload comparison reason * test(providers): add tests for dynamic batch size increase and per-account reset Add 14 new tests across 3 test classes: TestGetAccountStorageWithFallbackBatchIncrease (6 tests): - Batch increases on smaller payload (avg bytes/key strictly less) - No increase on same or larger payload - No increase when first request times out (no prior success reference) - Recovery tracking resets on re-failure - Increase capped at original batch size - INFO log emitted with old/new sizes and payload comparison TestSetStateWithBatchingBatchIncrease (5 tests): - Batch increases on smaller avg bytes/pair - No increase when avg bytes/pair same or larger - Recovery reset on re-failure - Increase capped at original - INFO log emitted TestBatchSizeResetBetweenAccounts (3 tests): - Fetch batch size resets between account calls - Push batch size resets between account calls - Fetch fallback to standard endpoint doesn't affect next account's paginated batch * docs(changelog): record dynamic batch size recovery and batch reset tests * Bump version: 3.1.1-dev1 → 3.1.1-dev2 --------- Co-authored-by: github-actions <41898282+github-actions@users.noreply.github.com>
1 parent 5bc147c commit de7044c

File tree

5 files changed

+729
-2
lines changed

5 files changed

+729
-2
lines changed

docs/source/dev_documentation/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### Added
66

77
- Explicit test for variadic counted values
8+
- Dynamic batch size recovery: after a timeout reduces the batch size, subsequent successful requests with smaller payloads automatically double the batch size back toward the original (for both storage fetch and push operations)
9+
- Tests verifying batch size resets between accounts
810

911
### Fixed
1012

mxops/common/providers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def set_state_with_batching(
8686
# for a given input dict
8787
pairs_list = list(pairs.items())
8888
current_batch_size = batch_size
89+
original_batch_size = batch_size
90+
failure_avg_bytes_ref: float | None = None
8991
pairs_sent = 0
9092
batches_sent = 0
9193
is_first_request = True
@@ -119,6 +121,11 @@ def set_state_with_batching(
119121

120122
except Exception as e:
121123
if _is_retryable_error(e):
124+
if batch_pairs:
125+
failed_total = sum(
126+
len(k) + len(v) for k, v in batch_pairs.items()
127+
)
128+
failure_avg_bytes_ref = failed_total / len(batch_pairs)
122129
new_batch_size = current_batch_size // 2
123130
logger.info(
124131
f"Set state for {address} failed with batch size "
@@ -151,6 +158,25 @@ def set_state_with_batching(
151158
f"Consider retrying or increasing min_batch_size."
152159
)
153160

161+
# Dynamic batch increase after successful push
162+
if (
163+
batch_pairs
164+
and failure_avg_bytes_ref is not None
165+
and current_batch_size < original_batch_size
166+
):
167+
total_bytes = sum(len(k) + len(v) for k, v in batch_pairs.items())
168+
current_avg = total_bytes / len(batch_pairs)
169+
if current_avg < failure_avg_bytes_ref:
170+
old_batch_size = current_batch_size
171+
current_batch_size = min(current_batch_size * 2, original_batch_size)
172+
logger.info(
173+
f"Set state for {address}: avg bytes/pair "
174+
f"({current_avg:.0f}) < failure reference "
175+
f"({failure_avg_bytes_ref:.0f}), increasing "
176+
f"batch size from {old_batch_size} to "
177+
f"{current_batch_size}"
178+
)
179+
154180
# Delay between successful batches
155181
if pairs_sent < len(pairs_list) and request_delay > 0:
156182
sleep(request_delay)
@@ -231,6 +257,9 @@ def get_account_storage_with_fallback(
231257
iterator_state: list[list[int]] = []
232258
iteration_succeeded = False
233259
current_batch_size = num_keys
260+
original_batch_size = num_keys
261+
last_successful_avg_bytes: float | None = None
262+
failure_avg_bytes_ref: float | None = None
234263
bech32_address = address.to_bech32()
235264

236265
try:
@@ -276,6 +305,7 @@ def get_account_storage_with_fallback(
276305

277306
except Exception as e:
278307
if _is_retryable_error(e):
308+
failure_avg_bytes_ref = last_successful_avg_bytes
279309
new_batch_size = current_batch_size // 2
280310
logger.info(
281311
f"Storage iteration for {bech32_address} failed with "
@@ -296,6 +326,28 @@ def get_account_storage_with_fallback(
296326
)
297327
break
298328

329+
# Dynamic batch increase after successful fetch
330+
if pairs:
331+
total_bytes = sum(len(k) + len(v) for k, v in pairs.items())
332+
current_avg = total_bytes / len(pairs)
333+
if (
334+
failure_avg_bytes_ref is not None
335+
and current_avg < failure_avg_bytes_ref
336+
and current_batch_size < original_batch_size
337+
):
338+
old_batch_size = current_batch_size
339+
current_batch_size = min(
340+
current_batch_size * 2, original_batch_size
341+
)
342+
logger.info(
343+
f"Storage iteration for {bech32_address}: "
344+
f"avg bytes/key ({current_avg:.0f}) < failure "
345+
f"reference ({failure_avg_bytes_ref:.0f}), "
346+
f"increasing batch size from {old_batch_size} "
347+
f"to {current_batch_size}"
348+
)
349+
last_successful_avg_bytes = current_avg
350+
299351
if iteration_succeeded:
300352
break
301353

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55

66
[project]
77
name = "mxops"
8-
version = "3.1.1-dev1"
8+
version = "3.1.1-dev2"
99
authors = [
1010
{name="Etienne Wallet"},
1111
]

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.1.1-dev1
2+
current_version = 3.1.1-dev2
33
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<release>[^-0-9]+)(?P<build>\d+))?
44
serialize =
55
{major}.{minor}.{patch}-{release}{build}

0 commit comments

Comments
 (0)