Files
DongHyeonkaandClaude Opus 5 d57d2f62a0 feat(mongodb): implement the MongoDB document persistence platform
Implements the mongodb-superpowers-package design: Stable Tasks 1-50 and
Advanced Tasks 1-15.

The design assumes 19 Stable + 12 Advanced Gradle projects under
modules/mongodb*. This repository's fail-closed registry declares exactly 19
leaf identities, so those modules become package boundaries inside the
registered leaf :adapter:outbound:persistence-mongo, with the design's module
dependency table enforced by ten ArchUnit rules. The mapping and every
deviation are recorded in docs/mongodb/repository-adaptation.md.

Contract highlights, all enforced by tests rather than convention:

- Transaction body retry and commit retry are separate loops. A new session per
  body attempt; commit-only retry on an unknown commit. The body is never
  replayed after a commit ambiguity, so a failover cannot become a duplicate.
- MongoExecutionOutcome keeps both ambiguous outcomes distinct from success and
  failure, and MongoFailureContext records only the design-permitted fields.
- Failure classification reads server error labels before numeric codes.
- BSON representations come from a pinned manifest, never a library default,
  and a golden type-signature gate fails on any drift.
- Index and validator changes go through the manifest and the admin plane;
  metadata ownership gates every drop.
- Every Advanced capability refuses construction unless its flag is enabled.

Verified against real servers, not only unit tests. Running the lanes for the
first time exposed four defects that a green `check` had hidden:

- Four release lanes passed while executing zero tests; the gate now counts
  executed tests per lane and fails on zero.
- The "single replica set" fixture was a standalone, because Testcontainers 2.x
  needs withReplicaSet(); its test only asserted a connection string.
- The three-node fixture was three independent clusters, so no election could
  occur, and awaitNewPrimary() compared against the post-stop primary.
- The migration lease checked modifiedCount, so a same-millisecond refresh read
  as a lost lease.

scripts/verify-mongodb-platform.sh now reports:
  9 lanes, 0 skipped, 0 failed, every evidence category produced.

scripts/verify-mongodb-advanced.sh reports NOT PROMOTABLE: actual-topology
evidence (real sharded cluster, real KMS, real target deployment) is
unobtainable here, so it is named rather than assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:41:00 +09:00

4.1 KiB

title, category, severity, owner, last_updated, status
title category severity owner last_updated status
Runbook — MongoDB unknown transaction commit result mongodb P1 oncall 2026-08-13 active

Runbook: unknown transaction commit result

Design §16, decision D-10, scenario UNKNOWN_TRANSACTION_COMMIT_RESULT.

MongoExecutionOutcome.TRANSACTION_COMMIT_UNKNOWN means the commit may have applied. It is not a failure and must never be reported to a caller as one. The single worst response is to re-run the transaction body: if the commit did apply, the body applies a second time.

Symptoms

  • MongoTransactionCommitUnknownException in logs.
  • Metric failureCategory=TRANSACTION_COMMIT_UNKNOWN.
  • Usually accompanies a primary election — see failover.md.
  • Downstream reports of duplicated effects (double charge, double increment) are the symptom of this being handled wrongly, not of the condition itself.

Diagnosis

  1. Confirm the platform did the right thing automatically. MongoTransactionRetryCoordinator retries the commit only, on the same session, within MongoRetryBudget. A commit retry against an already-committed transaction is a no-op by design. Most occurrences resolve here and never reach a human.

  2. If the budget was exhausted, determine the actual state. The commit either applied or it did not; you must find out which, not guess.

    • If the transaction body wrote a deterministic marker (an idempotency key, a business id, a revision), read it back. That is exactly what MongoCommitReconciler does, and it is the reason the design requires transactions to write one.
    • If there is no marker: reconstruct from a downstream artefact — an outbox row, an audit record, an external side effect. If nothing exists to compare against, the transaction was not designed to be reconcilable and that is the finding to record.
  3. Check whether the body was replayed. Grep for a second execution with the same operation name and correlation id. If the body ran twice, the effects need reversing, and the code path that replayed it is a defect: an ambiguous commit is COMMIT_ONLY scope (MongoRetryScope.COMMIT_ONLY), never BODY.

Action

Commit applied. Nothing to do. Record the reconciliation.

Commit did not apply. Re-run the whole operation from the top — a new session, a new body attempt. This is safe precisely because you established the previous attempt left no trace.

Cannot determine. Do not retry. Escalate. A blind retry here is a coin flip between "no effect" and "duplicate effect", and duplicates in a financial or notification path are worse than a delay. Freeze the affected entity if the domain supports it, and hand off with: operation name, correlation id, document id, the time window, and what you checked.

Recurring. More than one an hour means the commit path is racing something structural — a maxCommitTime shorter than the observed election duration, an oversized transaction, or an undersized retry budget. Fix the budget or the transaction shape; do not raise the retry count and call it resolved.

Prevention

  • Every transaction body writes a deterministic marker that identifies its own commit.
  • maxCommitTime exceeds the observed p99 election duration.
  • Callers surface the ambiguity to their own callers rather than mapping it to a generic 500 — an ambiguous outcome reported as a failure invites the caller to retry, which is the one thing that must not happen.
  • Prefer a single-document atomic operation (D-09). A transaction that exists only to wrap one document write has invented this failure mode for nothing.

Escalation

  • Always P1 when the state cannot be determined and the operation has an external effect.
  • Page the service owner immediately; the database owner only if elections are the trigger.

Verification

cd src
./gradlew :adapter:outbound:persistence-mongo:mongoFailoverTest --console=plain

MongoFailoverScenario.UNKNOWN_TRANSACTION_COMMIT_RESULT runs this path against a real three-node set, and the coordinator test asserts the body is never replayed after a commit ambiguity.