Problem
The library commit endpoint /api/libraries/v2/<lib_key>/commit/ returns a 500 error when publishing 3+ blocks on instances with large Meilisearch indexes (500K+ documents). The publish itself succeeds (data is committed in Learning Core), but the post-publish search index update exceeds the hardcoded 15-second timeout.
This leaves the library MFE showing blocks in an intermediate state — marked as unpublished with no publish option visible — because the frontend relies on Meilisearch (/multi-search) for the component listing and the search index was never updated from the frontend's perspective.
Root Cause
wait_for_post_publish_events in tasks.py dispatches a Celery task and blocks with:
result.get(timeout=15, disable_sync_subtasks=False)
The Celery task fires LIBRARY_BLOCK_PUBLISHED for each block, which triggers library_block_published_handler -> upsert_library_block_index_doc.apply(). The .apply() call runs synchronously and includes _wait_for_meili_task() — a polling loop that blocks until Meilisearch confirms indexing.
On a 500K-document index, each document takes 5-7 seconds to index (inherent to Meilisearch's inverted index rebuild for large indexes). This is not resource-bound — we tested with 30Gi RAM / 6 CPU and observed the same 7s per document. Three blocks = 21 seconds, exceeding the 15-second timeout.
Proposed Short-Term Fix
Make the timeout configurable: Replace the hardcoded timeout=15 in dispatch_and_wait with getattr(settings, 'CONTENT_LIBRARIES_PUBLISH_TIMEOUT', 15) so operators can tune it for their index size without patching.
Batch document submissions: In send_events_after_publish (or send_change_events_for_modified_entities on master), collect all documents from the publish operation and submit them to Meilisearch in a single update_documents call instead of one document at a time. Meilisearch processes a batch of N documents as a single indexing operation — the rebuild time is roughly the same whether it's 1 or 10 documents. This would reduce a 3-block commit from 3x7s = 21s down to ~7s total.
Proposed Long-Term Fix
There are two architectural options to eliminate this problem at the root:
Option A: Decouple the library component listing from Meilisearch
Currently, the library authoring MFE retrieves the list of components directly from Meilisearch (/multi-search). This creates a hard dependency: after any write operation, the API must wait for Meilisearch to be consistent before responding, otherwise the frontend shows stale data.
The fix: use Meilisearch only for search functionality (full-text search, filtering, facets). For the library component listing (the default view showing all components), fetch directly from the Learning Core relational database via a dedicated REST endpoint.
With this approach:
- The commit endpoint writes to Learning Core (the source of truth) and returns immediately.
- The indexing events are triggered asynchronously in the background.
- The frontend fetches the updated component list directly from the database — which already has the committed state — and renders it immediately.
- Meilisearch catches up in the background; search results become consistent within seconds without blocking the user.
- This eliminates the coupling between publish latency and search indexing entirely, while preserving Meilisearch's role for full-text search where eventual consistency (a few seconds delay) is acceptable.
Option B: Fully async indexing with per-component loading state in the frontend
Instead of blocking the API response until all indexing is complete, make publish a fire-and-forget operation:
- The commit endpoint triggers the indexing events asynchronously (no result.get() wait) and returns immediately with a 200.
- The frontend (library authoring MFE) shows a per-component loading/publishing state — each block transitions individually from "publishing" to "published" as its indexing completes.
- The frontend polls or subscribes (via WebSocket/SSE) to the indexing status to know when each component is done.
This moves the responsibility of handling eventual consistency to the frontend, which is better positioned to give users real-time feedback on the progress of bulk operations. The backend no longer needs to block a synchronous HTTP request waiting for an external service (Meilisearch) to finish.
Environment
edx-platform release/ulmo.3 (also affects master)
Meilisearch v1.8 with ~500K documents in tutor_studio_content index
Tutor 21.x k8s deployment
Steps to Reproduce
- Have a Meilisearch instance with >200K documents indexed
- Create a content library with 3+ components
- Edit all components (create pending draft changes)
- Click "Publish All" (calls /api/libraries/v2/<lib_key>/commit/)
- Observe 500 error (on ulmo.3) or stale MFE state (on master where exception is caught)
- The content IS published (visible in the DB) but the search index is not updated within the timeout window, leaving the MFE showing stale state.
Evidence:

Problem
The library commit endpoint
/api/libraries/v2/<lib_key>/commit/returns a 500 error when publishing 3+ blocks on instances with large Meilisearch indexes (500K+ documents). The publish itself succeeds (data is committed in Learning Core), but the post-publish search index update exceeds the hardcoded 15-second timeout.This leaves the library MFE showing blocks in an intermediate state — marked as unpublished with no publish option visible — because the frontend relies on Meilisearch (/multi-search) for the component listing and the search index was never updated from the frontend's perspective.
Root Cause
wait_for_post_publish_eventsin tasks.py dispatches a Celery task and blocks with:The Celery task fires LIBRARY_BLOCK_PUBLISHED for each block, which triggers library_block_published_handler -> upsert_library_block_index_doc.apply(). The .apply() call runs synchronously and includes _wait_for_meili_task() — a polling loop that blocks until Meilisearch confirms indexing.
On a 500K-document index, each document takes 5-7 seconds to index (inherent to Meilisearch's inverted index rebuild for large indexes). This is not resource-bound — we tested with 30Gi RAM / 6 CPU and observed the same 7s per document. Three blocks = 21 seconds, exceeding the 15-second timeout.
Proposed Short-Term Fix
Make the timeout configurable: Replace the hardcoded timeout=15 in dispatch_and_wait with
getattr(settings, 'CONTENT_LIBRARIES_PUBLISH_TIMEOUT', 15)so operators can tune it for their index size without patching.Batch document submissions: In send_events_after_publish (or send_change_events_for_modified_entities on master), collect all documents from the publish operation and submit them to Meilisearch in a single update_documents call instead of one document at a time. Meilisearch processes a batch of N documents as a single indexing operation — the rebuild time is roughly the same whether it's 1 or 10 documents. This would reduce a 3-block commit from 3x7s = 21s down to ~7s total.
Proposed Long-Term Fix
There are two architectural options to eliminate this problem at the root:
Option A: Decouple the library component listing from Meilisearch
Currently, the library authoring MFE retrieves the list of components directly from Meilisearch (/multi-search). This creates a hard dependency: after any write operation, the API must wait for Meilisearch to be consistent before responding, otherwise the frontend shows stale data.
The fix: use Meilisearch only for search functionality (full-text search, filtering, facets). For the library component listing (the default view showing all components), fetch directly from the Learning Core relational database via a dedicated REST endpoint.
With this approach:
Option B: Fully async indexing with per-component loading state in the frontend
Instead of blocking the API response until all indexing is complete, make publish a fire-and-forget operation:
This moves the responsibility of handling eventual consistency to the frontend, which is better positioned to give users real-time feedback on the progress of bulk operations. The backend no longer needs to block a synchronous HTTP request waiting for an external service (Meilisearch) to finish.
Environment
edx-platform release/ulmo.3 (also affects master)
Meilisearch v1.8 with ~500K documents in tutor_studio_content index
Tutor 21.x k8s deployment
Steps to Reproduce
Evidence: