55 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-distributed-lock-contract | branch-note | raw | feature-distributed-lock-contract |
|
|
|
2026-06-12 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-052 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-052 |
|
|
1 | 174d92e4290cde20c264638b526e3447c27c19eca0eab92d8023a03a66627754 |
branch: feature-distributed-lock-contract
Layer:
raw/branch-notes/— 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는/ingest로wiki/projects/에 추출. 원본은 raw에 영구 보관.status_label:in-progress|review|merged|abandoned
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note §29.E 신규 branch 권고 #9 (
feature-distributed-lock-contract— "Redisson / DB advisory lock + 트랜잭션 commit 정합") 영역의 결정/근거/금지 사항을 정제한다.
형제 branch (같은 부모의 다른 자식 — lock 인접 영역):
- raw/branch-notes/feature-background-job-async-contract — scheduler/outbox 의 lock 적용처 owner (D3). 본 branch 의
distributedLockProviderbean 을 consume. - raw/branch-notes/feature-cache-consistency-contract — cache stampede lock (Redisson RLock) owner (D3/D4)
- raw/branch-notes/feature-env-driven-runtime-configuration —
APP_MULTI_INSTANCE_ENABLEDflag +StartupSafetyValidatorpresence 강제 owner (D8)
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: lock provider·lease·transaction commit ordering과 failure test가 명시된다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-SCHEDULER-LOCK-001@1 |
single-instance가 default이며 multi-instance scheduler/outbox는 DB advisory lock을 사용한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TRANSACTION-001@1 |
application은 Spring transaction annotation 대신 TransactionPort 또는 TransactionalUseCaseRunner를 사용한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1 |
database는 PostgreSQL 16 단일 stack이다 | 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 |
|---|
목표
multi-instance 배포(APP_MULTI_INSTANCE_ENABLED=true) 시 ca-tmpl StartupSafetyValidator 가 presence 를 강제하는 5개 instance-coordination bean 중 distributedLockProvider 만 제공 결정의 owner branch 가 없었다 — raw/branch-notes/feature-background-job-async-contract §Audit A7 LOCK_BEAN_OWNER_UNRESOLVED (2026-06-11 coverage-auditor): ca-tmpl 코드 주석은 runtime-health 를 가리키나 그 노트는 "consume only" 자기 서술, 어느 branch 도 bean 을 누가 어떤 메커니즘으로 제공하는지 결정하지 않음.
본 branch 가 그 owner 가 되어 다음을 결정한다: general-purpose 분산 락 제공 계약 — 메커니즘 선택(DB 기반 vs Redis 기반), port 추상화, 트랜잭션 commit 정합(lock 해제 vs DB commit 순서), lease/timeout 계약, 실패 매핑, 정적 강제 요구.
- 이슈: parent project §29.E row #9 / background-job §Audit A7
- PR: (없음 — 계약 단계)
범위
포함 범위
distributedLockProviderbean 계약의 SSOT ownership (A7 해소) — bean 이름은 ca-tmplStartupSafetyValidator.REQUIRED_MULTI_INSTANCE_BEANS기존 값 재사용- general-purpose 분산 락 메커니즘 선택 (PG advisory lock / ShedLock / Spring Integration LockRegistry / Redisson 비교)
- port 추상화 — domain/application 층에서 lock client 직접 사용 금지
- 트랜잭션 commit 정합 — lock 해제와 DB commit 의 순서 불변식
- lease / timeout 획득 계약 — 무한 blocking 금지, 잔존 lock 자동 만료
- lock 획득 실패의 error code / metric 신규 제안 (registry-governance 절차 경유)
- 정적 강제(ArchUnit) 요구사항 등록 — rule 호스팅은
feature-architecture-enforcement-rules에 위임 - multi-instance contract test 계약 (bean presence + 정합)
제외 범위
의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거.
- scheduler/outbox 의 lock 적용 정책 — raw/branch-notes/feature-background-job-async-contract D3 소유 (본 branch 는 provider 만 공급)
- cache stampede 방지 lock — raw/branch-notes/feature-cache-consistency-contract D3/D4 소유 (Redisson RLock +
CACHE_STAMPEDE_LOCK_TIMEOUT) APP_MULTI_INSTANCE_ENABLEDflag 정의와StartupSafetyValidator집행 — raw/branch-notes/feature-env-driven-runtime-configuration D8 소유- distributed rate limiter (
distributedRateLimiterbean) —feature-rate-limit-idempotency-contract영역 - migration runner lock (
migrationStartupRunnerbean) —feature-migration-startup-contract영역 - fencing token 도입 — 미도입 결정 (D6). correctness 는 DB 제약으로 보장
- tenant 별 lock namespace —
feature-tenant-context-policy활성화 전까지 미정의
근거 (필수, 최소 1개+)
이 branch의 구현·설계 결정의 근거가 되는 외부 자료. 공식 문서·대기업 기술 블로그·강의 등 raw 자료를 인용. 같은 자료가 여러 결정의 근거면 결정 표시와 함께 여러 번 등장 가능.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/lock-postgres-advisory-locks | ca-tmpl distributedLockProvider 의 default 메커니즘으로 PostgreSQL advisory lock 검토 — session-level vs transaction-level 해제 시맨틱(PG-ADV-C2, PG-ADV-C3)이 "lock 해제 vs DB commit 순서 정합" 결정(D4)의 1차 근거 + session-level 배제(D3)·non-blocking try 변형(D5) 근거 |
| raw/official-docs/lock-spring-integration-lock-registry | ca-tmpl distributedLockPort 추상화의 reference 구현 후보로서 Spring Integration LockRegistry/JdbcLockRegistry 평가 — java.util.concurrent.locks.Lock 호환 추상화 + JDBC/Redis/Zookeeper/DynamoDB provider 교체 가능성이 "port 추상화 + provider 교체" 결정의 근거 (SI-LOCK-C1, SI-LOCK-C2, SI-LOCK-C3) + lease 갱신/만료 예외 계약(D5 — SI-LOCK-C4, SI-LOCK-C5) |
| raw/official-docs/lock-shedlock-readme | ShedLock 평가(D3 배제) — 용도 정의 "scheduled tasks at most once"(SHEDLOCK-C1) + lockAtMostFor/lockAtLeastFor lease 시맨틱(D5 참조 원리 — SHEDLOCK-C3, SHEDLOCK-C4) + clock 동기화 가정(D6 한계 방증 — SHEDLOCK-C5) |
| raw/official-docs/lock-shedlock-issue-899-non-scheduler-use | ShedLock 을 general-purpose lock 으로 쓰지 않는 결정(D3)의 직접 근거 — maintainer 가 generic lock 공식 선언 거부(SHEDLOCK-899-C1) + 획득 실패 시 skip(대기 없음) 시맨틱이라 blocking 계약과 불일치(SHEDLOCK-899-C2) |
| raw/company-tech-blogs/lock-subskribe-advisory-lock-distributed-consensus | PostgreSQL advisory lock 의 production 사용 사례 — 추가 인프라 없이 DB 만으로 distributed mutual exclusion 을 달성한 사례(SUBSKRIBE-LOCK-C1) + "optimistic variant(try-lock) 만 사용, pessimistic blocking 은 비권장" 운영 교훈(SUBSKRIBE-LOCK-C2)이 distributedLockProvider 메커니즘 비교의 사례 근거 (공식 best practice 아님 — 사례/관점으로만 취급) |
| raw/official-docs/cache-redisson-rlock-vs-setnx | Redis 기반 대안(D3 의 Redis-활성 분기) — Redisson RLock 의 j.u.c.Lock 호환 + watchdog(LOCK-C3, needs-confirmation), TTL 의 deadlock 회피 역할(D5 — LOCK-C2), efficiency vs correctness lock 분리(D6 — LOCK-C4). cache branch 와 공유 raw |
근거 자료가 raw에 아직 없다면 먼저 raw-source-template 또는 lecture-note-template 으로 raw에 등록한 뒤 여기서 링크.
TODO
각 항목 옆에 증거 등급 표기: actually-implemented | locally-verified | prod-verified | documented-only | planned | needs-confirmation
OperationalError.LOCK_ACQUISITION_TIMEOUTenum 상수 추가 (shared-contract) — 등급:actually-implemented/locally-verified(D7, 2026-06-13 이전 세션)- lock port 인터페이스 3종 정의 (application-core):
DistributedLockPort,DistributedLock,LockAcquisitionTimeoutException— 등급:actually-implemented/locally-verified(D2/D4/D5/D6, 2026-06-13) LockAcquisitionTimeoutExceptionTest+DistributedLockPortContractTest(application-core) — 등급:locally-verified(10/10 pass, 2026-06-13)LockSettings@ConfigurationProperties("ca-skeleton.lock")record (adapter-persistence) — 등급:actually-implemented/locally-verified(D3/D5, 2026-06-13)LockRegistryDistributedLockAdapter implements DistributedLockPort(adapter-persistence) — 등급:actually-implemented/locally-verified(D3/D4/D5, 2026-06-13)DistributedLockPersistenceConfigSpring wiring (adapter-persistence) — in-process (@Primary, matchIfMissing) + JDBC conditional beans — 등급:actually-implemented/locally-verified(D3, 2026-06-13)V4__int_lock.sqlFlyway migration (adapter-persistence/db/migration) — SI 6.5 verbatim PostgreSQL DDL — 등급:actually-implemented(D3/D4, 2026-06-13; Testcontainers run-verify is app-bootstrap scope)LockRegistryDistributedLockAdapterTest5종 단위 테스트 (DefaultLockRegistry, no Spring context) — 등급:locally-verified(5/5 PASS, 2026-06-13)lock.acquisitionmetric decoratorMeteredDistributedLockPort(app-bootstrap) — 등급:actually-implemented/locally-verified(D7, 2026-06-13)DistributedLockConfig@ConditionalOnProperty bean wiring (app-bootstrap) — distributedLockProvider@Primary, multi-instance=true 시만 활성 — 등급:actually-implemented/locally-verified(D1/D3, 2026-06-13)- ca-tmpl
StartupSafetyValidator의distributedLockProvider주석 owner 표기 갱신 (runtime-health → 본 branch) — 등급:actually-implemented/locally-verified(§Audit A1, 2026-06-13) ca-skeleton.lock: wait-time: 3s / lease-ttl: 30sapplication.yml 기본값 배선 (app-bootstrap) — 등급:actually-implemented(D5, 2026-06-13)MeteredDistributedLockPortTest6종 단위 테스트 (app-bootstrap) — acquired/timeout/error + no-registry no-op — 등급:locally-verified(6/6 PASS, 2026-06-13)LockAcquisitionTimeoutClassificationContractTest5종 계약 테스트 (app-bootstrap) — enum SSOT + skip-not-pass registry/metrics — 등급:locally-verified(5/5 PASS, 2026-06-13)DistributedLockProviderContractTest4종 계약 테스트 (app-bootstrap) — D1 bean presence/absence + D3 mutual exclusion + D5 lease expiry (Testcontainers PG) — 등급:locally-verified(4/4 PASS, 2026-06-13)- Quality-review remediation (2026-06-13): SI-LOCK-C5 lease-expiry 처리 + D5 테스트 poll 개선 — 등급:
actually-implemented/locally-verifiedMeteredDistributedLockPort:LOCK_LEASE_EXPIRED="lock.lease.expired"상수 +closeHandlingLeaseExpiry()+incrementLeaseExpired()추가.tryAcquire는 wrapping lambda 반환.MeteredDistributedLockPortTest: 기존 identity(isSameAs) 어설션 제거(wrapping lambda로 변경됨) + 신규 4종:LOCK_LEASE_EXPIRED상수 pinning + CME 삼킴 + non-CME 전파 + no-registry CME 삼킴 → 10/10 PASSDistributedLockProviderContractTest: D5 sleep-then-single 취약점 → bounded poll 수정 + SI-LOCK-C5 2종 신규(raw CME 증명 + metered 삼킴+카운터) + intentional discard@SuppressWarnings("unused")+ 총 6/6 PASS
- ArchUnit rule 요구사항을 raw/branch-notes/feature-architecture-enforcement-rules 에 등록 — 등급:
planned(D8) - background-job §테스트 계약의 ShedLock
LockProviderFQCN 전파 알림 — 등급:planned(§Audit A4)
진행 중 메모
- 2026-06-12: /branch-spec 자동조사 — wiki-decision-researcher 1회(대안 5개 비교) + wiki-source-summarizer 5회(신규 raw 5건). 비교 매트릭스 축: 인프라 의존 / 트랜잭션 commit 정합 / lease·timeout / reentrancy / Spring 생태계 통합 / 운영 복잡도.
- 대기업(국내) production 사례 공백 — Subskribe(미국 SaaS)·FireHydrant 영어권 사례만 확보. 토스/카카오/네이버 advisory-lock 사례는 검색 미발견 (추가 조사 후보).
- 2026-06-13 Layer 1 (shared-contract) 완료 (이전 세션):
OperationalError.LOCK_ACQUISITION_TIMEOUT(Category.CONFLICT, 409, true)추가. D7 §Decision Evidence Map row 상태 갱신 미완이었음 — 본 세션에서 TODO 행actually-implemented로 정정. - 2026-06-13 Layer 2 (application-core) 완료 (ca-implementer): 3종 타입 신설 + 계약 테스트 10/10 통과.
dev.caskeleton.application.lock.DistributedLockPort— D2/D4/D5/D6 javadoc 포함 (canonical usage + forbidden inverse)dev.caskeleton.application.lock.DistributedLock extends AutoCloseable—close()no checked exceptiondev.caskeleton.application.lock.LockAcquisitionTimeoutException(final, RuntimeException) —key(),waitTime(),errorCode()→LOCK_ACQUISITION_TIMEOUT- TDD:
compileTestJava실패(29 error) 확인 후 구현 →./gradlew :application-core:test10/10 PASS - 테스트 수정 1건:
message_contains_waitTime—Duration.ofMillis(500).toString()="PT0.5S"(ISO-8601), "500" 포함 아님. 어설션을contains(waitTime.toString())로 정정. - build.gradle 무수정 확인 (
:shared-contract이미implementation의존) - Spring/JPA import 0 — 순수
java.time+shared.error만 사용
- 2026-06-13 Layer 3 (adapter-persistence) 완료 (ca-implementer): LockSettings + adapter + Config + V4 migration.
dev.caskeleton.adapter.persistence.lock.LockSettings—@Validated @ConfigurationProperties("ca-skeleton.lock")record. compact-ctor: null→default(waitTime=3s, leaseTtl=30s), non-positive →IllegalArgumentException, cross-field leaseTtl < waitTime →IllegalArgumentException.dev.caskeleton.adapter.persistence.lock.LockRegistryDistributedLockAdapter implements DistributedLockPort— wraps any SILockRegistry.tryAcquire: leaseTtl > configuredTtl guard →IllegalArgumentException;l.tryLock(waitTime.toMillis(), MILLISECONDS); InterruptedException → restore interrupt + throw timeout; returnsl::unlocklambda.dev.caskeleton.adapter.persistence.lock.DistributedLockPersistenceConfig—@Configuration(proxyBeanMethods=false). in-process@Primary @ConditionalOnProperty(... matchIfMissing=true); JDBC 3 beans@ConditionalOnProperty(havingValue="true"). SI types confined to adapter-persistence (implementation dep — invisible to app-bootstrap/application at compile time).jdbcDistributedLockintentionally NOT@Primary— app-bootstrap wraps in metrics decorator (cross-module contract).V4__int_lock.sql— SI 6.5 verbatim PostgreSQL DDL with header comment (D3/D4 + TTL note). V1/V3 present, V2 absent; V4 is correct next.LockRegistryDistributedLockAdapterTest— 5 unit tests overDefaultLockRegistry(no Spring context, no DB). TDD: red(compileTestJava7 errors confirmed) → green(5/5 PASS). Key test: concurrent timeout via CountDownLatch (deterministic, no sleep).- SI 6.5 TTL finding:
DefaultLockRepository.setTimeToLive(int ms)is repository-level; per-locklock(Duration)API does not exist in 6.5 (SI 7.0+). configuredTtl guard in adapter prevents callers from overpromising per-call lease. verifyCleanArchitectureDependenciesnot run (build.gradle not modified);./gradlew :adapter-persistence:testfull suite PASS.
- 2026-06-13 Layer 4 (app-bootstrap) 완료 (ca-implementer): MeteredDistributedLockPort + DistributedLockConfig + StartupSafetyValidator 주석 + application.yml lock 기본값 + 3종 테스트.
dev.caskeleton.bootstrap.lock.MeteredDistributedLockPort implements DistributedLockPort—ObjectProvider<MeterRegistry>no-op 패턴(BackgroundJobMetrics동일). 상수:LOCK_ACQUISITION="lock.acquisition",TAG_OUTCOME="outcome",OUTCOME_ACQUIRED/TIMEOUT/ERROR. catchLockAcquisitionTimeoutException→TIMEOUT, catch otherRuntimeException→ERROR, success→ACQUIRED;increment()swallows meter errors.dev.caskeleton.bootstrap.lock.DistributedLockConfig—@Configuration(proxyBeanMethods=false).@Bean("distributedLockProvider") @Primary @ConditionalOnProperty(prefix="ca-skeleton.runtime", name="multi-instance-enabled", havingValue="true").@Qualifier("jdbcDistributedLock")주입 →MeteredDistributedLockPort래핑.StartupSafetyValidator.java주석 수정 —distributedLockProvider행 코멘트를 runtime-health →feature-distributed-lock-contract (D1/D3 — JdbcLockRegistry distributed lock; in-process default when single-instance)로 갱신. §Audit A1 해소.application.ymllock 블록 추가 —ca-skeleton.lock: wait-time: 3s / lease-ttl: 30s. 코드 기본값과 일치(APP_* env 키 미등록 — env-driven-runtime-configuration 소관).ca-skeleton.runtime:블록 아래.app-bootstrap/build.gradle—testImplementation 'org.springframework.integration:spring-integration-jdbc'추가. 이유: SI 타입(DefaultLockRepository/JdbcLockRegistry)이 adapter-persistenceimplementation의존이라 app-bootstrap 컴파일 classpath 에 미노출.DistributedLockProviderContractTest가 두 개의 독립 registry 인스턴스(두 앱 인스턴스 시뮬레이션)를 직접 빌드하는 데 필요.- TDD:
MeteredDistributedLockPortTest6개 먼저 작성(compileTestJava 실패) → 구현 → 6/6 PASS.LockAcquisitionTimeoutClassificationContractTest5개 → 5/5 PASS.DistributedLockProviderContractTest4개 → 4/4 PASS. - 핵심 발견:
DefaultLockRepositorySpring 컨텍스트 외부 초기화 —readCommittedTransactionTemplate은InitializingBean.afterPropertiesSet()이 아니라SmartInitializingSingleton.afterSingletonsInstantiated()에서 생성된다. Spring 컨텍스트 없이 쓸 때는setTransactionManager()→afterPropertiesSet()→afterSingletonsInstantiated()→start()순서를 명시 호출해야 한다. 누락 시 D3/D5 테스트에서CannotAcquireLockException(NullPointerException: readCommittedTransactionTemplate)발생. - 사전 기존 ArchUnit 실패:
outbound_adapter_method_returns_only_domain_or_primitives—OutboundHttpSettings.retry()/.circuitBreaker()가 adapter.outbound 내 nested record 반환. commits d702572/2613561/907dfad (이 task 이전) 에서 발생. 본 task 범위 외. verifyCleanArchitectureDependencies verifyEnvKeysPASS (build.gradle 수정 → verifyCleanArchitectureDependencies 필수).app-bootstrap전체 suite: 274 tests, 1 pre-existing failure.
- 2026-06-13 Quality-review remediation (ca-implementer): Finding 1 (Critical SI-LOCK-C5) + Finding 2 (Important — D5 flaky sleep + SI-LOCK-C5 coverage) + Minor #4 해소.
MeteredDistributedLockPort변경:java.util.ConcurrentModificationExceptionimport (JDK — no SI import in main src).tryAcquire가() -> closeHandlingLeaseExpiry(key, handle)wrapping lambda 반환.closeHandlingLeaseExpiry: CME 만 catch → log.warn +incrementLeaseExpired(); 다른 예외 전파.incrementLeaseExpired(): 동일 null-guard + try-catch-log-and-swallow 패턴.LOCK_LEASE_EXPIRED="lock.lease.expired"상수 신설.MeteredDistributedLockPortTest변경: 기존 2개 테스트의isSameAs(expectedHandle)어설션 → wrapping lambda 인식하도록isNotNull() + close() 정상검증으로 교체. 신규 4종: ①lock_lease_expired_constant_matches_registry_name(pinning), ②close_swallows_CME_and_increments_lease_expired_counter, ③close_propagates_non_CME_exception_unchanged, ④close_swallows_CME_when_no_registry_is_present. → 10/10 PASS.DistributedLockProviderContractTest변경: D5 test —Thread.sleep(+500)후 단일 시도 → 수면 후 bounded poll(최대 shortTtl×4, 200ms 간격). intentional discard@SuppressWarnings("unused")변수 명명 추가(Minor #4). 신규 2종:si_lock_c5_raw_adapter_close_throws_CME_after_lease_expires(raw CME 문서화) +si_lock_c5_metered_port_swallows_CME_and_increments_lease_expired_counter(metered 흡수+카운터). cross-package로LOCK_LEASE_EXPIRED상수 접근 불가 → 리터럴"lock.lease.expired"사용 (MeteredDistributedLockPortTest 의 pinning test 가 drift 방지 역할). → 6/6 PASS.app-bootstrap전체 suite: 280 tests, 1 pre-existing failure (outbound_adapter_method_returns_only_domain_or_primitives).LOCK_LEASE_EXPIRED상수 visibility: package-private (기존 상수 패턴 유지). cross-package 테스트는 리터럴 직접 사용 + same-package pinning test 로 drift 방지.
결정 사항
추후 면접/회고에서 "왜 이렇게 했나" 답할 근거. 대안과 함께 기록. 각 결정의 근거는 위 Sources 또는 새로 추가된 raw 자료를 가리킬 것.
- 2026-06-12 (D1): 본 branch 가
distributedLockProviderbean 계약의 SSOT owner — background-job §Audit A7 의 owner 공백 해소 / 이유: 5개 coordination bean 중 유일하게 owner 부재, 코드 주석의 runtime-health 표기는 stale / 대안: runtime-health 가 소유(그 노트가 consume-only 자기 서술이라 기각) / 근거: ca-tmplStartupSafetyValidator.java코드 + raw/branch-notes/feature-background-job-async-contract §Audit A7 - 2026-06-12 (D2): lock 접근은 application-core port 경유 —
obtain(key) → java.util.concurrent.locks.Lock시맨틱 / 이유: CA 레이어 규칙 + provider 교체 가능성 / 대안: 구현체 직접 사용(레이어 위반 기각) / 근거: raw/official-docs/lock-spring-integration-lock-registry - 2026-06-12 (D3): multi-instance 기본 provider = Spring Integration
JdbcLockRegistry(PG baseline 재사용), Redis 활성 시RedisLockRegistry/Redisson 교체 허용 / 검토 대안 5: PG session advisory(배제 — rollback 비해제·dangling), PG xact advisory(D4 의 보조 경로로 한정), JdbcLockRegistry(채택), ShedLock(배제 — maintainer 거부 + skip 시맨틱), Redisson(Redis-활성 분기) / 근거: raw/official-docs/lock-spring-integration-lock-registry, raw/official-docs/lock-shedlock-issue-899-non-scheduler-use, raw/official-docs/lock-postgres-advisory-locks - 2026-06-12 (D4): 트랜잭션 commit 정합 불변식 — lock 해제는 보호 대상 tx 의 commit 이후에만. tx-scope 일치 use case 는
pg_advisory_xact_lock허용(자동 해제), session-level advisory 는 도입 금지 / 근거: raw/official-docs/lock-postgres-advisory-locks (PG-ADV-C2/C3) - 2026-06-12 (D5): 획득 계약 = try-lock + 유한 waitTime + lease(TTL) 필수, 무한 blocking 금지 / 근거: PG-ADV-C4, LOCK-C2, SI-LOCK-C4/C5, SUBSKRIBE-LOCK-C2
- 2026-06-12 (D6): 본 lock 은 efficiency lock 전용 — correctness 는 DB 제약(unique/optimistic lock)으로, fencing token 미도입 / 근거: LOCK-C4 (Kleppmann,
engineering-blog— 재확인 보류 상태 명시) - 2026-06-12 (D7): lock 획득 실패 error code
LOCK_ACQUISITION_TIMEOUT(categoryCONFLICT, retryable true) + metriclock.acquisition— registry 에 없는 신규 제안 (기존 값 단정 아님, registry-governance 절차 경유) - 2026-06-12 (D8): domain-core·application-core 에서 lock 구현체 패키지 의존 금지 (정적 강제 요구) — rule 호스팅은
feature-architecture-enforcement-rulesSSOT 에 위임
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다.
Decision ID는 이 branch-note 안에서 안정적으로 유지한다. 예:D1,D2.Supporting Claims는raw/<category>/<slug>.md#C1형식으로 연결한다.
선택 조건열(R2): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면N/A.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 본 branch = distributedLockProvider bean 계약 SSOT owner (A7 해소). bean 이름은 StartupSafetyValidator.REQUIRED_MULTI_INSTANCE_BEANS 기존 값 "distributedLockProvider" 재사용 |
N/A — owner 공백 해소 (다른 branch 가 이미 소유했다면 본 branch 신설 불요였음) | ca-tmpl src/app-bootstrap/.../StartupSafetyValidator.java (code fact) + raw/branch-notes/feature-background-job-async-contract.md §Audit A7 |
internal-code-fact + sibling-audit (외부 출처 비대상 — 내부 ownership 결정) |
코드 주석의 owner 표기가 runtime-health 로 stale (§Audit A1 — ca-tmpl 갱신 필요) |
| D2 | lock 접근은 application-core port 경유, obtain(key) → java.util.concurrent.locks.Lock 시맨틱 (LockRegistry 모델 차용) |
구현체가 j.u.c.Lock 호환을 제공하는 한 이 결정. 호환 불가 provider 도입 시(예: skip-시맨틱) port 시그니처 재설계 | raw/official-docs/lock-spring-integration-lock-registry.md#SI-LOCK-C1, raw/official-docs/cache-redisson-rlock-vs-setnx.md#LOCK-C3 (Redisson 도 j.u.c.Lock — 이식성 방증) |
official-vendor-doc (SI) + needs-confirmation (LOCK-C3) |
port 명명·메서드 모양은 UNSUPPORTED_IMPL_DECISION (§구현 가이드 1) |
| D3 | multi-instance 기본 provider = JdbcLockRegistry (PG baseline 재사용, 추가 인프라 0). Redis 활성 시 RedisLockRegistry/Redisson 교체 허용. ShedLock·PG session-level advisory 배제 |
APP_MULTI_INSTANCE_ENABLED=true + Redis 비활성 → JdbcLockRegistry; Redis 활성(cache 활성) → RedisLockRegistry/Redisson 교체 가능; flag=false(default) → bean 불요, in-process 구현으로 충분 |
raw/official-docs/lock-spring-integration-lock-registry.md#SI-LOCK-C2, #SI-LOCK-C3, raw/official-docs/lock-shedlock-issue-899-non-scheduler-use.md#SHEDLOCK-899-C1, #SHEDLOCK-899-C2 (ShedLock 배제), raw/official-docs/lock-postgres-advisory-locks.md#PG-ADV-C2, #PG-ADV-C5 (session-level 배제), raw/company-tech-blogs/lock-subskribe-advisory-lock-distributed-consensus.md#SUBSKRIBE-LOCK-C1 (DB-only 사례) |
official-vendor-doc + maintainer-statement + company-case-study |
spring-integration-jdbc 신규 의존성 + INT_LOCK DDL 관리 비용. SI 버전 ↔ Boot BOM 정합 미확인 (§Claims To Verify) |
| D4 | 트랜잭션 commit 정합 불변식: lock 해제는 보호 대상 작업의 DB commit 이후에만. lock 수명 = 단일 tx 인 use case 는 pg_advisory_xact_lock 허용(commit/rollback 자동 해제). session-level advisory 의 수동 unlock 경로는 도입 금지 |
lock scope ⊆ 단일 tx → xact advisory lock (자동 정합); lock scope ⊃ tx (여러 tx/외부 호출 포함) → JdbcLockRegistry + "획득 → tx → commit 반환 후 unlock" 순서 강제 | raw/official-docs/lock-postgres-advisory-locks.md#PG-ADV-C2 (session-level 은 tx 시맨틱 무시 — rollback 후에도 잔존), #PG-ADV-C3 (xact-level 은 tx 종료 시 자동 해제), raw/company-tech-blogs/lock-subskribe-advisory-lock-distributed-consensus.md#SUBSKRIBE-LOCK-C4 (사례 보강) |
official-vendor-doc + company-case-study |
Spring @Transactional proxy 와 xact lock 의 실제 정합은 locally-verified 필요 (§Claims To Verify) |
| D5 | 획득 계약: try-lock + 유한 waitTime + lease(TTL) 필수. 무한 blocking 금지. lease 갱신은 보유 thread 만, lease 만료 후 unlock 은 예외 처리 의무 | N/A — 모든 획득 경로 공통. (lease 없는 lock 이 필요해지면 D6 correctness 경계 재검토가 선행) | raw/official-docs/lock-postgres-advisory-locks.md#PG-ADV-C4 (try 변형 존재), raw/official-docs/cache-redisson-rlock-vs-setnx.md#LOCK-C2 (TTL = crash 시 deadlock 회피), raw/official-docs/lock-spring-integration-lock-registry.md#SI-LOCK-C4 (갱신은 보유 thread 만), #SI-LOCK-C5 (만료 후 unlock → ConcurrentModificationException), raw/official-docs/lock-shedlock-readme.md#SHEDLOCK-C3, #SHEDLOCK-C4 (lease 상·하한 원리 참조), raw/company-tech-blogs/lock-subskribe-advisory-lock-distributed-consensus.md#SUBSKRIBE-LOCK-C2 (try-only 운영 사례) |
official-vendor-doc + official-reference + company-case-study |
구체 default 값(waitTime/TTL)은 UNSUPPORTED_IMPL_DECISION (§구현 가이드 4) |
| D6 | 본 lock 은 efficiency lock 전용. correctness 가 필요한 경로는 DB 제약(unique constraint = DB_UNIQUE_VIOLATION, optimistic lock = PRECONDITION_FAILED 기존 계약)으로 보장. fencing token 미도입 |
중복 작업 방지(비용 절감) 목적 → 본 lock; 중복 결과 차단(정합성) 필요 → DB 제약 사용. fencing token 이 필요한 외부 시스템 mutation 등장 시 본 결정 재검토 | raw/official-docs/cache-redisson-rlock-vs-setnx.md#LOCK-C4 (Kleppmann: lease 기반 correctness 는 unsafe, efficiency 는 충분), raw/official-docs/lock-shedlock-readme.md#SHEDLOCK-C5 (clock 동기화 가정 — lease 기반의 전제 한계 방증) |
engineering-blog (LOCK-C4 — verbatim 재확인 보류) + official-reference |
LOCK-C4 의 verbatim 재확인 불가 상태 지속 (cache branch 와 공동 — archive.org 스냅샷 필요) |
| D7 | lock 획득 실패/timeout 의 error code = LOCK_ACQUISITION_TIMEOUT (category CONFLICT, retryable true, client_safe true) + metric lock.acquisition (tag: outcome) — registry 신규 제안 |
N/A — 단 registry-governance 검토에서 기존 code 재사용 판정 시 그 code 채택 | UNSUPPORTED_IMPL_DECISION — registry(error-codes.yaml·metrics.yaml)에 일반 lock 항목 부재 확인(2026-06-12 grep). category CONFLICT 는 기존 enum(shared/error/Category.java) 재사용, code/metric 이름 은 근거 없는 신규 제안 |
none (신규 제안 — 기존 값 단정 금지) |
registry-governance 절차 미통과 상태. cache 의 CACHE_STAMPEDE_LOCK_TIMEOUT 과 의미 경계 문서화 필요 |
| D8 | domain-core·application-core 에서 lock 구현체 패키지(org.springframework.integration.., org.redisson.., net.javacrumbs.shedlock..) 의존 + advisory SQL 직접 호출 금지 — adapter 전용. rule 호스팅은 raw/branch-notes/feature-architecture-enforcement-rules SSOT 위임 (본 branch 는 요구사항만 등록) |
N/A — D2 port 결정의 정적 강제 도출 | D2 의 도출 + ca-tmpl CLAUDE.md 의존 방향 매트릭스 (code fact). rule 명명 은 UNSUPPORTED_IMPL_DECISION |
internal-code-fact (모듈 매트릭스) |
rule 이 architecture-enforcement-rules 에 실제 등록되기 전까지 documented-only |
구현 가이드
결정 (Decisions) 이 "무엇 을 할 것인가" 라면, 본 §는 "어디에 어떻게 구현될 것인가" 의 사전 명세. 3-rule meta principle (R1 Reference / R2 UNSUPPORTED_IMPL_DECISION / R3 OUT_OF_BRANCH_SCOPE) 준수 — CLAUDE.md §15.5. 구현 상태: 본 § 전체가
planned— src grep 실측(2026-06-12) 결과 lock 관련 구현은StartupSafetyValidator의 bean-presence 검사뿐, port/adapter/registry 코드는 전무.actually-implemented로 표현 금지.
1. Port · adapter · wiring 배치 (D1
Trace: D1 (bean 이름 = code 기존 값) + D2 (port 추상화 — SI-LOCK-C1) + D8 (구현체 격리)
- UNSUPPORTED_IMPL_DECISION: ① port 명명
DistributedLockPort+ 메서드tryAcquire(key, waitTime, ttl)모양 — 근거 raw 는 추상화 원칙(obtain→Lock)만 권고, 명명은 임의 (trade-off: sibling port 명명 패턴*Port정합). ② 모듈 배치 — adapter 구현을adapter-persistence에 두는 것은 "JDBC 기반"이라는 도출이지 raw 권고 아님 (trade-off: lock 저장소 = DB 이므로 persistence 인접이 의존 방향 최소).
| 항목 | 명세 | 상태 |
|---|---|---|
| port 인터페이스 | application-core — DistributedLockPort (가칭): tryAcquire(String key, Duration waitTime, Duration ttl) → lock handle (j.u.c.Lock 호환) |
planned |
| adapter 구현 | adapter-persistence — JdbcLockRegistry wrapping (D3). Redis 분기 구현은 Redis 활성 모듈에 별도 |
planned |
| bean wiring | app-bootstrap — bean 이름 distributedLockProvider (code 기존 값 — StartupSafetyValidator.REQUIRED_MULTI_INSTANCE_BEANS[0]). APP_MULTI_INSTANCE_ENABLED=true 일 때만 등록 |
planned |
| single-instance 경로 | flag=false(default) 시 in-process 구현(SI DefaultLockRegistry 동등 시맨틱)으로 port 계약 유지 — bean presence 강제 대상 아님 (env D8 consume) |
planned |
2. Provider 선택 분기 (D3)
Trace: D3 — SI-LOCK-C2 (4종 공식 구현체), SI-LOCK-C3 (JdbcLockRegistry 분산 락), SHEDLOCK-899-C1/C2 (ShedLock 배제), PG-ADV-C2/C5 (session-level 배제), SUBSKRIBE-LOCK-C1 (DB-only 사례)
| 조건 | provider | 비고 |
|---|---|---|
APP_MULTI_INSTANCE_ENABLED=false (default) |
in-process (SI DefaultLockRegistry 동등) |
분산 조정 불요 — single-instance 계약 |
| flag=true + Redis 비활성 | JdbcLockRegistry (채택 기본값) |
PG baseline 재사용, 추가 인프라 0. INT_LOCK 테이블 필요 (DDL 은 migration-startup 계약 경유) |
| flag=true + Redis 활성 | RedisLockRegistry 또는 Redisson RLock |
port 불변, 구현체만 교체 (SI-LOCK-C2). Redisson 채택 시 cache branch 의존성 재사용 |
| (배제) ShedLock | — | maintainer 가 generic lock 공식 선언 거부(SHEDLOCK-899-C1) + 획득 실패 시 skip 시맨틱으로 blocking 계약 불일치(SHEDLOCK-899-C2). scheduler 영역 사용은 background-job D3 소유로 불변 |
| (배제) PG session-level advisory | — | tx rollback 에도 잔존(PG-ADV-C2) + dangling lock 위험(PG-ADV-C5) + pool 반납 시 leak 경로 |
3. 트랜잭션 commit 정합 패턴 카탈로그 (D4)
Trace: D4 — PG-ADV-C2 (session = tx 무시), PG-ADV-C3 (xact = 자동 해제), SUBSKRIBE-LOCK-C4 (사례)
| 패턴 | 판정 | 이유 |
|---|---|---|
lock 획득 → @Transactional 작업 → commit 반환 후 finally unlock |
✅ 허용 (general 경로) | 해제가 commit 에 후행 — 임계 구역이 commit 전에 열리지 않음 |
pg_advisory_xact_lock 을 tx 내부에서 획득 |
✅ 허용 (tx-scope 경로) | commit/rollback 시 자동 해제 (PG-ADV-C3) — 정합을 DB 가 보장 |
| tx 내부에서 general lock 해제 (commit 전 unlock) | ❌ 금지 | 미commit 상태에서 다른 인스턴스가 임계 구역 진입 — lost update 류 race |
| session-level advisory lock + 수동 unlock | ❌ 금지 | rollback 에도 잔존(PG-ADV-C2) + unlock 누락 시 pool 반납 leak. 본 계약에서 경로 자체 미도입 |
4. 획득·해제 계약 + 실패 매핑 (D5
Trace: D5 — PG-ADV-C4, LOCK-C2, SI-LOCK-C4/C5, SHEDLOCK-C3/C4, SUBSKRIBE-LOCK-C2. D7 — registry 부재 확인(2026-06-12 grep).
- UNSUPPORTED_IMPL_DECISION: waitTime/TTL default 값 (예: waitTime 3s / TTL 30s) — 어떤 raw 도 구체 값을 권고하지 않음 (trade-off: Redisson watchdog default 30s 와 LOCK-C1 의 PX 30000 을 관행 참고치로만 사용, 측정 후 조정). error code
LOCK_ACQUISITION_TIMEOUT·metriclock.acquisition이름 — registry 신규 제안 (기존 값 아님을 명시). Jdbc 분기 long-task 의renewLock호출 주기 — SI 7.0+ 의lock(Duration ttl)API 존재는 raw 가 보장하나 갱신 주기 값은 임의 (trade-off: TTL 의 1/3 주기 관행 참고, 측정 후 조정).
| 항목 | 계약 | 상태 |
|---|---|---|
| 획득 | try-lock + 유한 waitTime 필수. 무한 blocking API 노출 금지 (PG-ADV-C4 의 try 변형 + SUBSKRIBE-LOCK-C2 운영 교훈) | planned |
| lease | TTL 필수 — 보유자 crash 시 자동 만료 (LOCK-C2, SHEDLOCK-C3 원리) | planned |
| 갱신 | 보유 thread 만 (SI-LOCK-C4). 자동 watchdog 은 Redisson 분기에서만 (LOCK-C3 — needs-confirmation) |
planned |
| Jdbc 분기 long-task 갱신 | Jdbc 분기에는 자동 watchdog 이 없음 — lock 보유 시간이 TTL 을 넘을 수 있는 작업은 ① 명시적 renewLock 주기 호출(보유 thread, SI-LOCK-C4) 또는 ② TTL ≥ 최대 작업 시간 보장 중 하나를 선택. 주기 값은 UNSUPPORTED_IMPL_DECISION (위 헤더) |
planned |
| 만료 후 해제 | ConcurrentModificationException 처리 의무 (SI-LOCK-C5) — 삼킴 금지, 로그 + metric |
planned |
| 실패 매핑 | timeout → LOCK_ACQUISITION_TIMEOUT (신규 제안 — category CONFLICT 기존 enum 재사용, retryable true). registry-governance 통과 전 코드 작성 금지 |
planned (제안 단계) |
| metric | lock.acquisition (tag: outcome = acquired/timeout/error) — 신규 제안. 기존 metrics.yaml 에 lock 항목 없음 확인 |
planned (제안 단계) |
5. Contract test 계약 (D1
Trace: D1 (bean presence) + D3 (provider 분기). env D8 의
StartupSafetyValidator집행을 consume — 검사 메커니즘 자체는 env branch 소유 (OUT_OF_BRANCH_SCOPE).
| 테스트 | 검증 내용 | 상태 |
|---|---|---|
| bean presence | APP_MULTI_INSTANCE_ENABLED=true 시 distributedLockProvider bean 부재 → startup fail (기존 StartupSafetyValidatorTest 는 이름 기반 presence 만 검증 — 본 branch 는 실제 bean 등록 쪽 테스트 추가) |
planned |
| 상호 배제 | 동일 key 에 2 인스턴스(2 DataSource 컨텍스트) 경쟁 → 1개만 획득 | planned |
| commit 정합 | tx 미commit 상태에서 두 번째 획득 시도가 성공하지 않음 (D4 패턴 ✅① 검증) | planned |
| lease 만료 | TTL 경과 후 두 번째 인스턴스 획득 가능 + 원 보유자 unlock 시 CME 처리 (SI-LOCK-C5) | planned |
Audit & Findings
이관 history + drift 기록 (CLAUDE.md §15.5 R3). §구현 가이드에는 in-scope 만 남기고, 범위 밖/정정/전파는 여기 보존.
- A1.
STALE_CODE_COMMENT(drift) — ca-tmplStartupSafetyValidator.java의"distributedLockProvider"행 주석이feature-runtime-health-lifecycle-contract를 owner 로 표기 — 그 노트는 "consume only" 자기 서술(background-job §Audit A7 발견). 본 branch 가 owner 로 확정되었으므로 코드 주석을 본 branch 로 갱신 권고 (ca-tmpl 측 변경 — 자동 수정 안 함, 정합 권고만). - A2.
RESEARCH_CORRECTION— 선행 조사(wiki-decision-researcher)가 "ShedLock = scheduler 전용 공식 입장"으로 요약했으나 README verbatim(SHEDLOCK-C2 "it's just a lock")은 그 표현을 지지하지 않음. issue #899 verbatim 으로 정정: 배제의 실근거 = generic lock 공식 선언 거부(SHEDLOCK-899-C1) + skip(비대기) 시맨틱(SHEDLOCK-899-C2). 커뮤니티의 non-scheduler production 사용 보고(SHEDLOCK-899-C4)도 존재 — "기술적 불가"가 아니라 "공식 비지원 + 시맨틱 불일치"가 배제 이유. - A3.
OUT_OF_BRANCH_SCOPE이관 기록 — ① scheduler/outbox lock 적용 정책 → background-job D3 (불변). ② cache stampede lock +CACHE_STAMPEDE_LOCK_TIMEOUT→ cache-consistency D3/D4 (불변). ③APP_MULTI_INSTANCE_ENABLED+ validator 집행 → env-driven D8 (consume). ④INT_LOCKDDL 의 migration 절차 → migration-startup-contract (본 branch 는 DDL 필요 사실만 제안). ⑤ ArchUnit rule 호스팅 → architecture-enforcement-rules (D8 은 요구사항만). - A4.
PROPAGATION_NOTICE(비차단) — background-job §테스트 계약·§구현 가이드 4 의 테스트 FQCNnet.javacrumbs.shedlock.core.LockProvider는 "ShedLock 또는 동등 bean" 가정 시절의 표기. 본 branch D3 가JdbcLockRegistry를 기본 채택했으므로 그 테스트 계약의 FQCN 은 port/bean 기준으로 갱신 필요. 동일하게 project-note §27 의 "ShedLock + Redisson + …" 5종 나열도 "distributedLockProvider(본 branch D3)" 로 읽도록 전파 대상. 비차단 — owner(background-job·env·project note) 가 다음 편집 시 반영. - A5.
NEW_BRANCH_REGISTRATION— parent project §29.E row #9 가 본 branch 를(없음)예정으로 표기 + §25 SSOT Owner Map 에 distributed lock row 부재. 본 branch 신설로 §31.1 Cluster list + §25 Owner Map + §29 row 상태 갱신 필요 (project-note 사용 절차 #4 의무 — 본 세션에서 최소 반영 또는 다음 project-note 편집 시).
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존을 미리 열거.
- 실패·엣지 경로:
- 획득 timeout →
LOCK_ACQUISITION_TIMEOUT(신규 제안) 반환, retryable true — 호출측 재시도 정책은 호출 branch 소유 - lease 만료 중 작업 진행 — 두 보유자 동시 진입 가능. D6 efficiency 경계로 허용하되 correctness 필요 경로는 DB 제약이 최종 방어 (LOCK-C4)
- lease 만료 후 unlock →
ConcurrentModificationException(SI-LOCK-C5) — 삼킴 금지, 로그+metric 후 정상 흐름 복귀 - JVM crash → lock row 는 TTL 로 자동 만료 (LOCK-C2/SHEDLOCK-C3 원리) — 잔존 lock 수동 정리 runbook 불요 설계
- clock skew — lease 판정이 노드 시계에 의존하면 SHEDLOCK-C5 의 동기화 가정 필요 → DB 시간 기준 여부 확인 (§Claims To Verify)
- 동일 thread 재진입 —
JdbcLockRegistry의 reentrancy 보장 미확인 (§Claims To Verify) — 보장 확인 전까지 재진입 금지 계약 - connection pool 고갈 — lock 대기가 DB connection 을 점유하는 구현(advisory blocking)은 배제됨(D3/D5) — JdbcLockRegistry 의 lock 당 connection 사용 패턴은 확인 필요
- 획득 timeout →
- 다른 계약 의존:
- raw/branch-notes/feature-env-driven-runtime-configuration D8 —
APP_MULTI_INSTANCE_ENABLEDflag +StartupSafetyValidatorpresence 강제를 consume. flag 의미/집행 변경 시 본 branch bean 등록 조건 영향 - raw/branch-notes/feature-background-job-async-contract D3 — scheduler/outbox 가 본 branch 의 provider 를 consume (§Audit A4 전파)
- raw/branch-notes/feature-cache-consistency-contract D3 — Redis 활성 분기에서 Redisson 의존성 공유. cache 가 Redisson 을 제거하면 본 branch Redis 분기 재검토
feature-migration-startup-contract—INT_LOCKDDL 의 Flyway 반영 절차- raw/branch-notes/feature-architecture-enforcement-rules — D8 rule 호스팅
- raw/branch-notes/feature-env-driven-runtime-configuration D8 —
검증해야 할 주장
공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리한다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
spring-integration-jdbc 가 ca-tmpl Boot BOM 과 호환 + TTL API(lock(Duration ttl), SI 7.0+) 사용 가능 |
SI 버전·TTL API 도입 시점과 현재 BOM 미대조 | build.gradle 의존성 추가 후 컴파일 + JdbcLock TTL 메서드 존재 확인 |
needs-confirmation |
INT_LOCK 테이블 DDL 은 자동 생성되지 않아 Flyway 수동 migration 필요 |
공식 문서에서 schema 자동 생성 여부 미확인 | SI 배포 schema 스크립트 위치 확인 + 로컬 기동 테스트 | needs-confirmation |
pg_advisory_xact_lock 이 Spring @Transactional commit 시점에 자동 해제 (D4 ✅② 경로) |
proxy 기반 tx 경계와 PG 세션의 실제 상호작용 미검증 | 2-connection 경쟁 통합 테스트: tx A 보유 중 tx B 획득 실패 → A commit 후 B 획득 성공 | needs-confirmation |
JdbcLockRegistry 의 동일 thread 재진입 보장 여부 |
SI-LOCK-C1 은 j.u.c.Lock 반환만 보장, reentrancy 는 "Does not prove" 명시 | 공식 Javadoc/소스 확인 + 재진입 단위 테스트 | needs-confirmation |
| Redisson RLock watchdog 시맨틱 (LOCK-C3) | redisson.org → redisson.pro redirect 차단으로 verbatim 재확인 불가 (cache branch 공동 관심) | Redisson Javadoc 직접 다운로드 또는 GitHub wiki 로 verbatim 격상 | needs-confirmation |
JdbcLockRegistry 의 lock 대기가 DB connection 을 점유하는지 (polling 마다 반납 vs holding) |
retry-polling(idleBetweenTries) 구조라 점유 패턴 미확인 — holding 이면 pool 고갈 시 self-deadlock 경로 | SI 소스/Javadoc 확인 + pool size 1 로 죄인 통합 테스트에서 동시 lock 대기 시 고갈 여부 관찰 | needs-confirmation |
flag=true + bean 등록 시 StartupSafetyValidator 가 실제 통과 (이름 기반 presence) |
현재 테스트는 부재 → fail 만 검증, 등록 → pass 는 bean 타입 무관 이름만 매칭 | StartupSafetyValidatorTest 확장 + 실제 adapter bean 으로 기동 테스트 |
planned |
관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
/coverage가 채우는 생성물 — 손으로 유지하지 않는다. governing 문서(frontmattergoverning_docs)가 요구하는 관심사를 이 브랜치가 빠짐없이 덮는지의 결과. 기준:rules/coverage-gate.md. 상태:covered-here(이 브랜치 결정) /delegated(다른 owner 브랜치) /missing(아무도 안 맡음 → Blocking).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
(생성 전 — /coverage feature-distributed-lock-contract 실행 대기) |
— | — | — | — |
마주친 문제
짧은 메모만. 깊이 있는 트러블슈팅은
raw/errors/로 분리하고 아래 Cluster에 연결.
- 2026-06-13 (Layer 2):
LockAcquisitionTimeoutExceptionTest.message_contains_waitTime첫 실행 실패. 원인:Duration.ofMillis(500).toString()은"PT0.5S"(ISO-8601) —"500"을 포함하지 않음. 어설션을contains(waitTime.toString())로 수정 후 통과. raw/errors 별도 분리 불필요 (trivial one-liner 수정). - 2026-06-13 (Layer 4):
DistributedLockProviderContractTestD3/D5 테스트 —CannotAcquireLockException(NullPointerException: readCommittedTransactionTemplate).DefaultLockRepository를 Spring 컨텍스트 없이 사용할 때SmartInitializingSingleton.afterSingletonsInstantiated()를 명시 호출해야 함을 발견. 상세: raw/errors/spring-integration-defaultlockrepository-aftersingletons-null-template-2026-06-13.
묶음 (이 branch에서 파생된 자료)
- raw/company-tech-blogs/lock-subskribe-advisory-lock-distributed-consensus
- raw/official-docs/lock-postgres-advisory-locks
- raw/official-docs/lock-shedlock-issue-899-non-scheduler-use
- raw/official-docs/lock-shedlock-readme
- raw/official-docs/lock-spring-integration-lock-registry
이 branch는 단일 노트가 아니라 작업 묶음의 entry point. 이 branch에서 파생된 모든 raw 노트를 카테고리별로 명시.
근거 자료
- raw/official-docs/lock-postgres-advisory-locks — PostgreSQL §13.3.5 Advisory Locks + §9.28.10 함수 레퍼런스 (session-level vs transaction-level 시맨틱, non-blocking 변형)
- raw/official-docs/lock-shedlock-readme — ShedLock README: scheduled task 전용 락 / not full-fledged scheduler 공식 경계,
lockAtMostFor/lockAtLeastForlease 시맨틱, clock 동기화 전제 조건 - raw/official-docs/lock-shedlock-issue-899-non-scheduler-use — ShedLock Issue #899: maintainer 가 generic lock 공식 선언 거부 + skip semantics 명시 (SHEDLOCK-899-C1, SHEDLOCK-899-C2) —
distributedLockProvider후보에서 ShedLock 배제/허용 결정의 근거 - raw/official-docs/lock-spring-integration-lock-registry — Spring Integration LockRegistry/JdbcLockRegistry 공식 레퍼런스 (j.u.c.Lock 추상화, 4종 구현체, TTL/renewal/CME 시맨틱)
- raw/company-tech-blogs/lock-subskribe-advisory-lock-distributed-consensus — Subskribe production 사례: advisory lock 만으로 distributed mutual exclusion + optimistic try-lock only 교훈 (company-case-study — 공식 승격 금지)
- raw/official-docs/cache-redisson-rlock-vs-setnx — (cache branch 와 공유) Redisson RLock/SETNX/Redlock 비교 + Kleppmann efficiency vs correctness (LOCK-C1~C4)
Sub-branches (세부 작업)
- (아직 없음)
오류 기록 (이 branch 작업 중 발생)
- raw/errors/spring-integration-defaultlockrepository-aftersingletons-null-template-2026-06-13 —
DefaultLockRepositorySpring 컨텍스트 외부 초기화 시afterSingletonsInstantiated()누락 →readCommittedTransactionTemplateNPE. Layer 4DistributedLockProviderContractTest작성 중 발생, resolved.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- "분산 락에서 lock 해제와 DB commit 의 순서가 왜 중요한가? lost-update race 를 설명하라" (D4 canonical pattern / forbidden inverse)
- "efficiency lock 과 correctness lock 의 차이는 무엇인가? 왜 DB unique constraint 가 최종 방어선인가?" (D6)
- "AutoCloseable 의
close()가throws Exception인데, 왜 이 인터페이스는 그것을 재정의하여 unchecked 로 만들었는가?" - "tryLock(waitTime) + leaseTtl 조합이 무한 blocking 과 deadlock 을 어떻게 방지하는가?" (D5)
- "finally 블록에서 예외를 던지면 왜 위험한가? 분산 락 해제 중 CME 를 re-throw 하지 않는 이유는?" (SI-LOCK-C5 / 정상 흐름 복귀)
- "Decorator 패턴에서 wrapping lambda 로 handle 을 교체할 때 기존 동일성 테스트(
isSameAs)가 왜 깨지는가?" (quality-review remediation — MeteredDistributedLockPort)
강의 (이 작업을 위해 학습한 강의)
- (아직 없음)
job-posting tie-ins (이 작업에서 파생된 글감)
- "ShedLock 은 분산 락이 아니다 — maintainer 의 입으로 확인한 skip 시맨틱" (SHEDLOCK-899-C1/C2)
- "분산 락과 트랜잭션: lock.close() 를 finally 에 두는 것만으로는 부족한 이유" (D4 forbidden inverse — commit 전 해제의 lost-update race)
- "Clean Architecture 에서 분산 락 추상화 — DistributedLockPort 가 JdbcLockRegistry 를 숨기는 방법" (D2/D8 port 설계)
- "Spring의 SmartInitializingSingleton: Spring 컨텍스트 없이 bean을 사용할 때 afterSingletonsInstantiated()를 직접 호출해야 하는 이유" (Layer 4 troubleshooting — DefaultLockRepository NPE)
- "finally 블록에서 예외를 삼키는 게 맞을 때도 있다 — JdbcLock lease-expiry CME 처리와 정상 흐름 복귀" (SI-LOCK-C5 / quality-review finding 1)
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- (2026-06-12 생성 — daily 노트 미작성)
완료 후 정리
머지/종료 시점에 채움.
/ingest가 이 섹션을 기준으로 wiki/projects/에 추출.
-
PR 링크: (미생성 — 사용자가 커밋·PR 수행)
-
리뷰 메모: 2026-06-13 3단계 리뷰 체인 전부
ready— ca-architect-sentinel(PASS, 0 blocking/0 advisory: SI 가 adapter-persistenceimplementation으로만 격리, app-bootstrap main 에 SI import 0, D8 모듈매트릭스 충족), ca-spec-reviewer(PASS, 요구 20/20 met, missing/extra/misinterpreted 0), ca-quality-reviewer(1차 NEEDS_FIX: Critical 1[SI-LOCK-C5] + Important 2 + Minor 2 → remediation 후 재리뷰 PASS, 0/0/0). -
머지 결과 / 배포 환경: 로컬 검증 완료 (Testcontainers PG Docker 가용 — 통합 테스트 SKIP 아님, 실제 실행). 최종 gradle 검증(2026-06-13):
:shared-contract:test/:application-core:test/:adapter-persistence:test— 전부 PASS:app-bootstrap:test— 280개 중 lock 관련 21개(Metered 10 + Provider 6 + Classification 5) 전부 PASS. 유일한 실패는 선행 커밋(d702572 등)에서 유래한 무관한 ArchUnit 위반outbound_adapter_method_returns_only_domain_or_primitives(OutboundHttpSettings.retry()/.circuitBreaker()nested record) —git stash후 clean HEAD 에서도 동일 실패 확인 → 본 branch 변경과 무관, 미수정(범위 밖, outbound branch 소유).verifyCleanArchitectureDependencies/verifyEnvKeys— PASS (env 키 신규 0;ca-skeleton.lock.*은 APP_ 비매핑 plain yaml).- registry 추가:
error-codes.yamlLOCK_ACQUISITION_TIMEOUT(CONFLICT/409/retryable, D7) +metrics.yamllock.acquisition(D7) +lock.lease.expired(§Edge/SI-LOCK-C5 — quality-review 후 추가, tagless counter).
-
wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented+locally-verified항목 (2026-06-13 현재):OperationalError.LOCK_ACQUISITION_TIMEOUT(shared-contract) — Layer 1DistributedLockPort/DistributedLock/LockAcquisitionTimeoutException(application-core) — Layer 2- 계약 테스트 10종 (application-core) — Layer 2
LockSettings/LockRegistryDistributedLockAdapter/DistributedLockPersistenceConfig(adapter-persistence) — Layer 3V4__int_lock.sql(adapter-persistence) — Layer 3LockRegistryDistributedLockAdapterTest5종 (adapter-persistence) — Layer 3
prod-verified항목: (없음)
-
추출하지 않을 항목 (planned / documented-only / abandoned):
- ArchUnit rule 호스팅 (
feature-architecture-enforcement-rules) — planned - background-job ShedLock FQCN 전파 알림 — planned
- ArchUnit rule 호스팅 (
-
wiki/projects 추출 추가 대상 (quality-review remediation 이후
actually-implemented+locally-verified):- Layer 4 완료분:
MeteredDistributedLockPort(SI-LOCK-C5 포함) +DistributedLockConfig+DistributedLockProviderContractTest6종 (2026-06-13)
- Layer 4 완료분: