Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/runtime-reachability-and-composition/case/case-a06-f003-change-streams-true.md
T
DongHyeonkaandClaude Fable 5.1 b25357c48a docs(clean-architecture-backend-template): fold analysis into final and re-select one topic
- 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>
2026-09-07 12:39:20 +09:00

13 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-f003-change-streams-true 거부라고 적힌 처리가 폐기이고, 그 값을 읽는 시작 검사는 켤 방법이 없다 runtime-reachability-and-composition clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:a06-f003-change-streams-true 2026-09-02 case-a06-f003-change-streams-true.body.md
key file
a06-f003-change-streams-true ../../../final/evidence/rendered/a06-f003-change-streams-true.svg
key file
a06-f003-change-streams-true-probe ../../../final/evidence/rendered/a06-f003-change-streams-true-probe.svg
key file
a06-f003-change-streams-true-shipped ../../../final/evidence/rendered/a06-f003-change-streams-true-shipped.svg
../../../final/evidence/raw/a06-f003-change-streams-true.txt
../../../final/evidence/raw/a06-f003-change-streams-true-probe.txt
../../../final/evidence/raw/a06-f003-change-streams-true-shipped.txt
원본 분석 절은 `final/document.md#a06#L156` 이다.
그 절의 판정은 P3 이고, 근거로 변경 스트림 실행체가 애초에 출하되지 않는다는 것을 든다. 같은 문서 `#L1069` 이 이 리비전에서 그 전제를 부정한다. 그래서 P3 에 붙은 "현재 잘못된 동작을 만들지는 않는다"는 그대로 두기 어렵지만, 등급 재조정은 이 기록이 하지 않는다. `#L1069` 의 P2 는 별도 기록이 다룬다.
네 호출이 각각 어디서 멈추는지, 그 분기가 내는 문장이 저장소에 몇 번 나오는지, 소비자 조건 다섯의 구현체가 전부 시험 픽스처라는 것, 그리고 세 입력의 바인딩 결과와 단독 서버에서의 두 배선 비교와 복구 정책 판정은 이 기록에서 확인했다.

거부라고 적힌 처리가 폐기이고, 그 값을 읽는 시작 검사는 켤 방법이 없다

설정 타입의 컴팩트 생성자가 변경 스트림 플래그를 예외 없이 거짓으로 덮어쓴다. 그 자리의 주석은 이 처리를 거부라고 부른다. 거부와 폐기는 운영자에게 다른 사건이다. 하나는 기동이 멈추고 하나는 아무 일도 일어나지 않는다.

관계

  • 하위 시스템 전체가 미배선인데 그것을 켜는 플래그는 시작 검사를 수행한다 그 기록에서는 플래그가 켜져 있어 요구를 만드는데 실행체가 없다.
  • sanitize가 아니라 reject가 기본이다 이 주석이 따르겠다고 선언한 규칙이다.
  • 플래그는 고정 거짓이라 능력 검사를 끄지만, 조립 조건이 아니라서 소비자 빈은 그대로 생성된다 같은 플래그를 다른 절에서 다룬 기록이다. 등급 재조정은 그 기록이 한다.

문제

레코드 컴포넌트는 넷이고 생성자는 넷을 서로 다르게 다룬다. 프로파일은 널이면 빈 맵으로 흡수한다. 필요 보조 노드 수는 널이면 기본값을 넣고 음수면 예외로 거부한다. 형제 불리언인 트랜잭션 플래그는 그대로 보존한다. 변경 스트림 플래그만 예외 없이 거짓이 된다.

결론

이 플래그가 실제로 끄는 것은 실행체가 아니라 검사다. 시작 검증기는 변경 스트림이 켜져 있을 때 토폴로지 능력을 확인하는 분기를 갖는다. 그 인자를 넘기는 프로덕션 지점은 자동 구성 한 줄이고, 그 줄이 읽는 값은 생성자가 이미 거짓으로 덮어쓴 뒤다.

시험도 그 분기 본문에 닿지 않는다. 검증기를 만드는 시험은 하나이고 호출은 넷인데, 둘은 거짓을 넘기고, 하나는 앞 단계인 토폴로지 검사에서 먼저 멈추며, 나머지 하나는 복제 셋이라 능력이 안정으로 나온다. 그 분기가 내는 문장은 저장소 안에서 자기 throw 자리 한 곳에만 있고, 그것을 단언하는 시험은 없다.

오플로그가 없는 단독 서버로 확인했다. 출하 배선의 시작 검증은 통과하고, 같은 검증기에 리터럴 참을 넘기면 토폴로지를 지목하는 예외가 난다. 그 뒤 커서를 열면 드라이버가 명령 단계에서 거절하고, 복구 정책은 그것을 실패로 확정하며 프라이머리 장애 조치 런북을 붙인다. 그 런북에 오플로그 없는 토폴로지 항목은 없다.

주석은 이 처리의 근거로 드라이버 쪽 구현이 출하되지 않아 빈이 0 이라는 것을 든다. 그 근거는 이 리비전에서 성립하지 않는다. 다만 그 사실이 바꾸는 것은 원본 절의 등급 판단이고, 이 기록의 관찰은 거부와 폐기의 차이 그대로다.

수정은 둘 중 하나다. 값을 정말로 거부하거나, 플래그를 레코드 컴포넌트에서 빼 존재하지 않는 스위치로 만드는 것이다.

검증 환경

OpenJDK : 21.0.12 MongoDB : 8.0.16 단독 서버 확인 방식 : 실제 바인딩에 세 입력 통과, 실제 서버에 시작 검증기와 커서 열기와 복구 정책 실행 소스 수정 : x

재현 조건

  1. 컴팩트 생성자에서 네 컴포넌트의 처리를 비교하고 변경 스트림 자리의 주석을 읽는다.
  2. 시작 검증기의 변경 스트림 분기, 그 인자를 넘기는 곳, 검증기를 만드는 곳을 전수로 센다.
  3. 그 분기가 내는 문장이 저장소 어디에 나오는지 센다.
  4. 검증기를 만드는 시험의 네 호출이 각각 어디서 멈추는지 읽는다.
  5. 오플로그가 없는 단독 서버를 띄운다.
  6. 세 입력을 실제 바인딩에 통과시키고 각각의 결과를 읽는다.
  7. 같은 서버에 대해 출하 배선과 리터럴 참 배선으로 시작 검증을 각각 돌린다.
  8. 같은 클래스로 커서를 열고, 그 실패를 복구 정책에 넘긴다.

본문

설정 타입의 주석은 이 값을 저장하지 않고 거부한다고 적으면서, 근거로 드라이버 쪽 구현이 없다는 것을 든다.

네 값 중 하나만 삼켜진다

:::evidence key="a06-f003-change-streams-true" alt="설정 타입의 컴팩트 생성자에서 네 컴포넌트가 각각 흡수·보존·덮어쓰기·예외로 처리되는 구간과 변경 스트림 자리의 주석, 시작 검증기의 변경 스트림 분기, 그 분기가 내는 문장이 저장소에 나오는 곳 전수, changeStreams 라는 이름이 나오는 곳 전수, 검증기를 만드는 곳 전수를 출력한 터미널 기록." caption="프로파일은 빈 맵으로 흡수 · 음수 보조 노드 수는 예외 · 트랜잭션은 보존 · 변경 스트림만 예외 없이 거짓 · 분기가 내는 문장은 자기 throw 자리 한 곳뿐 · 검증기 생성 지점은 프로덕션 1 시험 1 — 43줄 · exit 0" zoom="true" :::

// 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
// it
// would leave an operator believing it took effect, so the value is refused rather than stored:
// zero beans, zero threads, and a `true` that cannot be honoured never becomes one that looks
// honoured.
changeStreams = false;

여덟 줄 아래에서 같은 생성자가 음수 보조 노드 수를 예외로 던진다. 거부가 어떤 모양인지 같은 생성자 안에 있고, 이 줄은 그 모양이 아니다. 형제 불리언인 transactions 는 손대지 않는다.

검증기가 받는 값은 언제나 거짓이다

