Repository navigation
feat: incrementally load large assessment units - #39298
Open
abhalsod-sonata wants to merge 4 commits into
Open
abhalsod-sonata wants to merge 4 commits into
abhalsod-sonata wants to merge 4 commits into
Conversation
abhalsod-sonata
requested review from
farhan,
irtazaakram and
salman2013
as code owners
October 23, 2026 13:11
Rendering every problem of a very large unit, such as a 90-question
exam built from randomized question banks, in one response can exceed
the upstream request timeout, so the unit never loads.
Behind the new course waffle flag courseware.incremental_assessment_load
(off by default), a unit with more than INCREMENTAL_LOAD_PROBLEM_THRESHOLD
problems renders its first INCREMENTAL_LOAD_EAGER_COUNT children inline
and leaves placeholders for the rest. The page then fills them in, in
sequential batches of at most XBLOCK_CHILDREN_BATCH_MAX, from the new
GET /api/courseware/xblock_children/{usage_key} endpoint, retrying
transient failures. Mobile app requests and units at or below the
threshold always render in full.
With the flag off, rendering is unchanged.
Ported from #445, originally written by Santhosh Kumar.
Children rendered by the xblock_children endpoint did not see the learner's saved state, so an answered problem rendered as a fresh attempt and resubmitting it failed with "The state of this problem has changed since you loaded this page". Resolve each requested child to a fresh, unbound descriptor and bind it once, after one FieldDataCache populated for the whole batch through the new FieldDataCache.add_block_descendents_batch, so a batch costs a single learner-state lookup. The page now also shows a per-child error with a Retry button for children the endpoint reports in its errors list. Ported from #465, originally written by Santhosh Kumar.
Problems a randomized bank (item_bank / library_content) selected rendered as unanswered when they fell past the incremental-load eager window. The bank's selected problem is loaded through the runtime the parent vertical bound with its own depth-0 FieldDataCache, so it never saw the learner state prefetched into the separate batch cache. Resubmitting such a problem then failed with "The state of this problem has changed since you loaded this page". Bind the parent and the batch to one FieldDataCache so every block loaded through the shared runtime sees the same state. Lazily inserted children were also set with innerHTML, which never runs inline scripts, so setup_debug() from staff_problem_info.html never ran and Staff Debug Info / Submission History did nothing past the eager window. Insert them with edx.HtmlUtils.setHtml, which runs them as an inline render does and marks the server-rendered fragment trusted the same way capa does. Ported from #529.
Use the standard setting annotations for INCREMENTAL_LOAD_PROBLEM_THRESHOLD, INCREMENTAL_LOAD_EAGER_COUNT and XBLOCK_CHILDREN_BATCH_MAX so they show up in the generated settings documentation.
abhalsod-sonata
force-pushed
the
abhalsod/incremental-assessment-load
branch
from
October 23, 2026 13:23
ea796f4 to
5386736
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Very large assessment units, for example a 90-question mock exam where every question comes from a randomized question bank, can take longer to render in a single response than the request timeout allows, so the unit never loads.
This PR adds incremental loading for those units, behind a new course waffle flag,
courseware.incremental_assessment_load(opt-in per course). With the flag on, a unit with more thanINCREMENTAL_LOAD_PROBLEM_THRESHOLD(20) problems:INCREMENTAL_LOAD_EAGER_COUNT(5) children inline and leaves placeholders for the rest.GET /api/courseware/xblock_children/{usage_key}?child_usage_keys=...endpoint, in sequential batches of at mostXBLOCK_CHILDREN_BATCH_MAX(10). The page shows "Loading question X of Y...", retries transient failures with backoff, halves a batch that times out, and offers a Retry button for any child that fails.Units at or below the threshold, and requests from the mobile apps, always render in full.
The PR is four commits:
feat: incrementally load large assessment units: the flag, settings, endpoint, vertical shell and page script.fix: hydrate learner state for incrementally loaded children: lazily loaded problems now show the learner's saved answer, score and attempts. Without this, an answered problem rendered as a fresh attempt and resubmitting it failed with "The state of this problem has changed since you loaded this page". OneFieldDataCacheis populated per batch (newFieldDataCache.add_block_descendents_batch), so a batch costs a single learner-state lookup.fix: hydrate item bank problems and staff debug in lazily loaded units: the same fix for problems inside randomized banks (item_bank / library_content), which are loaded through the shared runtime the parent bound. It also makes Staff Debug Info and Submission History work for lazily loaded children, by inserting them withedx.HtmlUtils.setHtmlso their inline setup scripts run, the same as an inline render.docs: annotate incremental assessment load settings.User roles impacted:
lms/djangoapps/courseware/toggles.py,lms/envs/common.py).xblock_children.Before vs after
Measured on a local devstack with a 90-question unit, each question an item bank holding one problem, warm cache. The absolute numbers depend on the machine; the comparison is what matters.
masterbehavior)The total time to show every question stays about the same. The difference is that no single request has to render the whole unit, and the learner can start right after the first response.
1. Before (flag off): the whole 90-question unit arrives in a single 2.9 MB response.

2. After (flag on): the page arrives with the first 5 questions; questions 6 to 90 are placeholders.

3. After: the rest load in batches while the status line reports progress. The batch responses were slowed down in the browser to capture this state.

4. After: Staff Debug Info on question 7, a lazily loaded problem inside an item bank, opens.

5. After: question 7 answered and the page reloaded: the saved answer, the score (1/1 point) and the attempts (1 of 1) are kept, and Submit stays disabled.

Supporting information
The original feature and the first fix were written by Santhosh Kumar; this PR ports them, with the follow-up fix, onto
master.Testing instructions
courseware.incremental_assessment_load, forced on, for that course.GET /api/courseware/xblock_children/<unit key>/?child_usage_keys=...requests with at most 10 keys each.Automated tests:
lms/djangoapps/courseware/tests/test_incremental_render.py: the threshold and flag decision, the shell split, the endpoint helpers.openedx/core/djangoapps/courseware_api/tests/test_views.py(XBlockChildrenApiTestViews):lms/djangoapps/courseware/tests/test_model_data.py:add_block_descendents_batchpopulates the cache once per batch.Deadline
None.
Other information
edx.HtmlUtils.setHtml(el, edx.HtmlUtils.HTML(html)), the same idiom capa'sdisplay.jsuses for server-rendered problem HTML.masterdoes today. It is independent of this feature and can be proposed separately.uv.lockdependencies:test_incremental_render.py,courseware_api/tests/test_views.py,test_model_data.py,xmodule/tests/test_vertical.py,lms/djangoapps/courseware/tests/test_block_render.pyandlms/djangoapps/courseware/tests/test_views.py(496 passed).ruff check .,make xsslint,make pii_check,make check_keywords,make lint-imports,make check-types, andpylinton the changed files.