|
|
@@ -89,24 +89,33 @@ public:
|
|
|
std::vector<std::string> fields);
|
|
|
std::vector<std::string> indexed_fields(std::string_view collection);
|
|
|
|
|
|
- // ⚠ v2.10.0 — UNREACHABLE BY DESIGN, pending write-path work. Nothing calls
|
|
|
- // set_unique_fields, so the enforcement below never runs, and no RPC or config
|
|
|
- // key exposes it. Kept because the mechanism is correct in isolation and is
|
|
|
- // the base for finishing the feature.
|
|
|
+ // v2.11.0 T11 — ACTIVE. Called from three places: DatabaseGrpcImpl::CreateIndex
|
|
|
+ // and ::DropIndex (per-call, gated on a duplicate-value refusal — see
|
|
|
+ // find_duplicate_values below), and DatabaseService::applyIndexDeclarations
|
|
|
+ // at boot (re-arming from CollectionCfg::uniqueFields, persisted in
|
|
|
+ // `_collection_meta`). Exposed on the wire via CreateIndexRequest.unique and
|
|
|
+ // reported by ListIndexes.
|
|
|
//
|
|
|
- // Why it cannot be turned on yet: the check throws from put(), which runs
|
|
|
- // inside applyDualWriteMirror - and that catches every exception, logs it,
|
|
|
- // bumps mirror drift and flips mirror_healthy_. So a rejection would be
|
|
|
- // swallowed (the row still lands in MemoryStore, unenforced) AND would send
|
|
|
- // every read in the process to MemoryStore, which is the v2.8.1 fault. Worse,
|
|
|
- // MemoryStore mutates BEFORE the mirror runs, so a clean rejection needs the
|
|
|
- // in-memory write rolled back.
|
|
|
+ // Why this took its own task to turn on: the check throws UniqueViolation
|
|
|
+ // from put(), which runs inside applyDualWriteMirror - and that used to catch
|
|
|
+ // every exception, log it, bump mirror drift and flip mirror_healthy_. A
|
|
|
+ // rejection would have been swallowed (the row still lands in MemoryStore,
|
|
|
+ // unenforced) AND would have sent every read in the process to MemoryStore,
|
|
|
+ // the v2.8.1 fault. Worse, MemoryStore mutates BEFORE the mirror runs, so a
|
|
|
+ // clean rejection needed the in-memory write rolled back.
|
|
|
//
|
|
|
- // Finishing it means either propagating UniqueViolation through the mirror
|
|
|
- // without touching health, plus rollback, or moving enforcement ahead of the
|
|
|
- // MemoryStore mutation under the same collection lock. Both touch the write
|
|
|
- // path that produced the v2.4.3, v2.4.4 and v2.8.0 incidents, so it wants its
|
|
|
- // own change. See docs/ROADMAP.md.
|
|
|
+ // The fix (T11): applyDualWriteMirror catches UniqueViolation separately and
|
|
|
+ // rethrows it untouched - health/drift stay exactly as they were - and every
|
|
|
+ // MemoryStore write path that can raise it (insert/update/upsert/
|
|
|
+ // updateIfVersion/patchDocument/setAdd/restoreToVersion) undoes its own
|
|
|
+ // mutation via mirrorDocOrUndo() before letting it propagate. The gRPC
|
|
|
+ // handlers map it to ALREADY_EXISTS. See dual_write_mirror.hpp and
|
|
|
+ // memory_store.cpp's mirrorDocOrUndo for the mechanics, and
|
|
|
+ // database_service.cpp's replicated-entry apply path for the one caller of
|
|
|
+ // applyDualWriteMirror that deliberately keeps the old swallow-and-flip
|
|
|
+ // behaviour (loadDocument there already mutated MemoryStore before the
|
|
|
+ // mirror runs, and there is no "reject the whole operation" available on a
|
|
|
+ // replication-apply path).
|
|
|
void set_unique_fields(std::string_view collection,
|
|
|
std::vector<std::string> fields);
|
|
|
std::vector<std::string> unique_fields(std::string_view collection);
|