if (changeStreamsEnabled && !capabilities.isStable(MongoCapability.CHANGE_STREAM)) {

좌항을 넘기는 프로덕션 지점은 자동 구성 한 줄뿐이다. 검증기를 만드는 곳은 그 줄과 시험 하나이고, 시험은 리터럴을 넘긴다. 그런데 그 시험의 네 호출 어느 것도 이 if 의 본문을 실행하지 않는다.

  • 단독 서버에 프로덕션 프로파일을 놓는 호출은 참 둘을 넘기지만, validate() 가 능력 검사보다 먼저 부르는 토폴로지 검사에서 멈춘다
  • 선언과 실제가 어긋나는 호출은 거짓 둘을 넘긴다
  • 트랜잭션만 켜는 호출은 변경 스트림에 거짓을 넘긴다
  • 정상 복제 셋 호출은 참을 넘기지만 그 토폴로지에서는 능력이 안정으로 나온다

if 가 만드는 문장은 저장소 전체에서 자기 throw 자리 한 곳에만 있다.

실제 서버에서

:::evidence key="a06-f003-change-streams-true-probe" alt="오플로그가 없는 단독 MongoDB 를 띄우고 세 입력을 실제 바인딩에 통과시킨 결과, 관측 토폴로지와 변경 스트림 능력 판정, 출하 배선과 리터럴 참 배선으로 각각 돌린 시작 검증 결과, 같은 클래스를 직접 만들어 커서를 연 결과, 그리고 그 실패를 복구 정책에 넘긴 판정을 출력한 터미널 기록." caption="단독 서버에서 능력 판정은 isStable=false · 출하 배선의 시작 검증은 통과, 리터럴 참 배선은 토폴로지를 지목하며 거절 · 커서를 열면 드라이버가 40573 으로 거절 · 복구 정책은 FAILED 와 failover 런북 — 20줄 · exit 0" zoom="true" :::

오플로그가 없는 단독 서버를 띄우고 같은 클래스들을 그대로 썼다. 리터럴 참을 넘긴 배선의 예외 메시지는 이 배포에 없는 것과 필요한 것을 함께 적는다.

change streams are enabled but unavailable here: {reason=topology is STANDALONE, which has no oplog}; a REPLICA_SET, SHARDED or ATLAS topology is required

출하 배선에서는 이 예외가 만들어지지 않는다. 같은 클래스를 직접 만들어 커서를 열면 드라이버가 명령 단계에서 거절한다.

Command execution failed on MongoDB server with error 40573 (Location40573): 'The $changeStream stage is only supported on replica sets' on server 127.0.0.1:57017.

그 실패를 복구 정책에 넘기면 판정이 나온다.

MongoChangeStreamRecoveryDecision[state=FAILED, autoResume=false, requiredRunbook=docs/mongodb/runbooks/failover.md]

정책은 서버 코드가 이력 소실이면 전용 런북을, 라벨이 재개 가능이면 재개를 고르고, 그 밖은 실패로 확정한다. 40573 은 셋째 갈래다. 붙는 런북은 프라이머리 선출과 서버 선택 지연을 다루고, 이 서버에 오플로그가 없다는 경우는 다루지 않는다.

주석이 근거로 든 사실

:::evidence key="a06-f003-change-streams-true-shipped" alt="드라이버 쪽 구현을 만드는 빈 위에 겹쳐 있는 조건 넷(모듈 opt-in, 블로킹 템플릿 클래스, 리액티브 템플릿 클래스와 빈), 그 빈이 커서를 여는 호출 사슬, 소비자 빈이 요구하는 조건 다섯, 그 다섯을 구현하는 클래스 전수, 저장소 자신의 배선 시험 네 개, 그리고 이 자동 구성 파일에서 해당 플래그가 나오는 유일한 줄을 출력한 터미널 기록." caption="source 빈의 조건은 모듈 opt-in 과 리액티브 템플릿, 그리고 @ConditionalOnMissingBean · 소비자 조건 다섯의 구현체 열은 전부 시험 픽스처 · 배선 시험이 조립·미조립·미배선을 각각 고정 · 플래그는 검증기 인자 한 줄뿐 — 56줄 · exit 0" zoom="true" :::

드라이버 쪽 구현에는 빈 선언이 있다. 그 빈의 메서드에 붙은 조건은 @ConditionalOnMissingBean 하나지만, 그것을 감싼 중첩 설정 클래스가 리액티브 템플릿을 클래스로도 빈으로도 요구하고, 다시 그 바깥이 모듈 opt-in 을 요구한다. 무조건은 아니고 리액티브 템플릿이 있는 배포에서는 언제나다. 그 조건 넷 어디에도 변경 스트림 플래그는 없다.

소비자 빈의 선언도 있다. 조건이 다섯인데 다섯 다 배포가 공급해야 하는 타입이고, 이 저장소는 다섯 중 어느 것도 빈으로 만들지 않는다. 열 개의 구현체가 전부 시험 안의 private static final class 다.

저장소 자신의 배선 시험이 세 상태를 각각 고정한다. 리액티브 템플릿이 없으면 아무것도 만들지 않고, 있으면 source 는 만들고 투영기가 없으면 소비자는 만들지 않고, 다섯을 주면 소비자가 조립된다.

주석이 없다고 적은 넷 가운데 watchresumeAfter/startAfter 는 이 클래스에 있고, 커서 수명과 재연결은 같은 패키지의 소비자에 있다.

확인하지 못한 것

소비자 빈의 조건 다섯을 공급하는 포크의 배포는 다루지 않았다. 이 저장소 안에서 확인한 것은 다섯 타입의 구현체가 전부 시험 픽스처라는 것, 조건 목록에 이 플래그가 없다는 것, 그리고 저장소 자신의 배선 시험이 그 세 상태를 각각 고정한다는 것이다.