Skip to content

Namespace reset races concurrent upserts: dropIndex vs key deletion leaves a bounded unindexed window #24

Description

@Coriou

Deep audit 2026-09-06, finding F15 (concurrency, moderate). Survived refutation; the "permanent stale knownIndexes until restart" variant from the finder was REFUTED (single multiplexed Redis connection cannot invert JS-order vs Redis-order; see refutation in docs/audits/2026-09-06-deep-audit.md).

Mechanism. Single-namespace reset (src/routes/reset.ts:82-83) runs FT.DROPINDEX then a multi-round-trip SCAN+DEL loop. A concurrent upsert on a cached-known index skips ensureIndex (early return on knownIndexes) and its EVAL can land after the scan passed its key: the hash survives reset while the index is gone → invisible to /query (missing-index → []) but readable via /fetch//range. The next upsert recreates the index and RediSearch backfills, so the window is bounded (upsert-to-next-upsert), not permanent. Same pattern in reset-all (reset.ts:46) and delete-namespace/rename (namespaces.ts).

Candidate fix (needs a product decision). Stop calling FT.DROPINDEX on namespace reset — delete keys only. Kills both interleavings (index never dropped → never stale; late writes stay indexed). Trade-off: namespace dimension becomes sticky across reset (persisted index keeps its DIM; post-reset upsert with a different dimension 400s via the compatibility check). Upstash semantics arguably match the sticky behavior (index dimension is fixed at creation there). Only delete-namespace should keep the drop.

Disposition. Not fixed in the audit pass because it is a visible behavior change (sticky dimension) that needs a deliberate decision, not a drive-by.

Triage guidance: docs/RUNBOOK.md. Audit procedure: docs/workflows/deep-audit.md.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    deep-auditFindings from scheduled deep audits

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions