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

3.2 KiB

Advanced — Time Series

Capability: MongoCapability.TIME_SERIES Property: ca-skeleton.persistence-mongo.advanced.time-series.enabled Status: Advanced.

Requirements

Topology Replica set or sharded cluster.
Server MongoDB 7.0 or 8.0.
Privilege Standard application role; collection creation goes through the admin plane.

Descriptor

MongoTimeSeriesDescriptor declares:

  • timeField — required, a BSON date. This is the bucketing axis.
  • metaField — optional but nearly always wanted: the series identity (device id, tenant, sensor). Documents sharing a metaField value bucket together, which is where the compression comes from.
  • granularityMongoTimeSeriesGranularity:
Granularity Bucket span Use for
SECONDS 1 hour Sub-second to per-second ingest.
MINUTES 24 hours Per-minute metrics.
HOURS 30 days Hourly rollups.

Granularity that is too fine produces many small buckets and loses the compression; too coarse produces oversized buckets that must be read whole to answer a narrow query.

What a time series collection is not

MongoTimeSeriesCapabilityValidator refuses the operations the collection type does not support, at declaration time rather than at first use:

  • No arbitrary updates. Time series data is append-mostly. Delete and limited update support exists on recent servers but is not part of this platform's contract.
  • No unique index on the measurement. There is no _id to be unique on in the usual sense.
  • No CSFLE. Refused — see encryption.md.
  • No change stream on the raw buckets as a business event source. The bucket documents are a storage representation, not your measurements.

Converting an existing regular collection to a time series collection is a copy, not an alter. Plan it as a migration with a dual-write window.

TTL

Time series collections use expireAfterSeconds on the collection rather than a TTL index on a field. The TTL rules still apply: expiry is physical cleanup on a bucket boundary, so a measurement can outlive its expiry by up to a bucket span plus the monitor interval. Do not treat absence as a deadline.

Operations

MongoTimeSeriesOperations is the port for insert and windowed read. Reads are bounded by the same MongoOperationBudget as everything else: an unbounded time-range query on a time series collection is the fastest way to read a year of data into heap.

Failure recovery

Symptom Cause Action
Writes rejected with an unsupported-operation error An update or unique-index expectation The collection type does not support it; change the access pattern.
Poor compression / large storage Missing metaField, or granularity too fine Both require a rebuild; measure on a copy before committing.
Slow range queries Granularity too coarse for the query window Same: rebuild with the granularity matched to the dominant query.

Promotion evidence

Actual-topology evidence on the target deployment, a migration path from the existing collection, failure cases (unsupported update refused, CSFLE combination refused), and this document as the runbook.