Files
llm-wiki/raw/branch-notes/feature-persistence-failure-baseline.md
T

46 KiB
Raw Blame History

title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch related_projects governing_docs tags created target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-persistence-failure-baseline branch-note raw feature-persistence-failure-baseline
ca-skeleton
wiki/projects/ca-tmpl/data-layer-persistence-cache-outbound
branch
ca-skeleton
persistence
jpa
database
2026-05-21 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-006 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-006
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TESTCONTAINERS-001@1
1 de3aae90785a1d43f67d6b179b3372223b788348472b4a1667f38ec217f73eb0

branch: feature-persistence-failure-baseline

Layer: raw/branch-notes/ — DB/JPA 실패 분류와 persistence adapter 실패 계약을 정의합니다.

부모 (필수)

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: persistence failure mapping과 integration test가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1 database는 PostgreSQL 16 단일 stack이다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TESTCONTAINERS-001@1 Testcontainers는 persistence/outbound integration test에 사용하고 unit·architecture·contract test에서는 금지한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

DB/JPA 실패를 단순히 DataIntegrityViolationException -> 409로 끝내면 운영 기준에 부족합니다. connection unavailable, lock, timeout, integrity, query/system failure를 분리하고 presentation까지 JPA 예외가 새지 않게 해야 합니다.

  • 이슈:
  • PR:

범위

포함 범위

  • Spring DataAccessException 계열 분류.
  • JPA exception mapping.
  • DB unavailable/lock/query timeout/data integrity 분류.
  • SQL/parameter 로그 금지.
  • datasource/pool/timeout/connection exhaustion log field.
  • Hikari metric 노출 기준.
  • OSIV off 유지 검증.

제외 범위

  • 특정 DB vendor 최적화.
  • migration strategy.
  • business transaction 설계.

근거 (필수, 최소 1개+)

본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.

Source 정당화하는 결정
raw/official-docs/persistence-spring-dataaccessexception-hierarchy SQLState 9-row matrix가 Spring DAO hierarchy(TransientDataAccessException / `NonTransientDataAccessExcepti...
raw/official-docs/persistence-osiv-antipattern-hibernate-vladmihalcea OSIV off 기본값의 외부 근거
raw/official-docs/persistence-hikaricp-pool-sizing-wiki Hikari alert threshold (pool wait p99 > 100ms 5분 / exhaustion > 1분
raw/official-docs/persistence-r2dbc-reactive-spring R2DBC reactive 대안

외부 근거 (Group G-C — Persistence failure)

ca-tmpl 결정의 backbone과 대안 비교 자료. 각 raw는 별도 파일에서 trade-off를 정리.

검색 키워드 기록: Spring DataAccessException hierarchy, OSIV anti-pattern Vlad Mihalcea, HikariCP about pool sizing, R2DBC vs JDBC reactive.

TODO

TODO drained — 결정은 아래 표/결정 사항 참조.

Work Item Contract

각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.

field required rule
Decision yes 구현자가 선택해야 하는 기본값
Allowed yes 허용되는 예외와 조건
Forbidden yes 절대 금지되는 구현/문서 상태
Required registry update conditional error/env/header/log/metric/capability 변경 시 필수
Required contract test yes 계약 위반 시 실패해야 하는 테스트
Failure condition yes review/build에서 실패로 판정할 상태
Canonical extraction target yes wiki/projects 승급 위치

진행 중 메모

  • persistence failure는 infrastructure에서 operational error로 변환되어야 합니다.

결정 사항 (decisions)

  • 2026-05-21: JPA/Spring exception은 presentation까지 노출하지 않음.
  • 2026-05-22: disaster recovery는 backup 존재가 아니라 restore drill 통과를 기준으로 판단. 기본은 분기 1회 staging/local restore smoke.
  • 2026-05-22: read replica는 기본 미사용. 활성화 시 max replica lag threshold와 stale-read 허용 endpoint를 명시.
  • 2026-05-22: OSIV는 off가 기본이며 lazy loading으로 presentation에서 DB 접근이 발생하면 계약 위반.

결정-근거 매핑

각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. Decision ID 는 본 branch-note 안에서 안정적으로 유지. Claim ID 는 cited raw 의 ## Claims Extracted 표에서 verbatim 확인된 것만 사용. 회사 기술블로그는 company-case-study 로만 라벨 (best practice 단정 금지).

⚠️ CATEGORY_DRIFT (정합 권고): 아래 D4 의 PERSISTENCE/DB_UNAVAILABLE · D5 의 DATA_INTEGRITY_VIOLATION 표기는 Category.java 10-value enum / error-codes.yaml 의 실제 값과 어긋난다. 권위 SSOT 값은 §SQLState → Error Code Matrix 와 §Audit & Findings 참조 — PERSISTENCE 카테고리는 enum 에 존재하지 않음. 사용자 결정 영역이라 자동 rewrite 보류, 정합 권고만 남긴다.

Decision ID Decision (요약) Supporting Claims Evidence Strength Open Risk
D1 JPA/Spring exception 은 presentation 까지 노출하지 않음 (3-way classifier: TransientDataAccessException / NonTransientDataAccessException / RecoverableDataAccessException 위에 SQLState matrix) raw/official-docs/persistence-spring-dataaccessexception-hierarchy.md#SDA-EX-C1, #SDA-EX-C2, #SDA-EX-C3, #SDA-EX-C5 official-vendor-doc + official-reference SQLState ↔ Spring exception class 의 vendor 매핑 (8*, 23*, 40001, 40P01, 23505) 은 #SDA-EX-C6/#SDA-EX-C7needs-confirmation — ca-tmpl 의 9-row matrix 는 sql-error-codes.xml 직접 검증 전까지 vendor 정당성 미확정
D2 OSIV 는 off 가 기본 — lazy loading 으로 presentation 에서 DB 접근 발생 시 계약 위반 raw/official-docs/persistence-osiv-antipattern-hibernate-vladmihalcea.md#OSIV-AP-C1, #OSIV-AP-C2, #OSIV-AP-C3, #OSIV-AP-C4 official-vendor-doc (Spring Boot WARN, C4) + engineering-blog (Vlad Mihalcea, C1~C3 — Hibernate developer advocate 의 권위 있는 분석이지만 Hibernate User Guide 자체의 anti-pattern 선언 verbatim 미확보) Hibernate ORM User Guide 자체에서 OSIV deprecation 또는 anti-pattern 선언 verbatim 확보 필요 (현재 vladmihalcea.com WebFetch 차단으로 재검증 보류)
D3 Hikari pool wait p99 > 100ms 5분 → P2, pool exhaustion (active = max) > 1분 → P1 raw/official-docs/persistence-hikaricp-pool-sizing-wiki.md#HIKARI-POOL-C1, #HIKARI-POOL-C5 (MBean attribute: ThreadsAwaitingConnection, ActiveConnections, TotalConnections) official-vendor-doc (HIKARI-POOL-C1/C5 — pool axiom + MBean attribute 존재) 구체적 threshold 수치 (100ms / 5분 / 1분) 는 HikariCP 가 정의하지 않은 운영자 SLO — UNSUPPORTED_THRESHOLD (HikariCP 공식 권고가 아님, ca-tmpl 내부 결정). Micrometer metric 이름 (hikaricp.connections.acquire, .pending) 은 #HIKARI-POOL-C6needs-confirmation — Micrometer / Spring Boot Actuator 측 별도 raw 필요 (registry 는 .acquire/.usage/.active 사용 — §Audit METRIC_NAME_DRIFT)
D4 DB unavailable →PERSISTENCE/DB_UNAVAILABLE, HTTP 503, retryable true raw/official-docs/persistence-spring-dataaccessexception-hierarchy.md#SDA-EX-C3 (TransientDataAccessException 의 top-level 3-way 분류 존재) official-reference (3-way 분류 존재만 보장) SQLState 08* → DataAccessResourceFailureException 의 직접 매핑은 #SDA-EX-C7 needs-confirmation — vendor 별 sql-error-codes.xml 검증 전까지 ca-tmpl DB_UNAVAILABLE 매핑 정당성 미확정. ⚠️ PERSISTENCE 카테고리는 enum 부재 — registry 실제값 TRANSIENT_DEPENDENCY (§Audit CATEGORY_DRIFT)
D5 integrity violation →DATA_INTEGRITY_VIOLATION, HTTP 409, retryable false; 23505 unique violation 은 별도 code (DB_UNIQUE_VIOLATION) raw/official-docs/persistence-spring-dataaccessexception-hierarchy.md#SDA-EX-C3 (NonTransientDataAccessException top-level 존재) official-reference (3-way 분류만 보장) 23* → DataIntegrityViolationException, 23505 → DuplicateKeyException 의 위계는 #SDA-EX-C7 needs-confirmationDuplicateKeyException 의 직접 부모가 DataIntegrityViolationException 임은 javadoc 별도 확인 필요. ⚠️ DATA_INTEGRITY_VIOLATION 코드는 registry 부재 — 실제값 DB_NULL_VIOLATION/DB_FK_VIOLATION/DB_CHECK_VIOLATION(category DATA_INTEGRITY) + 23505→DB_UNIQUE_VIOLATION(category CONFLICT) (§Audit CATEGORY_DRIFT)
D6 optimistic lock conflict 409 / deadlock·serialization 은 retryable by policy (40001, 40P01) raw/official-docs/persistence-spring-dataaccessexception-hierarchy.md#SDA-EX-C3 (TransientDataAccessException top-level 존재), #SDA-EX-C5 (optimistic locking failure 예시 명시) official-reference 40001 →ConcurrencyFailureException, 40P01 → 같은 계열의 매핑은 #SDA-EX-C7 needs-confirmation — vendor sql-error-codes.xml 확인 필요
D7 disaster recovery 는 backup 존재가 아니라 restore drill 통과를 기준. 기본 분기 1회 staging/local restore smoke (UNSUPPORTED_DECISION — cited raw 4종 중 어디에도 restore drill 권고 verbatim claim 없음. AWS / Postgres 운영 가이드 별도 raw 필요) internal-policy restore drill 주기 (분기 1회) 는 ca-tmpl 내부 운영 정책 — 외부 권위 근거 미수집. ⚠️ 본 branch In-scope 밖 — §Audit OUT_OF_BRANCH_SCOPE 후보
D8 read replica 기본 미사용. 활성화 시 max replica lag threshold + stale-read 허용 endpoint 명시 (UNSUPPORTED_DECISION — cited raw 4종에 replica lag 관련 verbatim claim 없음.persistence-r2dbc-reactive-spring 도 reactive 대안 자료이지 replica lag 자료 아님) internal-policy Postgres streaming replication 또는 vendor 별 replica lag 권고 raw 별도 수집 필요. ⚠️ 본 branch In-scope 밖 — §Audit OUT_OF_BRANCH_SCOPE 후보

Decisionized Work Items

item Decision Allowed Forbidden Required test
DB unavailable PERSISTENCE/DB_UNAVAILABLE, HTTP 503, retryable true degraded read-only mode with runbook generic 500 DB unavailable mapping
integrity violation DATA_INTEGRITY_VIOLATION, HTTP 409, retryable false domain pre-check can produce conflict raw constraint name in response integrity mapping
lock/deadlock optimistic conflict 409, deadlock/timeout retryable by policy explicit pessimistic lock use case all lock errors same code lock mapping
restore drill quarterly smoke default monthly for critical service backup with no restore evidence restore checklist
read replica primary read default replica with max lag threshold silent stale reads replica lag contract

⚠️PERSISTENCE/DB_UNAVAILABLE · DATA_INTEGRITY_VIOLATION 표기도 §Audit CATEGORY_DRIFT 정합 권고 대상 — registry 실제값은 §SQLState → Error Code Matrix.

SQLState → Error Code Matrix

본 표가 registry(ca-tmpl/docs/registries/error-codes.yaml L230354, owner_branch=feature-persistence-failure-baseline)와 1:1 정합인 권위 매핑. 카테고리는 모두 Category.java 10-value enum 의 실존 값.

SQLState Vendor category error.code retryable
08* all TRANSIENT_DEPENDENCY DB_UNAVAILABLE true
40001 Postgres/MySQL CONFLICT DB_SERIALIZATION_FAILURE true
40P01 Postgres CONFLICT DB_DEADLOCK true (backoff)
23502 Postgres DATA_INTEGRITY DB_NULL_VIOLATION false
23503 Postgres DATA_INTEGRITY DB_FK_VIOLATION false
23505 Postgres CONFLICT DB_UNIQUE_VIOLATION false (business mapping)
23514 Postgres DATA_INTEGRITY DB_CHECK_VIOLATION false
25P03 Postgres TRANSIENT_DEPENDENCY DB_IDLE_IN_TX_TIMEOUT true
57014 Postgres TRANSIENT_DEPENDENCY DB_QUERY_CANCELED false

Hikari Alert Threshold

  • pool wait p99 > 100ms 5분 지속 → P2
  • pool exhaustion (active = max) > 1분 → P1

Mapping Ownership

  • constraint name → business error 변환 owner: persistence adapter layer.
  • mapping table은 application port 인접 위치에 둔다.

구현 가이드

결정 (Decisions) 이 "무엇 을 할 것인가" 라면, 본 §는 "어디에 어떻게 구현될 것인가" 의 사전 명세. ca-tmpl 의 실제 클래스/registry 를 anchor 로 쓰되, 코드로 미확인 항목은 planned 로 표기. 계약값 SSOT: Category.java (enum) + error-codes.yaml/metrics.yaml/env-keys.yaml (registry).

1. SQLState 분류 어댑터 (adapter-persistence)

Trace: D1 + #SDA-EX-C1/C2/C3/C5; D4·D5·D6; §SQLState → Error Code Matrix 9-row. 카테고리 SSOT = shared-contract/src/main/java/dev/caskeleton/shared/error/Category.java (10-value), code/category/http/retryable SSOT = error-codes.yaml L230354 (owner_branch=feature-persistence-failure-baseline).

  • UNSUPPORTED_IMPL_DECISION: 변환기 클래스 명명·위치(예: PersistenceExceptionTranslator)와 Spring SQLErrorCodeSQLExceptionTranslator 재사용 vs 커스텀 SQLState 매핑 중 택일은 cited raw 가 권고하지 않음 — §Claims To Verify 1번(sql-error-codes.xml 대조) 해소 후 확정. trade-off: 재사용=vendor xml 의존/유지보수 적음, 커스텀=9-row 정확 제어/구현 비용.
구현 항목 위치 (ca-tmpl) 상태 근거
Category enum (10-value,PERSISTENCE 없음) shared-contract/.../error/Category.java actually-implemented grep 확인
DB_* error code 9종 (code/category/http/retryable/runbook) docs/registries/error-codes.yaml L230354 actually-implemented (registry, owner=this) registry
code→category 계약 테스트 (DB_NULL_VIOLATION→DATA_INTEGRITY, DB_UNIQUE_VIOLATION→CONFLICT, DB_SERIALIZATION_FAILURE/DB_DEADLOCK→CONFLICT) app-bootstrap/.../contract/BusinessRuleValidationContractTest.java L100105 actually-implemented (contract test) 코드
SQLState→Spring exception→error.code 런타임 변환 어댑터 adapter-persistence/.../failure/PersistenceExceptionTranslator.java (custom SQLState 매핑, 9-row + 08* prefix, fallback=empty) actually-implemented (2026-06-09, Phase C2) 코드 + PersistenceExceptionTranslatorTest (16 case)
DB_* 코드 9종 enum 표현 (carrier 반환 타입) shared-contract/.../error/OperationalError.java (DB_* 9종 추가) + PersistenceFailureException carrier actually-implemented (2026-06-09) 코드 + ErrorCodeRegistryMappingTest 가 enum↔registry http_status 정합 검증
presentation 매핑 (HTTP status·response envelope) adapter-web/.../error/GlobalExceptionHandler#handlePersistenceFailure (carrier→envelope, category-derived safe message) actually-implemented (2026-06-09) GlobalExceptionHandlerTest (3 case, leak-free)

2. OSIV off 강제 (startup)

Trace: D2 + #OSIV-AP-C1~C4.

  • UNSUPPORTED_IMPL_DECISION: spring.jpa.open-in-view=false 를 startup fail-fast assertion 으로 추가 강제할지 vs env 기본값 + Spring Boot WARN 에 의존할지 — #OSIV-AP-C4 는 WARN 만 보장(자동 disable 아님). trade-off: assertion=명시적 계약 위반 차단, default-only=설정 override 시 silent OSIV on.
구현 항목 위치 (ca-tmpl) 상태 근거
APP_DATASOURCE_OPEN_IN_VIEW env key (default false) env-keys.yaml L445 (owner=feature-env-driven-runtime-configuration) → application.yml L43 open-in-view: ${...} actually-implemented (config-level) grep 확인
OSIV off 강제 startup fail-fast assertion app-bootstrap/.../runtime/OpenInViewSafetyValidator.java (SmartInitializingSingleton, spring.jpa.open-in-view=true → boot fail) + RuntimeSafetyConfig bean actually-implemented (2026-06-09, Phase C2) 코드 + OpenInViewSafetyValidatorTest (3 case)

3. Hikari pool 관측 (metrics)

Trace: D3 + #HIKARI-POOL-C1/C5; metrics.yaml (owner 공유 feature-metrics-alerting-contract).

metric registry 상태 비고
hikaricp.connections.acquire registered (metrics.yaml L158, p99>100ms 5m→P2) actually-implemented (registry)
hikaricp.connections.usage registered (L178)
hikaricp.connections.active registered (L194, exhaustion 1m→P1)
  • UNSUPPORTED_THRESHOLD: 100ms/5분/1분 수치는 HikariCP 비권고 내부 SLO (D3 Open Risk).
  • METRIC_NAME_DRIFT: D3 이 hikaricp.connections.pending 인용했으나 registry 는 .usage/.active 사용 → §Audit.

4. datasource/pool env 계약

Trace: In-scope "datasource/pool/timeout/connection exhaustion log field" + env-keys.yaml (owner 공유 feature-env-driven-runtime-configuration).

APP_DATASOURCE_URL/_USERNAME/_PASSWORD/_POOL_MAX_SIZE/_POOL_MIN_IDLE/_CONNECTION_TIMEOUT registered (env-keys.yaml L307373) → actually-implemented (registry). SQL/parameter 로그 금지(In-scope)는 APP_DATASOURCE_SHOW_SQL default false (env-keys.yaml L417 → application.yml L41 show-sql: ${...}, _FORMAT_SQL L431 동반) 로 config-level actually-implemented; 위반 시 실패하는 contract test 는 planned (§테스트 계약). 위 env 키 owner 는 모두 raw/branch-notes/feature-env-driven-runtime-configuration (delegated).

엣지·실패·의존

R4(깊이 게이트) 캡처용. 정상 경로 외 실패/엣지/다른 계약 의존을 미리 열거.

  • 실패·엣지 경로:
    • 9-row 밖 미지의 SQLState: fallback 은 INTERNAL category + generic 메시지, raw exception/SQL 비노출. (planned — translator 부재)
    • pool acquire timeout: connection 미확보 → DB_UNAVAILABLE(503, retryable) 분류 + hikaricp.connections.acquire{outcome=timeout} 증가. pool exhaustion(active=max) 1분 → P1.
    • OSIV off + lazy access: presentation 에서 LazyInitializationException 발생 시 D2 계약 위반. fetch graph(@EntityGraph/JOIN FETCH/DTO projection) 누락 → N+1 (§Claims To Verify 5번).
    • 23505 unique: persistence adapter 가 business conflict(CONFLICT/DB_UNIQUE_VIOLATION)로 변환, constraint name 응답 비노출.
    • transient vs integrity 혼동: deadlock/serialization(retryable CONFLICT)과 integrity(non-retryable DATA_INTEGRITY)가 같은 code 로 뭉개지면 실패(§테스트 계약).
  • 다른 계약 의존:

테스트 계약

  • JPA exception class name이 API response에 나오면 실패.
  • SQL/parameter가 log에 남으면 실패.
  • DB unavailable은 retryable dependency failure로 분류되어야 함.
  • integrity violation과 transient lock failure가 같은 code로 뭉개지면 실패.
  • read replica lag threshold 없이 replica read가 활성화되면 실패.

검증해야 할 주장

공식 문서 인용이 결정의 근거이지만, ca-tmpl 자체 구현에서의 동작은 별도 검증이 필요한 주장.

Claim Why uncertain How to verify Status
ca-tmpl 9-row SQLState matrix 의 vendor 매핑 (08*→DataAccessResourceFailureException, 40001→ConcurrencyFailureException, 23*→DataIntegrityViolationException, 23505→DuplicateKeyException) 이 Spring sql-error-codes.xml 의 PostgreSQL/MySQL section 과 일치 #SDA-EX-C7 needs-confirmation — Spring Framework Reference dao.html landing 에 verbatim 미등장 SQLErrorCodeSQLExceptionTranslator Javadoc + sql-error-codes.xml source 를 별도 raw 로 수집 후 1:1 대조 needs-confirmation
spring.jpa.open-in-view=false 가 ca-tmpl startup assertion 으로 강제됨 OSIV-AP-C4 는 Spring Boot 가 WARN 만 출력함을 보장하며 자동 disable 은 안 함 application.yml + JpaBaseConfiguration startup assertion 코드 검증, integration test 에서 property 값 false 단언 planned
Vlad Mihalcea 의 OSIV anti-pattern 권위 있는 verbatim 재확인 + Hibernate ORM User Guide 의 OSIV 관련 직접 인용 확보 OSIV-AP-C1~C3 의 strength 가 engineering-blog 으로 제한, Hibernate 공식 verbatim 미확보 vladmihalcea.com 재시도 (다음 세션) + hibernate.org User Guide §Transactions WebFetch 재시도 needs-confirmation
Hikari pool wait p99 > 100ms 5분 / pool exhaustion > 1m threshold 가 ca-tmpl SLA 와 일치하며 측정 가능 HIKARI-POOL-C1~C5 는 axiom + MBean attribute 존재만 보장. 정확한 SLO 수치는 HikariCP 가 정의하지 않음 k6 부하 테스트로 p99 wait time 측정 +hikaricp.connections.acquire/.usage Micrometer metric 노출 확인 planned
Micrometer metric name (registry 는 hikaricp.connections.acquire/.usage/.active — 노트 D3 의 .pending 과 불일치) 의 정확한 정의 HIKARI-POOL-C6 needs-confirmation + registry drift — HikariCP wiki 본문에는 metric 명 직접 없음 Spring Boot Actuator / Micrometer reference 의 HikariCP metric 섹션 raw 수집 후 metric 명 확정 + D3 정합 needs-confirmation
OSIV off 상태에서 service layer 가 fetch graph (@EntityGraph/JOIN FETCH/DTO projection) 를 일관성 있게 적용 OSIV-AP-C1~C3 의 권고는 도구 사용을 강제하지 않음 ArchUnit 또는 Hibernate statistics 로 N+1 발생 시 fail 하는 contract test planned
disaster recovery restore drill 의 효과성 (D7 의 운영 정책) D7 은 cited raw 외부 근거 없음 — 내부 정책 분기 1회 staging restore smoke test 실행 결과 (RTO / RPO 측정) planned
read replica 도입 시 max lag threshold 의 적절한 값 (D8) D8 은 cited raw 외부 근거 없음 — 내부 정책 Postgres streaming replication 모니터링 + 도메인별 stale-read SLA 정의 planned

Audit & Findings

§2 ca-tmpl ground truth 대조에서 발견한 drift / scope 이슈. 사용자 결정 영역은 자동 rewrite 하지 않고 정합 권고만 남긴다 (CLAUDE.md §11, branch-spec §2).

  • CATEGORY_DRIFT (🔴 정합 권고): §Decision Evidence Map D4 PERSISTENCE/DB_UNAVAILABLE · D5 DATA_INTEGRITY_VIOLATION, §Decisionized Work Items 동일 표기가 코드/registry SSOT 와 어긋남.
    • Category.java 10-value enum = {VALIDATION, AUTH, AUTHZ, NOT_FOUND, CONFLICT, RATE_LIMIT, TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY, DATA_INTEGRITY, INTERNAL} — PERSISTENCE 없음.
    • error-codes.yaml 실제값: DB_UNAVAILABLETRANSIENT_DEPENDENCY(503); integrity→DB_NULL_VIOLATION/DB_FK_VIOLATION/DB_CHECK_VIOLATION(DATA_INTEGRITY,409); 23505→DB_UNIQUE_VIOLATION(CONFLICT,409). 코드 DATA_INTEGRITY_VIOLATION 은 registry 부재.
    • §SQLState → Error Code Matrix 는 이미 정합. drift 전파 경로: project-note §6 → governing canonical data-layer-persistence-cache-outbound.md L51/L89(PERSISTENCE / CONFLICT / TRANSIENT_DEPENDENCY 3-category) → 본 노트 D4/D5. error-codes.yaml L580 주석에도 stale persistence→PERSISTENCE/CONFLICT 잔존.
    • 권고: D4/D5 + Decisionized Work Items 의 PERSISTENCE/·DATA_INTEGRITY_VIOLATION 표기 + governing canonical 의 3-category 문구를 registry 값으로 정합. (사용자 결정 영역 → 본 명령은 정합 권고만, 자동 rewrite 보류.)
  • METRIC_NAME_DRIFT (🟡): D3 이 hikaricp.connections.pending 인용 → metrics.yaml.acquire/.usage/.active 사용(.pending 미등록). 권고: D3·§Claims metric 명을 registry 와 정합.
  • OUT_OF_BRANCH_SCOPE 후보 (D7, D8 — deferred): restore drill cadence(D7) + read replica lag(D8) 는 본 branch In-scope("DataAccessException 분류 / JPA mapping / pool / OSIV") 밖. 둘 다 UNSUPPORTED_DECISION(내부 RTO/RPO·SLA 정책, 외부 권위 근거 없음). 자동조사 보류 사유: 내부 운영 SLA 는 외부 공식 문서가 권위적으로 결정하지 않음(회사 블로그→공식 승격 금지). 추적 (2026-06-09): 부모 raw/project-notes/ca-skeleton-operational-contract §11 Persistence "추후 branch 분해 대상" 에 deferred 로 기록됨 → 착수 시 feature-disaster-recovery-restore-drill / feature-read-replica-lag-contract 로 전개.
  • IMPL_STATUS reconciliation (2026-06-09 갱신): SQLState→exception 런타임 변환 어댑터 PersistenceExceptionTranslator 가 adapter-persistence failure/구현됨 → 런타임 translator actually-implemented (Phase C2). DB_* 9 코드는 registry 에서 OperationalError enum 으로도 승격되어 ErrorCodeRegistryMappingTest 가 enum↔registry http_status 정합을 강제. presentation 매핑 (GlobalExceptionHandler#handlePersistenceFailure) + OSIV fail-fast (OpenInViewSafetyValidator) + SQL-log 금지 contract test (SqlLoggingForbiddenContractTest) 도 actually-implemented. 잔여 planned: runbook runbook://db/* 파일 부재(docs/runbooks/), N+1 fetch-graph ArchUnit, k6 pool-wait 부하측정, D7/D8(OUT_OF_BRANCH_SCOPE).

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

governing: wiki/projects/ca-tmpl/data-layer-persistence-cache-outbound (§Persistence). /coverage 가 최종 갱신 — 아래는 branch-spec 1차 seed.

관심사 상태 owner 심각도 근거
SQLState 9-row classifier → DataAccessException hierarchy 매핑 covered-here D1, D4, D5, D6 + §SQLState Matrix
Hibernate OSIV off baseline covered-here D2
HikariCP pool wait/exhaustion alert covered-here feature-metrics-alerting-contract (metric 공동) D3 + §Audit 위임
SQL/parameter 로그 금지 covered-here feature-env-driven-runtime-configuration (APP_DATASOURCE_SHOW_SQL/_FORMAT_SQL 소유) policy 본 branch; config show-sql=false default. contract test planned
datasource/pool/timeout/connection exhaustion log field covered-here feature-env-driven-runtime-configuration (APP_DATASOURCE_URL/_USERNAME/_PASSWORD/_POOL_MAX_SIZE/_POOL_MIN_IDLE/_CONNECTION_TIMEOUT/_OPEN_IN_VIEW 소유) §구현 가이드 4 항목별 위임 명시
read replica lag threshold delegated (제안) [[raw/branch-notes/feature-read-replica-*]] 🟡 Should-fix D8 — §Audit OUT_OF_BRANCH_SCOPE (위임 브랜치 미생성)
disaster recovery restore drill delegated (제안) [[raw/branch-notes/feature-disaster-recovery-*]] 🟡 Should-fix D7 — §Audit OUT_OF_BRANCH_SCOPE (위임 브랜치 미생성)

마주친 문제

  • (Phase C2 2026-06-09) 없음 — TDD 로 각 레이어 red→green, ./gradlew check 전체 통과. IDE diagnostics 의 "DB_* cannot be resolved" 는 shared-contract 미재컴파일로 인한 stale 신호였고 gradle 빌드에서는 정상 해소.

묶음

Phase C2 실 코드 작성 단계 (2026-06-09) 진입 — derived 후보 아래 정리. errors 노트는 불필요(클린 사이클).

오류 기록 (본 feature 작업 중 발생)

  • (없음 — TDD 사이클이 깔끔하게 통과, 별도 raw/errors 노트 불필요)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • "adapter-web 이 adapter-persistence 를 의존할 수 없는데 persistence 의 DataAccessException 분류 결과를 어떻게 presentation 까지 leak 없이 전달하는가?" → shared-contract 의 framework-neutral carrier(PersistenceFailureException) + OperationalError DB_* 코드, web 은 category 별 고정 safe message. (raw/interviews 승급 후보 — Phase D)
  • "JPA 예외를 SQLState 로 분류할 때 Spring SQLErrorCodeSQLExceptionTranslator(vendor xml) 재사용 vs 커스텀 매핑 trade-off?" → 9-row 정확 제어 위해 커스텀 채택, SQLState 문자열 기반이라 Spring subtype 이 coarse 해도(23505/23502 둘 다 DataIntegrityViolationException) CONFLICT/DATA_INTEGRITY 로 정확 분기.
  • "OSIV off 를 Spring Boot WARN 에만 의존하지 않고 startup fail-fast 로 강제한 이유?" → WARN 은 deploy 를 막지 못하므로 SmartInitializingSingleton hard stop.

Blog topics

  • (별도 topic 없음 — branch note + interview prep 로 충분)

관련 일일 노트

  • 2026-06-09: Phase C2 실 구현 — translator/carrier/enum/web-handler/OSIV-validator/contract-test 6종 추가, ./gradlew check 통과.

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목:
    • locally-verified 항목:
    • prod-verified 항목:
  • 추출하지 않을 항목 (planned / documented-only / abandoned):