Log missing/invalid edx_module_id in content-file API requests#3539
Conversation
Sentry already groups these by the stable message template and applies its own rate limiting, so the Redis-backed per-id throttle was redundant. Removing it drops the caches["redis"] coupling (and a 500 risk on the contentfiles endpoint), the identifier hashing, the throttle setting, and the real-redis test fixture. log_missing_content_file is now a plain log.error; every occurrence is logged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o results Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OpenAPI Changes3 changes: 0 error, 3 warning, 0 info Unexpected changes? Ensure your branch is up-to-date with |
There was a problem hiding this comment.
Pull request overview
Adds observability for degraded consumers (e.g., AskTIM) by logging (and emitting Sentry events via LoggingIntegration) when API requests reference edx_module_id values that are missing in the DB or missing from the Qdrant index, without breaking search behavior.
Changes:
- Introduces shared logging utilities to record missing
ContentFilebacking for requestededx_module_idvalues (not_in_db,not_in_index). - Instruments the vector content-files search endpoint to probe DB/Qdrant when
edx_module_idis provided and the search returns no hits (probe failures are swallowed to preserve endpoint availability). - Instruments the REST
ContentFileFilter.edx_module_idfilter to log any requested IDs that have no backingContentFilerow, and adds automated test coverage for both REST and vector behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| vector_search/views.py | Calls the missing-content probe when edx_module_id is provided and the vector search returns zero hits. |
| vector_search/views_test.py | Adds endpoint-level tests for logging, probe failure isolation, and skipping the probe when hits exist. |
| vector_search/utils.py | Implements the async probe that checks DB presence and (when applicable) Qdrant index presence via count(exact=True). |
| vector_search/utils_test.py | Adds unit tests covering not_in_db, not_in_index, silent success, and “unknown collection” skip behavior. |
| learning_resources/utils.py | Adds log_missing_content_file and present_edx_module_ids helpers used by REST and vector instrumentation. |
| learning_resources/utils_test.py | Adds a unit test asserting the log template/arguments for log_missing_content_file. |
| learning_resources/filters.py | Adds LoggedEdxModuleIdFilter and wires it into ContentFileFilter.edx_module_id. |
| learning_resources/filters_test.py | Adds REST filter tests to ensure missing IDs are logged and present IDs are not. |
There was a problem hiding this comment.
approving since it does what it needs to.
I did leave a small comment about performance when using exact=True - however since the edx_module_id is an indexed payload i think this will be minimal.
I also tried manually running this count with exact=True on prod and it was pretty fast
What are the relevant tickets?
Closes mitodl/hq#11873
Description (What does it do?)
Logs an error whenever an API request references an
edx_module_idthat has no backing content, so we get asignal when AskTIM and other consumers are degraded by missing content.
For every requested id, existence is probed directly against the source of
truth, never inferred from empty search results. Two reasons are
distinguished:
not_in_db— noContentFilerow (ETL/scrape gap)not_in_index— row exists but isn't embedded in Qdrant (embedding gap)How can this be tested?
Pick a real
edx_module_idfrom your local data, and also deindex aLog in as an admin user and try the following URLs. The edx_module_id values will need to be url-encoded.
REST endpoint — existing id is silent, bogus id logs
not_in_db:http://open.odl.local:8065/api/v1/contentfiles/?edx_module_id=<REAL_ID>non-existent -> empty results AND an error log
http://open.odl.local:8065/api/v1/contentfiles/?edx_module_id=does-not-existVector endpoint — only probes when there are no hits:
non-existent id, no hits -> logs not_in_db
http://open.odl.local:8065/api/v0/vector_content_files_search/?edx_module_id=does-not-existreal id that is in the DB but absent from Qdrant -> logs not_in_index
http://open.odl.local:8065/api/v0/vector_content_files_search/?edx_module_id=<REAL_UNINDEXED_ID>Watch the web container logs for lines like: