Batch catalog reindex into sequential chords to bound Redis broker pressure - #3639
Batch catalog reindex into sequential chords to bound Redis broker pressure#3639feoh wants to merge 4 commits into
Conversation
start_recreate_index and start_update_index previously dispatched the entire catalog as one celery chord, so a reindex flooded the shared broker/cache Redis with chord result-collection traffic. Split the fan-out into sequential chords of at most OPENSEARCH_REINDEX_BATCH_SIZE subtasks, storing batch state and interim errors in the durable database cache, while preserving error aggregation and the single alias swap in the finish tasks.
OpenAPI ChangesNo changes detected Unexpected changes? Ensure your branch is up-to-date with |
There was a problem hiding this comment.
Pull request overview
This PR updates the OpenSearch reindex Celery canvas to avoid dispatching one catalog-wide chord (which can overwhelm the shared Redis broker/result backend) by splitting the work into sequential chord batches and persisting batch state in the durable (DB) cache.
Changes:
- Introduces
replace_with_batched_reindex+continue_reindex_batchesto run reindex subtasks as sequential chords of bounded size, accumulating errors across batches. - Adds
OPENSEARCH_REINDEX_BATCH_SIZE(default 100) to control max subtasks per chord batch. - Updates test utilities and task tests to account for direct
celery.chordusage and to validate batching behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
main/settings.py |
Adds OPENSEARCH_REINDEX_BATCH_SIZE setting to bound chord batch size. |
learning_resources_search/tasks.py |
Implements sequential chord batching with durable-cache persisted state and updates reindex entrypoints to use it. |
learning_resources_search/tasks_test.py |
Adds/updates tests to validate batching, sequential dispatch, error accumulation, and no-op behavior. |
fixtures/common.py |
Extends mocked_celery fixture to patch celery.chord for the new canvas pattern. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
for more information, see https://pre-commit.ci
|
@copilot Please fix pre-commit errors. |
…orrectly The else-path of continue_reindex_batches (error accumulation, finish_signature validation, cache cleanup, and final replace) was accidentally at module level instead of inside the function body. pre-commit.ci had inserted blank lines but left the code un-indented. Move the block inside the function with proper 4-space indentation."
Fixed in commit |
|
@rhysyngsun points out that this is too risky to be desirable before the weekend. I'll leave it for now. |
What are the relevant tickets?
Closes #3623
Description (What does it do?)
start_recreate_indexandstart_update_indexpreviously materialized a subtask for every ~100-item chunk of the entire catalog and dispatched all of them as one giant celery chord. With the Redis result backend, every subtask RPUSHes its result into a single per-group list, so a full reindex produced a massive burst of Redis list/set commands and result keys on the sharedmitlearn-redisbroker/cache instance — the failure mode behind the 2026-07-15 memory saturation (#3624) and the ongoing paging.This change bounds the in-flight footprint:
replace_with_batched_reindexsplits the index subtask list into batches of at mostOPENSEARCH_REINDEX_BATCH_SIZE(new setting, default 100) and dispatches only the first batch as a chord.continue_reindex_batches(each batch chord's callback) accumulates that batch's error strings, then dispatches the next batch — so only one batch's chord bookkeeping lives on Redis at a time — and finally invokes the original finish signature with all accumulated results.finish_recreate_indexstill receives the merged error strings and performs the alias swap exactly once after all batches complete (merge_stringsaggregation unchanged), andfinish_update_indexstill clears the views cache. Callers waiting on the original task still wait for the full chain viaTask.replace.If there are zero index subtasks, the task skips straight to the finish signature (previously an empty chord was dispatched).
How can this be tested?
pytest learning_resources_search/tasks_test.py— includes new tests covering batch splitting/storage (test_replace_with_batched_reindex), the no-op path, sequential dispatch + error accumulation (test_continue_reindex_batches_*), cache cleanup on completion, and loud failure when batch state is missing/expired.learning_resources_search/tasks_test.py,learning_resources/tasks_test.py,vector_search/tasks_test.py,learning_resources_search/management— 163 passed../manage.py recreate_index --all(orupdate_index) against a populated environment and observe on the Redis side that chord result lists stay bounded (≤OPENSEARCH_REINDEX_BATCH_SIZEentries) instead of growing to catalog size, while the final alias swap still occurs once at the end.Additional Context
CELERY_RESULT_EXPIRESbound). Move Celery result backend off the shared mitlearn broker/cache Redis #3622 (separate result backend) remains available as defense in depth.OPENSEARCH_REINDEX_BATCH_SIZEis env-configurable; 100 subtasks/batch ≈ 10k documents in flight per batch at the default chunk sizes.mocked_celeryfixture now also patchescelery.chordsince the reindex canvas uses chords directly instead ofchain(group, ...).