Skip to content

fix(user-history): create the profile sync index before the hinted query - #4022

Open
polylane[bot] wants to merge 1 commit into
masterfrom
polylane/autofix/vz2svza9tpvx
Open

polylane[bot] wants to merge 1 commit into
masterfrom
polylane/autofix/vz2svza9tpvx

Conversation

@polylane

@polylane polylane Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Profile sync sends its history query to MongoDB with a hint that names the user history (uid, server_time_stamp) index. Nothing guarantees that index exists, so on a database without it Mongo rejects the query and every syncing client gets HTTP 500. After this change the sync creates the index before it sends the hinted query, so the hint is always valid. The index is only declared in ensure_indices(), whose sole caller is a one-off migration script, so a database restored without secondary indexes has none. I reproduced the OperationFailure on a real mongod and confirmed the hinted query passes once the index exists; the hinted query keeps its performance intent.

Production impact at a3b356b: Profile sync creates its index idempotently; no production behavior changes

Fix walkthrough

Watch the fix walkthrough

Watch the fix explained (1:25). Recorded for commit a3b356bcd13992db9a38c7ed51f01ac3b6b8ee7c.

Full analysis
flowchart TD
  A["POST /api/profile/sync"] --> B["reader/views.py profile-sync view"]
  B --> C["UserHistory.get_sync_items"]
  C --> D["UserHistorySet hint=uid_1_server_time_stamp_1"]
  D -->|"index absent"| E["Mongo OperationFailure: hint does not correspond to an existing index"]
  E --> F["HTTP 500 to syncing clients"]
  D -->|"index present"| G["recent and flagged items returned"]
  C -.->|"fix: create_index before hinting"| H["user_history uid_1_server_time_stamp_1"]
  H --> D
  I["ensure_indices() - dead code, never run"] -.->|"does not create"| H
Loading
Detection evidence

Affected: int_1d0fec24c001pa40590o8ub5

Production impact

The change only calls create_index for the user_history (uid, server_time_stamp) index before the hinted query. Production already has this index, so the call is a metadata no-op there; Datadog shows no missing-index error in 14 days, and create_index on an existing index does not rebuild it. The added index only appears in environments that lacked it (CI sandbox, dev restores), where it was previously a 500.

user_history_test outcomes by revision (real mongod, index absent)

Checked and not affected (2)
  • //container.googleapis.com/projects/production-deployment/locations/us-east1-b/clusters/cluster-1/k8s/default/Service/mongo
  • Sefaria-Project

Observability

  • pymongo.errors.OperationFailure: hint provided does not correspond to an existing index from the sync path is the signal to watch; it should stop.
  • No new telemetry is added; the change relies on the existing 500/exception visibility for the sync endpoint.

Polylane assessed this pull request at a3b356b.

Causal chain
  • Signal (log template): pymongo.errors.OperationFailure: hint provided does not correspond to an existing index
  • Surfacing site: Sefaria/Sefaria-Project (sefaria-web) at reader/views.py profile-sync endpoint, POST /api/profile/sync
  • Mechanism: The profile-sync view calls UserHistory.get_sync_items, which runs its user_history query with hint=uid_1_server_time_stamp_1; the connected database has no such index, so Mongo rejects the query and the view returns 500.
  • Producer: UserHistory.get_sync_items, at Sefaria/Sefaria-Project:sefaria/model/user_profile.py#get_sync_items
  • Trigger: A database whose user_history indices were restored without secondary indexes: the CI sandbox and dev database restores (wiki record 2026-10-26).
  • What happens to the failed unit today: The hinted query is a Mongo find with no retry or fallback, so a rejected hint propagates as an unhandled exception and a 500.
  • Last observed: 2026-11-12T08:31:07Z
  • Blast radius: unknown other resource(s), unknown other tenant(s); data at risk: none named
  • Producer evidence:
    • get_sync_items builds both user_history queries with hint=by_time_index = uid_1_server_time_stamp_1 and never checks that the index exists.
    • The index is only declared in ensure_indices(), whose sole caller is the one-off scripts/convert_notifications_to_stories.py, so it is not created at startup, in CI, or on a Mongo restore.
    • Reproduced on a real mongod 7.0.14: the hinted query raises OperationFailure without the index and succeeds once create_index creates it; through sefaria/model/tests/user_history_test.py, 5 failed before the fix and 5 passed after.
2 files changed (+114/-1)
  • sefaria/model/tests/user_history_sync_index_test.py: added, +76/-0
  • sefaria/model/user_profile.py: modified, +38/-1

View autofix


Generated by Polylane. You can ask follow-ups by mentioning @polylane in a comment. If you close this pull request without merging, add a comment that says why. Polylane reads it to make its next fixes better.

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
@polylane polylane Bot added polylane severity:medium Polylane autofix severity: medium labels Nov 12, 2026
@polylane
polylane Bot requested a review from YishaiGlasner November 12, 2026 20:09

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

polylane severity:medium Polylane autofix severity: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant