46 KiB
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 |
|
|
|
2026-05-21 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-006 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-006 |
|
1 | de3aae90785a1d43f67d6b179b3372223b788348472b4a1667f38ec217f73eb0 |
branch: feature-persistence-failure-baseline
Layer:
raw/branch-notes/— DB/JPA 실패 분류와 persistence adapter 실패 계약을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
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를 정리.
- 채택 결정의 공식 근거:
- raw/official-docs/persistence-spring-dataaccessexception-hierarchy — SQLState 9-row matrix가 Spring DAO hierarchy(
TransientDataAccessException/NonTransientDataAccessException/RecoverableDataAccessException)와 정합인 근거. - raw/official-docs/persistence-osiv-antipattern-hibernate-vladmihalcea — OSIV off 기본값의 외부 근거. Hibernate 권위 + Spring Boot WARN 메시지.
- raw/official-docs/persistence-hikaricp-pool-sizing-wiki — Hikari alert threshold (pool wait p99 > 100ms 5분 / exhaustion > 1분) 의 metric 출처.
- raw/official-docs/persistence-spring-dataaccessexception-hierarchy — SQLState 9-row matrix가 Spring DAO hierarchy(
- 대안 비교:
- raw/official-docs/persistence-r2dbc-reactive-spring — R2DBC reactive 대안. JPA blocking baseline을 택한 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.java10-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-C7 이 needs-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-C6 이 needs-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-confirmation — DuplicateKeyException 의 직접 부모가 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.yamlL230–354, owner_branch=feature-persistence-failure-baseline)와 1:1 정합인 권위 매핑. 카테고리는 모두Category.java10-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.yamlL230–354 (owner_branch=feature-persistence-failure-baseline).
- UNSUPPORTED_IMPL_DECISION: 변환기 클래스 명명·위치(예:
PersistenceExceptionTranslator)와 SpringSQLErrorCodeSQLExceptionTranslator재사용 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 L230–354 |
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 L100–105 |
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 L307–373) → 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 은
INTERNALcategory + 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 로 뭉개지면 실패(§테스트 계약).
- 9-row 밖 미지의 SQLState: fallback 은
- 다른 계약 의존:
- raw/branch-notes/feature-operational-error-observability-foundation 의
Category.java10-value enum(D10) — 본 branch 9 코드가 이 enum 으로 분류. enum 변경 시 본 매핑 영향. - raw/branch-notes/feature-metrics-alerting-contract — hikari metric 명/threshold 공동 소유. metric 명 변경 시 D3 영향.
- raw/branch-notes/feature-env-driven-runtime-configuration —
APP_DATASOURCE_*env 키 공동 소유. - adapter-web
GlobalExceptionHandler— presentation 매핑 소유(persistence 가 category-correct code 제공, web 이 HTTP envelope 변환).
- raw/branch-notes/feature-operational-error-observability-foundation 의
테스트 계약
- 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· D5DATA_INTEGRITY_VIOLATION, §Decisionized Work Items 동일 표기가 코드/registry SSOT 와 어긋남.Category.java10-value enum = {VALIDATION, AUTH, AUTHZ, NOT_FOUND, CONFLICT, RATE_LIMIT, TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY, DATA_INTEGRITY, INTERNAL} —PERSISTENCE없음.error-codes.yaml실제값:DB_UNAVAILABLE→TRANSIENT_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.mdL51/L89(PERSISTENCE / CONFLICT / TRANSIENT_DEPENDENCY3-category) → 본 노트 D4/D5.error-codes.yamlL580 주석에도 stalepersistence→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-persistencefailure/에 구현됨 → 런타임 translatoractually-implemented(Phase C2). DB_* 9 코드는 registry 에서OperationalErrorenum 으로도 승격되어ErrorCodeRegistryMappingTest가 enum↔registry http_status 정합을 강제. presentation 매핑 (GlobalExceptionHandler#handlePersistenceFailure) + OSIV fail-fast (OpenInViewSafetyValidator) + SQL-log 금지 contract test (SqlLoggingForbiddenContractTest) 도actually-implemented. 잔여planned: runbookrunbook://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 빌드에서는 정상 해소.
묶음
- raw/official-docs/persistence-hikaricp-pool-sizing-wiki
- raw/official-docs/persistence-osiv-antipattern-hibernate-vladmihalcea
- raw/official-docs/persistence-r2dbc-reactive-spring
- raw/official-docs/persistence-spring-dataaccessexception-hierarchy
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) +OperationalErrorDB_* 코드, 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 를 막지 못하므로
SmartInitializingSingletonhard 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):