Files
clean-architecture-backend-…/docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md
T

1304 lines
32 KiB
Markdown

# MongoDB 문서 영속성 플랫폼 설계서
- 문서 상태: 구현 기준 설계
- 기준일: 2026-08-11
- 대상 프로젝트: Java 21 / Spring Boot 4.1 기반 Backend Skeleton
- 모듈 루트: `modules/mongodb`
- 루트 패키지: `io.backend.skeleton.mongodb`
- 요구사항 원본: `MongoDB 문서 영속성 플랫폼 심층 리서치`
## 1. 설계 목적
이 설계의 목적은 Spring Data MongoDB를 다시 감싼 공통 CRUD 라이브러리를 만드는 것이 아니다. 도메인 모듈이 MongoDB의 Document, Repository, Query 의미와 Index 요구를 직접 소유하면서도, 다음 기술 결정은 모든 서비스가 같은 기준으로 재사용하도록 만드는 것이다.
```text
Document Modeling
→ BSON Mapping
→ Atomic Write
→ Consistency Profile
→ Session / Transaction
→ Retry / Ambiguous Outcome
→ Query / Aggregation Guardrail
→ Schema / Index Manifest
→ Pagination / Bulk
→ Change Stream
→ Security / Observability
→ Topology / Release Gate
```
플랫폼은 MongoDB를 관계형 저장소처럼 평탄화하지 않는다. 반대로 Native Driver와 `runCommand()`를 일반 애플리케이션에 무제한 노출하지도 않는다. MongoDB의 단일 문서 원자성, BSON 표현, Aggregation, Replica Set, Change Stream, Sharding, Encryption 의미론을 유지하면서 잘못된 사용을 사전에 차단한다.
## 2. 최상위 설계 결정
| 번호 | 결정 | 구현 결과 |
|---:|---|---|
| D-01 | 도메인이 `@Document`, Repository, Query, Index Requirement, Schema Version을 소유한다. | 플랫폼에는 범용 `CommonMongoRepository<T, ID>`를 만들지 않는다. |
| D-02 | Local 기본 토폴로지는 Single-node Replica Set이다. | Transaction, Retryable Write, Change Stream을 Local에서 동일하게 검증한다. |
| D-03 | Standalone은 smoke test만 지원한다. | 운영 Profile이나 Stable Release Gate로 인정하지 않는다. |
| D-04 | D1/D2 Runtime Client는 Stable API V1과 `apiStrict=true`를 기본으로 한다. | 일반 CRUD·Query·Transaction·Change Stream 경로의 호환성을 제한한다. |
| D-05 | D3 Capability Client와 D4 Admin Client를 분리한다. | Stable API 밖 기능과 관리 명령을 일반 Runtime에서 우회하지 못한다. |
| D-06 | UUID·Decimal·시간·type metadata 표현을 Manifest로 고정한다. | 배포나 라이브러리 변경으로 BSON 표현이 암묵적으로 바뀌지 않는다. |
| D-07 | 부분 변경은 Update Operator를 우선한다. | `save()` 기반 전체 Document 교체로 인한 Lost Update를 줄인다. |
| D-08 | 전체 Document 교체에는 optimistic revision을 요구한다. | `@Version` 또는 expected revision predicate를 사용한다. |
| D-09 | Transaction보다 단일 Document 원자 연산을 우선한다. | Multi-document invariant에만 Transaction을 사용한다. |
| D-10 | Transaction 본문 Retry와 Commit Retry를 분리한다. | `TransientTransactionError`는 전체 본문, `UnknownTransactionCommitResult`는 Commit만 재시도한다. |
| D-11 | Query·Aggregation·Index는 등록된 operation/manifest를 요구한다. | 자유형 JSON Query와 무제한 Pipeline을 차단한다. |
| D-12 | Change Stream은 at-least-once projector다. | physical change event를 업무 Integration Event로 직접 공개하지 않는다. |
| D-13 | TTL은 물리 cleanup이다. | 정확한 업무 Scheduler 또는 접근 차단의 유일한 근거로 사용하지 않는다. |
| D-14 | GridFS는 compatibility adapter다. | 신규 파일 Source of Truth는 기존 Fileserver/Object Storage를 사용한다. |
| D-15 | Sharding·Time Series·Encryption·Search·Vector·Multi-tenancy는 선택 모듈이다. | Stable Starter의 기본 dependency와 권한에 포함하지 않는다. |
## 3. 범위와 비범위
### 3.1 Stable 범위
```text
Mapping Manifest
Repository / MongoTemplate 통합
Imperative / Reactive 실행 경로
단일 Document 원자 Update
Optimistic Lock
Replica Set Transaction
Consistency Profile
Retry / Error Translation
Query Guardrail
Aggregation Guardrail
Schema / Index Manifest
Keyset Pagination
Bulk Partial Result
Change Stream
TTL Cleanup Contract
GeoJSON / 2dsphere
Security
Observability
Replica Set Testkit
```
### 3.2 Advanced 범위
```text
Sharding-aware Query
Time Series
CSFLE
Queryable Encryption Equality / Range
Change Stream → Messaging Bridge
Shared Collection Multi-tenancy
```
### 3.3 Experimental 범위
```text
MongoDB Search
Vector Search
Hybrid Search
Database-per-tenant
Collection-per-tenant
Reshard orchestration
Atlas/provider 고유 기능
```
### 3.4 명시적 비지원
```text
CommonMongoRepository<T, ID>
Runtime arbitrary runCommand
운영 auto-index creation
Standalone 운영 계약
TTL 기반 정확한 Scheduler
Change Stream 원본의 외부 업무 이벤트 공개
신규 GridFS 파일 플랫폼
Java FQCN을 장수 BSON schema로 강제
무제한 skip pagination
무제한 aggregation / regex / result
```
## 4. 지원 기준
| 구성 | Stable 기준 | 정책 |
|---|---|---|
| Java | 21 | 프로젝트 Runtime 기준 |
| Spring Boot | 4.1.x BOM | 개별 Driver 버전 override 금지 |
| Spring Data MongoDB | 5.1.x | Repository·Template 통합 |
| MongoDB Java Driver | Boot BOM 관리 | 직접 버전 고정 금지 |
| MongoDB Server | 8.0 최신 패치 | Primary Certification Lane |
| MongoDB 7.0 | 최신 7.0 패치 | Compatibility Lane |
| Stable API | V1 | D1/D2 strict 기본 |
| Local | Single-node Replica Set | 기본 개발 환경 |
| 운영 Stable Gate | 3-node Replica Set | Failover 검증 필수 |
| Sharding | 실제 Sharded Cluster | Advanced Gate |
| Search/Vector | Atlas Local + 실제 목표 배포 | 기능별 Gate |
| Encryption | 실제 KMS·Key Vault 환경 | 기능별 Gate |
MongoDB 8.0/7.0 Stable 계약에서는 `validationAction=errorAndLog`를 사용하지 않는다. Stable Validation Action은 `error``warn`이다.
## 5. 공개 계층
```text
D1 Standard Document Persistence
├─ Spring Data Repository
├─ Typed Query / Projection
├─ Mapping Manifest
├─ Atomic Update Primitive
└─ Optimistic Revision
D2 Advanced Document Operations
├─ MongoTemplate
├─ Transaction / Session
├─ Bulk
├─ Aggregation
├─ Keyset / Cursor
└─ Change Stream
D3 Explicit Mongo Capability
├─ Native BSON
├─ Time Series
├─ Search / Vector
├─ CSFLE / QE
└─ Sharding-aware Operations
D4 Admin Plane
├─ Collection
├─ Validator
├─ Index
├─ Migration
├─ Shard / Refine / Reshard
└─ Repair / Verification
```
D3도 raw client escape가 아니다. 모든 D3 호출은 다음 순서를 통과한다.
```text
Capability 등록 확인
→ Database Profile 확인
→ Collection allowlist
→ Operation Name 필수
→ Timeout / maxTimeMS
→ Consistency Profile
→ Result / Batch Limit
→ Trace
→ Log Redaction
→ Command Category 검증
→ D4 Command 차단
→ 실행
```
## 6. 모듈 구조
### 6.1 Stable 모듈
```text
modules/mongodb/
├── mongodb-core-api
├── mongodb-spring-data
├── mongodb-imperative
├── mongodb-reactive
├── mongodb-query
├── mongodb-aggregation
├── mongodb-transaction
├── mongodb-index-schema
├── mongodb-change-stream
├── mongodb-geospatial
├── mongodb-migration-core
├── mongodb-migration-flamingock
├── mongodb-observability
├── mongodb-security
├── mongodb-spring-boot-starter
├── mongodb-testkit-core
├── mongodb-testkit-replicaset
├── mongodb-testkit-failover
└── mongodb-testkit-migration
```
### 6.2 Advanced·Experimental 모듈
```text
modules/mongodb-advanced/
├── mongodb-sharding
├── mongodb-timeseries
├── mongodb-csfle
├── mongodb-queryable-encryption
├── mongodb-search
├── mongodb-vector-search
├── mongodb-tenancy-shared
├── mongodb-tenancy-database
├── mongodb-change-stream-messaging-bridge
├── mongodb-gridfs-compat
├── mongodb-testkit-sharded
└── mongodb-testkit-atlas
```
### 6.3 Dependency 규칙
```text
mongodb-core-api
→ Java 표준 라이브러리만
mongodb-spring-data
→ core-api
mongodb-imperative / mongodb-reactive
→ core-api
→ spring-data
mongodb-query
→ core-api
→ spring-data
mongodb-aggregation
→ core-api
→ query
mongodb-transaction
→ core-api
→ spring-data
mongodb-index-schema
→ core-api
→ spring-data
mongodb-change-stream
→ core-api
→ reactive
mongodb-geospatial
→ core-api
→ spring-data
mongodb-migration-flamingock
→ migration-core
→ index-schema
mongodb-spring-boot-starter
→ Stable 모듈만
Advanced 모듈
→ Starter에 자동 포함하지 않음
```
## 7. 핵심 타입
### 7.1 Operation·Profile
```java
public record MongoOperationName(String value) {
public MongoOperationName {
if (value == null || !value.matches("[a-z][a-z0-9.-]{2,95}")) {
throw new IllegalArgumentException("invalid MongoDB operation name");
}
}
}
public record DatabaseProfileName(String value) {}
public record CollectionProfileName(String value) {}
public record MongoOperationContext(
MongoOperationName operationName,
DatabaseProfileName databaseProfile,
CollectionProfileName collectionProfile,
MongoConsistencyProfile consistency,
Duration timeout) {
}
```
Operation name은 metric·trace·policy key로 사용하므로 사용자 ID나 동적 값이 들어갈 수 없다.
### 7.2 Consistency Profile
```java
public enum MongoConsistencyProfile {
PRIMARY_LOCAL,
PRIMARY_MAJORITY,
CAUSAL_MAJORITY,
STALE_READ_ALLOWED,
SNAPSHOT_TRANSACTION,
MONGO_SHORT_WRITE
}
```
| Profile | Read Preference | Read Concern | Write Concern | 용도 |
|---|---|---|---|---|
| PRIMARY_LOCAL | primary | local | acknowledged | 일반 저지연 작업 |
| PRIMARY_MAJORITY | primary | majority | majority | rollback 저항·내구성 |
| CAUSAL_MAJORITY | primary 기본 | majority | majority | read-your-writes |
| STALE_READ_ALLOWED | secondaryPreferred | local/majority | 없음 | 명시적 stale read |
| SNAPSHOT_TRANSACTION | primary | snapshot | majority | multi-document snapshot |
| MONGO_SHORT_WRITE | primary | profile 값 | profile 값 | 짧은 write transaction |
`STALE_READ_ALLOWED`는 이름으로 위험을 드러낸다. `readOnly=true`를 자동으로 secondary routing 조건으로 사용하지 않는다.
### 7.3 실행 결과
```java
public enum MongoExecutionOutcome {
NOT_SENT,
NO_WRITE_PERFORMED,
WRITE_CONFIRMED,
PARTIAL_BULK_WRITE,
WRITE_RESULT_UNKNOWN,
TRANSACTION_COMMIT_UNKNOWN
}
```
`WRITE_RESULT_UNKNOWN``TRANSACTION_COMMIT_UNKNOWN`은 명백한 실패가 아니다. 동일 업무 본문을 무조건 재실행하지 않고 version, unique key, idempotency record, transaction record를 조회해 복구한다.
### 7.4 Capability
```java
public enum MongoSupportLevel {
STABLE,
ADVANCED,
EXPERIMENTAL,
UNSUPPORTED
}
public record MongoCapabilitySupport(
String capability,
MongoSupportLevel level,
Map<String, String> constraints) {
}
```
Capability는 `boolean`만 반환하지 않는다. Time Series, Sharding, Encryption, Search처럼 제약이 큰 기능은 토폴로지·버전·권한·비지원 조합을 함께 반환한다.
## 8. Client와 권한 경계
```text
Runtime Strict Client
├─ Stable API V1
├─ apiStrict=true
├─ App read/write credential
└─ D1/D2
Capability Client
├─ capability allowlist
├─ Stable API strict 여부 기능별 결정
├─ capability credential
└─ D3
Admin Client
├─ Runtime Starter에서 기본 미등록
├─ migration/deployment job 전용
├─ admin credential
└─ D4
```
일반 Runtime에서 `MongoClient`, `MongoDatabase`, `MongoCollection<Document>` Bean을 이름 없이 전역 공개하지 않는다. Spring Data가 내부적으로 사용하는 Bean은 존재하지만 애플리케이션이 직접 주입하지 않도록 ArchUnit·Bean visibility·문서 규칙을 적용한다.
## 9. Document 모델링 계약
### 9.1 Embed와 Reference
```text
같이 읽음
AND 같이 변경함
AND 크기가 bounded
→ Embed
독립 lifecycle
OR unbounded growth
OR 여러 Aggregate에서 공유
OR 독립 sharding 필요
→ Manual Reference
```
| 특성 | Embed | Manual Reference |
|---|---:|---:|
| 항상 함께 조회 | 권장 | 가능 |
| 같은 명령에서 변경 | 권장 | 신중 |
| bounded | 필수 | 불필요 |
| 무한 배열 | 금지 | 권장 |
| 공유 데이터 | 신중 | 권장 |
| 독립 보안·보존 | 신중 | 권장 |
| hot write | 신중 | 권장 |
### 9.2 Document Modeling Manifest
도메인별로 다음 Manifest를 제공한다.
```yaml
collections:
orders:
document-type: order
schema-version: 3
max-estimated-document-bytes: 2097152
embedded-collections:
lineItems:
max-elements: 200
stateHistory:
max-elements: 100
references:
customerId:
target-collection: customers
required: true
```
플랫폼은 16 MiB 한계에 가까운 설정을 허용하지 않고 안전 여유가 포함된 프로젝트 상한을 요구한다. 실제 serialized BSON 크기를 contract test로 측정한다.
### 9.3 GridFS 경계
```text
신규 파일 저장
→ Fileserver Application
→ Filesystem / Object Storage Adapter
MongoDB Document
→ FileId / ContentKey Reference
GridFS
→ Legacy read / migration compatibility only
```
## 10. BSON 표현 Manifest
```yaml
mongodb:
mapping:
uuid-representation: STANDARD
big-decimal-representation: DECIMAL128
big-integer-representation: STRING
instant-representation: BSON_DATE
local-date-time: REJECT_UNLESS_CONVERTER_REGISTERED
enum-representation: STRING
type-metadata: ALIAS_FOR_LONG_LIVED_COLLECTIONS
```
| Java/Domain 타입 | BSON | 정책 |
|---|---|---|
| ObjectId | ObjectId | Mongo 전용 내부 ID에 허용 |
| UUID/UUIDv7 | Binary UUID | STANDARD 고정 |
| String Domain ID | String | ObjectId 자동 변환 차단 |
| BigDecimal | Decimal128 | 정밀도·범위 검사 |
| BigInteger | 명시적 | 컬렉션별 고정 |
| Instant | BSON Date | Stable |
| LocalDateTime | 암묵 저장 금지 | 명시 Converter 요구 |
| Enum | String | rename은 migration |
| Money | Document | amount/currency 고정 |
| Encrypted field | BinData | Encryption 모듈 소유 |
다음 차이는 모두 schema 차이다.
```text
absent != null
[] != absent
"1" != 1
String decimal != Decimal128
String object id != ObjectId
```
### 10.1 Golden BSON Gate
모든 지원 타입은 다음 흐름을 통과한다.
```text
Java object
→ MappingMongoConverter
→ BSON snapshot
→ 실제 Replica Set 저장
→ raw BSON read-back
→ Java object round-trip
```
## 11. Type Metadata
| Collection | 정책 |
|---|---|
| 내부 단기 | 기본 `_class` 허용 가능 |
| 장수·다중 버전 | `@TypeAlias` 필수 |
| 외부 공유 | 명시적 `documentType` |
| package refactoring 가능 | FQCN 금지 |
`_class` 제거는 모든 다형성 Mapping에 동일하게 적용하지 않는다. Collection별 `TypeMetadataPolicy`를 사용한다.
## 12. Schema Validation·Versioning·Migration
### 12.1 Validation 계층
```text
Bean Validation
→ 입력·Java 객체 조기 실패
MongoDB JSON Schema
→ 저장 BSON type/required/range 최종 방어
Domain Invariant
→ 업무 규칙
```
| 단계 | validationLevel | validationAction |
|---|---|---|
| 신규 Collection | strict | error |
| Legacy 정비 시작 | moderate | warn |
| Backfill | migration profile | warn 또는 error |
| 정비 완료 | strict | error |
### 12.2 Schema Version
```java
public record DocumentSchemaVersion(int value) {
public DocumentSchemaVersion {
if (value < 0) throw new IllegalArgumentException("negative schema version");
}
}
```
```text
schemaVersion 없음
→ Legacy V0
신규 Write
→ Current Version만
지원 범위 밖 Version
→ MongoDataSchemaUnsupportedException
```
### 12.3 변경 절차
```text
Dual Reader
→ New Writer
→ Rate-limited Backfill
→ Version 잔존량 검증
→ Legacy Index 제거
→ Legacy Reader 제거
```
### 12.4 Migration SPI
```java
public interface MongoMigration {
MigrationId id();
MigrationChecksum checksum();
MigrationPrecondition precondition();
MigrationResult execute(MigrationContext context);
MigrationPostcondition postcondition();
}
```
필수 기능:
```text
Ledger
Distributed Lock
Dry Run
Batch
Checkpoint
Resume
Rate Limit
maxTimeMS
Precondition
Postcondition
Forward Fix
Operator Metadata
```
신규 기본 Adapter는 `mongodb-migration-flamingock`으로 두되, 플랫폼 공개 계약은 Flamingock 타입에 의존하지 않는다. Mongock 신규 채택은 금지한다. Liquibase MongoDB는 후속 D4 Adapter로 추가할 수 있다.
## 13. 쓰기 모델
| 상황 | 기본 연산 |
|---|---|
| 신규 Document | insert |
| 전체 재계산 | versioned save/replace |
| 일부 필드 | updateOne + operator |
| Counter | `$inc` |
| 상태 전이 | expected state + `$set` |
| bounded Set | `$addToSet` |
| 배열 원소 | positional / arrayFilters |
| 변경 결과 반환 | findAndModify |
| 경쟁 생성 | unique index + upsert |
| 독립 다건 | bulk |
| 하나의 multi-document invariant | transaction |
### 13.1 Atomic Update API
```java
public interface MongoAtomicOperations {
<T> AtomicUpdateResult<T> updateOne(
MongoOperationContext context,
Class<T> documentType,
AtomicFilter filter,
AtomicUpdate update,
ReturnDocumentMode returnMode);
}
```
자유형 BSON filter/update를 받지 않고 등록된 field descriptor와 operator allowlist를 사용한다.
### 13.2 Optimistic Revision
전체 Document 교체와 custom update는 다음 predicate를 사용한다.
```text
filter:
_id = id
version = expected
update:
business fields
version = version + 1
```
Conflict Retry는 stale object 재저장이 아니다.
```text
최신 Document 재조회
→ 전체 Use Case 재계산
→ 외부 side effect 부재 확인
→ 제한 재시도
```
## 14. Session·Transaction
### 14.1 선택 기준
```text
단일 Document invariant
→ atomic update
여러 Document invariant
→ transaction
MongoDB + HTTP
→ transaction 밖
MongoDB + Object Storage
→ saga/state machine
MongoDB + Messaging
→ outbox 또는 change-stream bridge
```
### 14.2 Transaction API
```java
public interface MongoTransactionExecutor {
<T> T execute(MongoTransactionProfile profile, Supplier<T> work);
}
public interface ReactiveMongoTransactionExecutor {
<T> Publisher<T> execute(
MongoTransactionProfile profile,
Supplier<? extends Publisher<T>> work);
}
```
### 14.3 Retry 분리
```text
TransientTransactionError
→ 새 Session
→ Transaction 본문 전체 재실행
UnknownTransactionCommitResult
→ 본문 재실행 금지
→ Commit만 재시도
→ 최종 불명 시 reconciliation
```
모든 Retry는 max attempts, max elapsed time, jitter, deadline을 가진다.
## 15. 오류 모델
```text
MongoPersistenceException
├─ MongoDuplicateKeyException
├─ MongoSchemaValidationException
├─ MongoOptimisticConflictException
├─ MongoWriteConflictException
├─ MongoTransactionTransientException
├─ MongoTransactionCommitUnknownException
├─ MongoWriteConcernException
├─ MongoReadConcernException
├─ MongoServerSelectionException
├─ MongoConnectionException
├─ MongoTimeoutException
├─ MongoCursorException
├─ MongoDocumentTooLargeException
├─ MongoBulkPartialFailureException
├─ MongoShardRoutingException
├─ MongoResumeException
├─ MongoEncryptionException
└─ MongoOperationRejectedException
```
오류에는 다음만 보존한다.
```text
operationName
databaseProfile
collectionProfile
operationType
consistencyProfile
retryable
ambiguous
errorLabels
serverCode
attempt
elapsed
traceId
```
Document, Query parameter, credential, plaintext encrypted field, resume token, shard key 실제 값은 저장하지 않는다.
## 16. Query Guardrail
### 16.1 Query 등급
| 등급 | API | 사용 위치 |
|---|---|---|
| Q1 | Derived Query, Projection | 일반 Domain |
| Q2 | Criteria, Custom Repository, Querydsl | 동적 Query |
| Q3 | Aggregation, Search/Vector, 제한 Native BSON | Advanced |
| Q4 | Collection/Index/Admin command | D4 |
### 16.2 정책
```text
Sort field allowlist
Projection field allowlist
Field path allowlist
Operator allowlist
Collation profile
Hint allowlist
maxTimeMS
Result limit
Regex 길이·문자 클래스 제한
Dynamic collection 차단
```
다음 API는 제공하지 않는다.
```java
Document executeUserBson(String json);
Document runCommand(Map<String, Object> input);
```
## 17. Aggregation Guardrail
| 등급 | Stage | 정책 |
|---|---|---|
| A1 | match/project/set/unset/bounded limit | Stable |
| A2 | sort/group/unwind/lookup/bucket | Resource Budget 필수 |
| A3 | facet/graphLookup/large window/union | Advanced Review |
| A4 | out/merge/admin write stage | D4 |
기본 설정:
```text
strictMapping=true
maxTimeMS=profile-defined
resultLimit=bounded
allowDiskUse=explicit resource profile
writeStage=false
lookupCollection=allowlist
operationName=required
```
## 18. Schema·Index Manifest
```yaml
collections:
orders:
owner: order-domain
validator: classpath:/mongodb/orders-schema-v3.json
validation-level: strict
validation-action: error
indexes:
- name: uq_order_number
keys:
orderNumber: 1
unique: true
expected-usage: order.find-by-number
- name: ix_customer_created_id
keys:
customerId: 1
createdAt: -1
_id: -1
expected-usage: order.find-recent
```
Index Manifest 필드:
```text
name
key order
unique
partialFilter
sparse
collation
expireAfterSeconds
hidden
wildcardProjection
geo options
shardKeySupport
expectedUsage
owner
metadataOwnership
```
`metadataOwnership` 값:
```text
APPLICATION
MONGODB_MANAGED
ENCRYPTION_MANAGED
SEARCH_MANAGED
```
운영에서는 자동 Index 생성이 아니라 diff·approval·D4 apply를 사용한다.
```text
Local/Test: apply 허용
Dev: apply + diff
Staging: diff + 승인 apply
Prod: D4만 생성·삭제·collMod
```
Index 삭제:
```text
deprecated 표시
→ usage 확인
→ hidden
→ 관측
→ explain regression
→ 승인 drop
```
## 19. Pagination·Cursor·Bulk
### 19.1 Pagination
```text
작은 Admin 목록 → Page
일반 목록 → Slice
대규모 목록 → Keyset
Batch 처리 → Cursor + batchSize
무한 변경 구독 → Change Stream
```
Keyset은 항상 유일한 tie-breaker를 포함한다.
```text
sort: createdAt DESC, _id DESC
cursor: createdAt + _id + sortVersion
```
### 19.2 Bulk
```java
public record MongoBulkResult(
int requested,
int inserted,
int modified,
int deleted,
int upserted,
List<MongoBulkItemFailure> failures,
boolean partial) {
}
```
Ordered·Unordered Bulk 모두 부분 성공을 보존한다. 성공 항목을 다시 실행하지 않는다.
## 20. Change Stream
### 20.1 상태
```text
STARTING
RUNNING
RESUMING
HISTORY_LOST
FAILED
STOPPED
```
### 20.2 Checkpoint 계약
```text
Event 처리
→ idempotent projector 완료
→ checkpoint 저장
```
이 순서는 duplicate 가능성을 허용하지만 event loss를 방지한다. Projector는 stable event identity와 idempotency key를 사용한다.
### 20.3 Resume
```text
resumeAfter
→ 일반 resume token
startAfter
→ invalidate 이후 재개
oplog history 부족
→ HISTORY_LOST
→ 자동으로 최신부터 시작하지 않음
→ 운영 복구 정책 필요
```
### 20.4 Messaging 경계
```text
MongoDB Physical Change
→ Internal Projector
→ Stable Integration Event
→ Messaging Platform
```
DB 변경과 Event가 반드시 같은 commit에 있어야 하면 Outbox를 사용한다.
## 21. TTL·Geo·특수 Collection 경계
### 21.1 TTL
```text
Query 접근 조건: expiresAt > applicationNow
TTL Index: physical cleanup
```
TTL 삭제 지연을 정상으로 취급한다. 정확한 업무 상태 전이는 별도 Scheduler가 담당한다.
### 21.2 Geospatial
Stable 기본:
```text
GeoJSON
2dsphere
longitude, latitude 순서
거리 단위 명시
bounded near/within query
```
### 21.3 Time Series
Time Series는 일반 Collection 계약을 상속하지 않는다.
| 기능 | 일반 | Time Series |
|---|---:|---:|
| Validator | O | X |
| Change Stream | O | X |
| CSFLE | O | X |
| Transaction Write | O | X |
| Search | 조건부 | X |
| TTL | O | O |
| Document limit | 16 MiB | 별도 제한 |
## 22. Sharding 경계
Application Plane:
```text
ShardKeyDescriptor
ShardAwareQueryValidator
CollectionRoutingProfile
Targeted/Scatter Classification
Telemetry
```
Admin Plane:
```text
shardCollection
analyzeShardKey
refineCollectionShardKey
reshardCollection
zone
balancer
shard add/remove
```
모든 shard-aware write는 shard key 또는 routing evidence를 요구한다. `@Sharded` 하나로 충분하다고 간주하지 않는다.
## 23. Encryption 경계
```text
mongodb-csfle
mongodb-queryable-encryption
```
| 요구 | Capability |
|---|---|
| Query 불필요 PII | randomized 또는 unindexed |
| equality | deterministic 또는 QE equality |
| range | QE range |
| prefix/suffix/substring | MongoDB 8.0 Stable 비지원 |
| tenant key | explicit resolver |
| collection setup | D4 |
| rotation | D4 + runbook |
CSFLE와 QE를 같은 Collection에 동시에 적용하지 않는다. QE 내부 metadata collection과 `__safeContent__`는 ENCRYPTION_MANAGED ownership으로 보호한다.
## 24. Search·Vector 경계
```text
GeoJSON / 2dsphere → Stable
Legacy $text → Compatibility only
MongoDB Search → Advanced
Vector Search → Advanced
Hybrid Search → Advanced
Search Index Admin → D4
```
Index 생성 완료와 `READY` 상태를 구분한다. Atlas Local은 빠른 CI, 실제 목표 배포는 Release Gate다.
## 25. Multi-tenancy 경계
| 모델 | 등급 |
|---|---|
| Single DB | Stable |
| Shared Collection + tenantId | Advanced |
| Database per Tenant | Experimental |
| Collection per Tenant | 제한 Experimental |
| Cluster per Tenant | Infra Profile |
Shared Collection은 모든 find/update/delete, aggregation, change stream에 tenant predicate를 적용하고 unique index·shard key에 tenant 의미를 검증한다. raw tenant ID는 log·metric에 남기지 않는다.
## 26. Security
Principal 분리:
```text
mongo-app-read
mongo-app-write
mongo-change-stream
mongo-migration
mongo-search-admin
mongo-shard-admin
mongo-encryption-admin
mongo-dba
```
Production 기본 금지:
```text
auth disabled
TLS disabled
admin credential 공유
connection string credential source 저장
raw user BSON 실행
arbitrary runCommand
Document 전체 logging
plaintext encrypted field logging
```
Query 입력은 allowlist로 변환하고 Regex는 길이·패턴·timeout을 제한한다.
## 27. Observability
Driver native ObservabilitySettings를 기준으로 한다.
| 영역 | Signal |
|---|---|
| Pool | size, checked-out, wait, failure |
| SDAM | topology, primary, heartbeat |
| Operation | count, latency, result, timeout |
| Write | inserted, modified, deleted, upsert, concern failure |
| Retry | driver/app retry, labels |
| Transaction | duration, abort, body retry, commit retry, unknown |
| Aggregation | duration, disk use, result count |
| Query Efficiency | examined/returned, index, spill |
| Sharding | targeted/scatter |
| Change Stream | lag, resume, history lost |
| TTL | cleanup lag |
| Migration | progress, checkpoint |
| Search | index readiness |
허용 Tag:
```text
mongoProfile
databaseProfile
collectionProfile
operationName
operationType
result
failureCategory
consistencyProfile
```
금지 Tag:
```text
documentId
rawTenantId
dynamicCollectionName
queryParameter
fullBson
resumeToken
shardKeyValue
plaintextPII
credential
```
## 28. Spring Boot 설정
```yaml
backend:
mongodb:
profiles:
default:
uri-secret: secret://mongodb/default-uri
topology: REPLICA_SET
stable-api:
version: V1
strict: true
deprecation-errors: true
consistency-default: PRIMARY_MAJORITY
timeout:
server-selection: 3s
connect: 2s
socket-read: 5s
operation: 3s
pool:
min-size: 2
max-size: 40
max-wait-time: 500ms
mapping:
uuid-representation: STANDARD
big-decimal-representation: DECIMAL128
index:
runtime-auto-create: false
security:
tls-required: true
authentication-required: true
```
Startup 실패 조건:
```text
Production에서 Standalone
Production에서 auth/TLS 비활성
Runtime auto-index 활성
UUID/Decimal 표현 미설정
Stable D1/D2에서 apiStrict 미설정
Required Replica Set인데 topology 불일치
Transaction 활성인데 Replica Set 아님
Change Stream 활성인데 watch capability 없음
Admin Client가 일반 Runtime credential과 동일
Unsupported schema version
Dynamic collection profile
```
## 29. 테스트 아키텍처
```text
Unit
→ Manifest / Policy / Classifier
Repository Slice
→ Converter / Repository / Query
Single-node Replica Set
→ Mapping / Atomic / Transaction / Change Stream
3-node Replica Set
→ Failover / Retry / Commit Unknown / Resume
Sharded Cluster
→ Routing / Chunk Migration / Cross-shard / Reshard
Atlas Local
→ Search / Vector quick CI
Actual Target Deployment
→ Search / Vector / Encryption Release Gate
```
필수 장애 테스트:
```text
Primary kill
Network partition
Server selection timeout
Write response loss
TransientTransactionError
UnknownTransactionCommitResult
Bulk partial failure
Change Stream process kill
Resume token loss
Oplog history loss
Index drift
Schema validation mismatch
Credential rotation
```
## 30. Release Gate
Stable 릴리스 조건:
```text
Java 21 / Boot 4.1 BOM 확인
MongoDB 7.0 compatibility
MongoDB 8.0 primary certification
Single-node RS contract
3-node RS failover
Golden BSON snapshot
Atomic update race
Optimistic conflict
Transaction body/commit retry 분리
Index/Schema manifest drift
Keyset pagination
Bulk partial result
Change Stream resume/history loss
TLS/RBAC/redaction
Driver native observability
```
Advanced 기능은 해당 모듈과 실제 환경 Gate를 통과하기 전 Starter에 포함하지 않는다.
## 31. 구현 단계
| 단계 | 범위 | 완료 조건 |
|---|---|---|
| 1 Foundation | BOM, modules, profiles, topology, stable API | Context startup·topology gate |
| 2 Document Core | BSON manifest, converter, type metadata, modeling | Golden BSON·size guard |
| 3 Write·Consistency | atomic update, revision, profile, transaction, retry | race·failover·commit ambiguity |
| 4 Schema·Query | validator, migration, query, aggregation, index | strictMapping·diff·explain |
| 5 Scale·Stream | keyset, cursor, bulk, TTL, change stream | partial result·resume·kill |
| 6 Operations | security, observability, starter, runbook | least privilege·redaction |
| 7 Advanced | sharding, time series, encryption, search/vector, tenancy | capability-specific gate |
## 32. 완료 정의
다음 질문에 모두 구현과 테스트로 답할 수 있어야 한다.
```text
Document가 bounded라는 증거가 있는가?
BSON 타입 표현이 배포 간 고정되는가?
부분 Update가 전체 Document를 덮지 않는가?
Read/Write Concern의 실제 보장을 호출자가 아는가?
Transaction body retry와 commit retry가 분리되는가?
Bulk 일부 성공이 보존되는가?
Query와 Index가 Release 전에 연결되는가?
Change Stream checkpoint 경계가 명확한가?
TTL을 Scheduler로 오해하지 않는가?
Shard Key 없는 Query를 감지하는가?
암호화 metadata를 drift cleanup이 삭제하지 않는가?
실제 Replica Set·Sharded·Atlas 환경에서 검증되는가?
```
## 33. 요구사항 추적표
| 리서치 영역 | 설계 절 | 구현 계획 영역 |
|---|---|---|
| 기준선·Topology | 4, 8, 28 | Task 1~5, 45~48 |
| D1~D4 경계 | 5, 6 | Task 1, 38, 39 |
| Document Modeling | 9 | Task 10 |
| BSON 표현 | 10, 11 | Task 6~9 |
| Schema·Migration | 12 | Task 11, 18~22, 46 |
| Atomic Write·Optimistic | 13 | Task 13~14 |
| Consistency·Transaction | 14 | Task 23~28 |
| 오류·Retry | 15 | Task 4~5, 27~28 |
| Query·Aggregation | 16~17 | Task 15~17 |
| Index | 18 | Task 18~20 |
| Pagination·Bulk | 19 | Task 29~31 |
| Change Stream | 20 | Task 33~35 |
| TTL·Geo | 21 | Task 32, 36 |
| Security·Observability | 26~27 | Task 37~40, 47 |
| Starter·Gate | 28~30 | Task 41~50 |
| Advanced | 22~25 | 별도 Advanced 계획 |