- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
14 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | body | assets | evidence | source | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | a06-f018-changestreams-false | 플래그는 고정 거짓이라 능력 검사를 끄지만, 조립 조건이 아니라서 소비자 빈은 그대로 생성된다 | assembly-ownership | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:a06-f018-changestreams-false | 2026-09-02 | case-a06-f018-changestreams-false.body.md |
|
|
|
플래그는 고정 거짓이라 능력 검사를 끄지만, 조립 조건이 아니라서 소비자 빈은 그대로 생성된다
설정 주석은 드라이버 쪽 구현이 출하되지 않아 빈이 0 이라는 것을 근거로 플래그를 고정 거짓으로 만든다. 이 리비전에서 그 구현에는 빈 선언이 있고, 배포가 다섯을 공급하면 소비자도 선다. 조립 조건 어디에도 그 플래그는 없다.
관계
- 거부라고 적힌 처리가 폐기이고, 그 값을 읽는 시작 검사는 켤 방법이 없다 같은 플래그를 다른 절에서 다룬 기록이다. 거부와 폐기의 차이는 그 기록이 다룬다.
- 하위 시스템 전체가 미배선인데 그것을 켜는 플래그는 시작 검사를 수행한다 형제 불리언 쪽 기록이다. 트랜잭션 계수와 그 결과는 그 기록이 다룬다.
- @Bean이 있다는 것은 조립 증거가 아니다 플래그와 조립의 관계를 확인하는 규칙이다.
문제
시작 검증기에는 인접한 두 분기가 있다. 하나는 트랜잭션이 켜져 있는데 토폴로지가 지원하지 않으면 던지고, 다른 하나는 변경 스트림에 대해 같은 일을 한다. 두 좌항은 같은 설정 타입의 형제 불리언이고 자동 구성의 인접한 두 줄이 넘긴다.
두 불리언은 같은 검증기에서 서로 다르게 끝난다. 앞의 값은 배포가 넣은 대로 도착하고, 뒤의 값은 컴팩트 생성자가 이미 거짓으로 바꾼 뒤다.
결론
플래그가 고정 거짓이므로 검증기의 변경 스트림 분기는 실행되지 않는다. 조립은 그와 무관하게 진행된다. 소비자 빈의 조건은 배포가 공급해야 하는 타입 다섯이고, 그 목록에 이 플래그는 없다. 자동 구성 파일 전체에서 그 이름이 나오는 줄은 검증기 인자 하나뿐이다.
전체 자동 구성을 올린 스프링 컨텍스트로 확인했다. 설정 빈은 change-streams=true 를 받고도 거짓을 보고하고, 그 두 경우의 빈 집합이 같다. 모듈 opt-in 을 켜고 리액티브 템플릿이 있으면 드라이버 쪽 구현 빈은 만들어지고 소비자 빈은 만들어지지 않는다. 다섯을 함께 넣으면 소비자 빈도 만들어진다. opt-in 을 켜지 않으면 셋 다 없다.
주석은 이 코드가 있기 전 상태를 서술한다. 빈이 0 이고 스레드가 0 이라는 근거는 이 리비전에서 성립하지 않는다.
남는 것은 능력 검사만 꺼진 상태다. 다만 그 검사가 열리는 조건이 따로 있다. 검사 빈은 토폴로지 프로브를 조건으로 걸고, 보안 프로파일과 관리 자격 참조와 스키마 버전 범위 중 하나라도 없으면 부분 검증 대신 예외로 닫는다. 그리고 검증기는 선언 토폴로지와 실제를 능력 검사보다 먼저 대조한다. 변경 스트림 분기는 그 둘을 통과한 배포에서만 차례를 얻는데, 그 차례가 와도 좌항이 거짓이다.
그 다음 실패는 커서를 여는 시점의 드라이버 오류다. 복구 정책은 서버 코드가 이력 소실이 아니고 재개 가능 라벨도 아니면 실패로 확정하며 장애 조치 런북을 붙인다. 런북 어디를 봐도 그 세 낱말이 없다. 이 연쇄는 형제 기록이 오플로그 없는 서버에서 실행으로 확인했다.
수정은 셋 중 하나다. 플래그를 되살려 조립 조건으로 쓰거나, 소비자 빈이 설 때 능력을 기동에서 확인하거나, 최소한 주석을 현재 사실로 고치는 것이다.
검증 환경
OpenJDK : 21.0.12 확인 방식 : 전체 자동 구성을 올린 스프링 컨텍스트에서 빈 집합과 바인딩 값 비교, 코드베이스 정적 검색 소스 수정 : x
재현 조건
- 시작 검증기의 인접한 두 분기와 그 좌항을 넘기는 두 줄을 읽는다.
- 그 검사를 만드는 빈의 조건과 입력이 빠졌을 때의 처리를 읽는다.
- 컴팩트 생성자의 플래그 강제와 그 주석을 읽는다.
- 소비자 빈에 붙은 조건과 자동 구성 파일에서 그 플래그가 나오는 줄 수를 확인한다.
- MongoPlatformAutoConfiguration 전체와 블로킹·리액티브 템플릿 빈을 등록한 컨텍스트를 ca-skeleton.persistence-mongo.enabled=true 로 띄우고 두 빈과 설정 빈의 유무, 그리고 바인딩된 플래그 값을 읽는다.
- 조건 다섯을 함께 넣고 같은 것을 읽는다.
- 두 경우를 change-streams=true 를 넣은 상태에서 반복한다.
- opt-in 을 켜지 않은 경우와 리액티브 템플릿이 없는 경우를 각각 읽는다.
- 복구 정책의 분류와 그것이 붙이는 런북 전체를 읽는다.
본문
시작 검증기에는 능력을 요구하는 분기가 둘 있고, 두 좌항은 같은 설정 타입의 형제 불리언이다.
두 분기가 읽는 값이 오는 자리
:::evidence key="a06-f018-changestreams-false" alt="시작 검증기의 인접한 두 능력 분기와 그 좌항을 넘기는 자동 구성의 두 줄, 그 검사를 만드는 빈의 조건과 입력이 빠졌을 때 닫는 처리, 컴팩트 생성자의 플래그 강제와 그 주석, 자동 구성 파일에서 그 플래그가 나오는 줄 수, 소비자 빈에 붙은 조건 전체, 복구 정책의 분류 메서드, 그리고 그것이 붙이는 런북의 증상 절 전체와 그 런북에서 단독 서버·오플로그·해당 오류 코드가 나오는 줄 수를 출력한 터미널 기록." caption="두 분기의 좌항은 형제 불리언이고 인접한 두 줄이 넘김 · 검사 빈은 토폴로지 프로브 조건이고 입력이 빠지면 닫음 · 변경 스트림은 생성자에서 고정 거짓이고 자동 구성 파일에 그 이름이 나오는 줄은 1 · 소비자 조건은 @ConditionalOnMissingBean 과 타입 다섯 · 실패는 FAILED 와 장애 조치 런북 · 그 런북에 단독 서버·오플로그·40573 은 0줄 — 91줄 · exit 0" zoom="true" :::
if (transactionsEnabled && !capabilities.isStable(MongoCapability.TRANSACTION)) {
...
if (changeStreamsEnabled && !capabilities.isStable(MongoCapability.CHANGE_STREAM)) {
자동 구성의 인접한 두 줄이 그 좌항을 넘긴다.
properties.transactions(),
properties.changeStreams(),
앞의 값은 배포가 넣은 대로 도착한다. 그 값에서 무슨 일이 벌어지는지는 형제 기록이 다룬다. 여기서는 뒤의 값이 이미 거짓이라는 것과, 그런데도 조립은 진행된다는 것만 본다.
조립 조건
주석이 근거로 든 것은 빈이 0 이라는 사실이다.
// Experimental, and therefore not a switch (MNG-INT-003). The driver-side source — watch,
// resumeAfter/startAfter, cursor lifetime, reconnection — is not shipped; what exists is policy
// and value objects that do not add up to a running consumer. Accepting the flag and ignoring …
...
changeStreams = false;
소비자 빈에 붙은 조건은 @ConditionalOnMissingBean 과 타입 다섯의 @ConditionalOnBean 이다.
@org.springframework.boot.autoconfigure.condition.ConditionalOnBean({
dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamSubscription.class,
dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeCheckpointStore.class,
dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeTokenCodec.class,
dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeProjector.class,
dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeDeduplicationStore
.class
})
다섯 다 배포가 공급해야 하는 타입이다. 이 플래그는 목록에 없고, 자동 구성 파일 전체에서 그 이름이 나오는 줄은 검증기 인자 하나뿐이다.
컨텍스트를 띄운 결과
:::evidence key="a06-f018-changestreams-false-wiring" alt="전체 자동 구성을 등록한 스프링 컨텍스트를 모듈 opt-in 없이, opt-in 과 리액티브 템플릿만으로, opt-in 과 배포가 공급해야 하는 다섯을 함께, 그리고 opt-in 과 리액티브 템플릿 없이 각각 띄워 드라이버 쪽 구현 빈과 소비자 빈과 설정 빈의 유무, 그리고 바인딩된 플래그 값을 change-streams 를 넣지 않은 경우와 넣은 경우에 대해 읽은 터미널 기록." caption="opt-in 없으면 셋 다 없음 · opt-in 과 템플릿이면 드라이버 쪽만 섬 · 다섯을 넣으면 소비자도 섬 · 설정 빈은 change-streams=true 를 받고도 changeStreams()=false · 두 경우의 빈 집합이 같음 — 13줄 · exit 0" zoom="true" :::
MongoPlatformAutoConfiguration 전체를 등록하고 블로킹·리액티브 템플릿을 넣어 띄웠다.
[opt-in, 리액티브 템플릿만]
기본 드라이버쪽=있음 소비자=없음 설정빈=있음 changeStreams()=false
change-streams=true 드라이버쪽=있음 소비자=없음 설정빈=있음 changeStreams()=false
[opt-in, 배포가 공급해야 하는 다섯을 함께]
기본 드라이버쪽=있음 소비자=있음 설정빈=있음 changeStreams()=false
change-streams=true 드라이버쪽=있음 소비자=있음 설정빈=있음 changeStreams()=false
설정 빈은 컨텍스트에 있고 속성을 받는다. 받고도 거짓을 보고하므로 조립 조건에 닿기 전에 이미 값이 정해져 있다. 소비자가 서는 조건은 다섯을 공급했는지 하나다.
모듈 opt-in 이 없으면 세 빈이 모두 만들어지지 않고, opt-in 이 있어도 리액티브 템플릿이 없으면 두 빈이 만들어지지 않는다.
[모듈 opt-in 없이]
기본 드라이버쪽=없음 소비자=없음 설정빈=없음
[opt-in, 리액티브 템플릿 없이]
기본 드라이버쪽=없음 소비자=없음 설정빈=있음 changeStreams()=false
능력 검사가 열리는 조건
검사가 꺼진 것과 검사가 애초에 만들어지지 않는 것은 다르다. 검사 빈부터 조건이 있다.
@Bean
@ConditionalOnBean(MongoTopologyProbe.class)
public InitializingBean mongoPlatformStartupCheck(
...
if (security == null || admin == null || versions == null) {
// Fail closed rather than validate a subset. A partial startup check reports success for
// the parts nobody supplied, which is the shape the missing wiring already had.
토폴로지 프로브가 있어야 만들어지고, 보안 프로파일과 관리 자격 참조와 스키마 버전 범위가 다 있어야 돈다. 그리고 검증기는 능력 검사보다 먼저 선언 토폴로지와 실제를 대조한다. 그 둘을 통과한 배포에서만 변경 스트림 분기가 자기 차례를 얻고, 그 차례에서 좌항이 거짓이다.
그 다음 실패가 가는 곳
if (failure.hasLabel("ResumableChangeStreamError")) {
return MongoChangeStreamRecoveryDecision.resume();
}
return MongoChangeStreamRecoveryDecision.halt(MongoChangeStreamState.FAILED, FAILURE_RUNBOOK);
...
private static boolean isHistoryLost(int serverCode) {
return serverCode == 286 || serverCode == 280;
토폴로지가 복제 셋이 아니라는 오류는 286 도 280 도 아니고 재개 가능 라벨도 없으므로 셋째 갈래다. 붙는 런북의 증상 절은 네 항목이고 전부 프라이머리 선출과 서버 선택 지연이다.
- `MongoServerSelectionException` / `MongoConnectionException` spike, then recovery within seconds.
- `MongoSdamObservationListener` reports a topology change (primary removed, new primary elected).
- `MongoPoolObservationListener` shows checkout wait times rising while server-side command duration
stays flat — the wait is topology, not query cost.
- Latency spike on writes with no corresponding rise in read latency.
증상 절뿐 아니라 그 런북 전체에서 단독 서버도 오플로그도 해당 오류 코드도 나오지 않는다. 이 연쇄를 실제 서버에서 이은 것은 형제 기록이다.
확인하지 못한 것
다섯을 공급한 포크의 배포를 오플로그 없는 토폴로지에 올려 기동 통과와 커서 열기 실패를 이어서 재현하지는 않았다. 그 연쇄는 형제 기록이 단독 서버에서 실행으로 확인했다. 여기서는 조립 조건과 검사가 열리는 조건까지 확인했다.