--- title: 우아한형제들 — 트랜잭션 커밋 이후 캐시 무효화 (after-commit invalidation 사례) source_type: company-tech-blog url: https://techblog.woowahan.com/2667/ archive_url: status: raw confidence: medium tags: [ca-cache-consistency, woowahan, after-commit, transaction-synchronization, korean-fintech] related_projects: [ca-skeleton-operational-contract] related_branches: [feature-cache-consistency-contract, feature-transaction-concurrency-contract] created: 2026-05-22 last_reviewed: 2026-05-27 --- # 우아한형제들 — 트랜잭션 커밋 이후 캐시 무효화 사례 > Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그 사례 발췌 (요지 발췌, 한국어). > ca-tmpl 의 "cache invalidation = after-commit only" 결정의 **사례** 근거 (공식 best-practice 가 아닌 case-study 취급). ## Parent / 활용 branch (필수) | Branch | 이 자료가 정당화하는 결정 | |---|---| | [[raw/branch-notes/feature-cache-consistency-contract]] | "tx 내부 cache mutation forbidden + afterCommit invalidation 강제" 결정의 한국 도메인 사례 근거. invalidation 실패 → 별도 처리 (observable failure, 재시도/비동기 큐) 요구의 사례 출처 | | [[raw/branch-notes/feature-transaction-concurrency-contract]] | `TransactionSynchronizationManager.registerSynchronization` 의 `afterCommit` 후크 활용 패턴의 사례 — port adapter 가 트랜잭션 lifecycle 에 hook 하는 구현 옵션 | ## 컨텍스트 ca-tmpl 결정 **"cache invalidation = after-commit only"** 의 사례 근거. Spring `TransactionSynchronizationManager.registerSynchronization` 을 실제 도메인에서 쓰는 한국 기업 사례. ## 출처 / Source - 원본 URL: https://techblog.woowahan.com/2667/ - 보조: 우아한형제들 기술블로그의 다른 캐시 글 묶음 (Redis, 동시성) - 참고 검색어: "우아한형제들 캐시 무효화 트랜잭션", "woowahan transactionsynchronization registerSynchronization" - 아카이브 URL: (미수집) - 저자 / 조직: 우아한형제들 (Woowahan Brothers / Woowa Bros.) — 기술블로그 - 발행일: rolling docs (페이지 자체에 명시 없음) - 마지막 확인일: 2026-05-27 - **재검증 한계: WebFetch 차단 — 본 인용은 user 수집본 (2026-05-22) 보존, verbatim 재확인 보류.** 본 자료의 인용은 "요지 발췌" 로 명시되어 있어 원문 일치도 검증 시 paraphrase 가능성 있음. ca-tmpl 의 `verified` 승급 전 원문 재확인 의무. ## 핵심 인용 / Key quotes (verbatim) > [§요지 — 글 본문 요약] needs-confirmation (요지 발췌, paraphrase 가능성): "캐시 무효화를 트랜잭션 내부에서 호출하면, 커밋이 롤백된 경우에도 캐시는 이미 invalidate 된다. 다른 트랜잭션이 그 사이 cache miss → DB 조회로 stale 값을 다시 채우는 race 가 발생했다." > [§해결책] needs-confirmation (요지 발췌): "해결책으로 `TransactionSynchronizationManager.registerSynchronization` 을 이용해 `afterCommit` 시점에만 캐시 무효화를 수행하도록 변경했다. 롤백 시에는 캐시를 건드리지 않는다." > [§한계] needs-confirmation (요지 발췌): "단, `afterCommit` 자체는 트랜잭션 외부이므로 무효화 실패는 별도 처리 (observable failure, 재시도 또는 비동기 큐 전송) 가 필요하다." > [§선언적 대안] needs-confirmation (요지 발췌): "Spring `@TransactionalEventListener(phase = AFTER_COMMIT)` 로 같은 효과를 더 선언적으로 얻을 수 있다." ## Claims Extracted / 추출된 주장 | Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove | |---|---|---|---|---|---| | WOOWA-CACHE-C1 | **사례**: 트랜잭션 내부 cache invalidation 호출 시 rollback 발생하면 cache 만 invalidate 되고 DB 는 유지 → 동시 다른 tx 의 cache miss → DB 조회 → stale 값 재로딩 race 가 발생 | [§요지] needs-confirmation: "캐시 무효화를 트랜잭션 내부에서 호출하면, 커밋이 롤백된 경우에도 캐시는 이미 invalidate 된다. 다른 트랜잭션이 그 사이 cache miss → DB 조회로 stale 값을 다시 채우는 race 가 발생했다." | `company-case-study` | 우아한형제들 도메인의 특정 워크로드 (정확한 endpoint 범위는 글에 비명시) | 이 race 가 모든 cache + tx 환경에서 항상 발생한다는 일반 best-practice 보장은 아님 — 사례 1건 | | WOOWA-CACHE-C2 | **사례 해결책**: `TransactionSynchronizationManager.registerSynchronization` 의 `afterCommit` 후크로 cache invalidation 을 commit 이후로 지연 — rollback 시에는 cache 미터치 | [§해결책] needs-confirmation: "해결책으로 `TransactionSynchronizationManager.registerSynchronization` 을 이용해 `afterCommit` 시점에만 캐시 무효화를 수행하도록 변경했다. 롤백 시에는 캐시를 건드리지 않는다." | `company-case-study` | Spring `TransactionSynchronizationManager` 사용 환경 | `afterCommit` 후크 사용이 모든 도메인에서 표준 패턴이라는 보장은 아님. 단, Spring 공식 javadoc 에 메커니즘은 명시 (별도 raw 필요) | | WOOWA-CACHE-C3 | **사례 한계 인식**: `afterCommit` 은 트랜잭션 외부이므로 cache invalidation 실패 시 트랜잭션이 rollback 되지 않음 → observable failure 노출 + 재시도 / 비동기 큐 전송 등 보상 로직 별도 필요 | [§한계] needs-confirmation: "단, `afterCommit` 자체는 트랜잭션 외부이므로 무효화 실패는 별도 처리 (observable failure, 재시도 또는 비동기 큐 전송) 가 필요하다." | `company-case-study` | `afterCommit` hook 으로 cache invalidation 위임한 모든 환경 | 본 사례가 제시한 specific 보상 메커니즘 (재시도 vs 비동기 큐) 의 선택 기준은 글에 명시 없음 | | WOOWA-CACHE-C4 | **선언적 대안 언급**: 동일 효과를 Spring `@TransactionalEventListener(phase = AFTER_COMMIT)` 로 얻을 수 있다는 언급 | [§선언적 대안] needs-confirmation: "Spring `@TransactionalEventListener(phase = AFTER_COMMIT)` 로 같은 효과를 더 선언적으로 얻을 수 있다." | `company-case-study` | Spring 4.2+ 환경 | `@TransactionalEventListener` 가 `registerSynchronization` 보다 모든 면에서 우월하다는 평가는 글에 명시 없음 — listener 미등록 환경 silent drop 위험은 별도 (Spring official 문서 필요) | ## Usage Boundaries / 적용 경계 - **이 자료가 직접 증명하는 것** (재검증 한계 + 사례 한정): - `WOOWA-CACHE-C1` ~ `C4`: 우아한형제들이 겪은 특정 race condition + `TransactionSynchronizationManager.afterCommit` 채택 + 외부 처리 필요성 인식 + `@TransactionalEventListener` 대안 언급 - **이 자료가 증명하지 않는 것** (company-tech-blog 일반 한계 + 본 사례 한정): - 한국 기업 표준 / Spring 공식 권장 — 본 자료는 **사례 1건** (CLAUDE.md §5: "공식 best-practice 로 취급 금지") - 모든 cache + tx 조합에서 `afterCommit` 패턴이 최적이라는 일반화 — 사례의 워크로드 특성 (read-heavy / write 빈도) 미명시 - `TransactionSynchronizationManager` vs `@TransactionalEventListener` 의 선택 기준 — 글이 두 옵션을 언급하나 비교 분석 부재 - cache invalidation 실패의 정확한 모니터링 / alert 메커니즘 — "observable failure" 만 언급, 구현 디테일 부재 - 본 자료 단독으로 `afterCommit` 패턴을 "official best practice" 로 격상 불가 → **Spring 공식 문서 (별도 raw)** 와 corroborate 필요 (다른 raw 의 official-vendor-doc strength claim 과 결합 시에만 일반화 가능) - **내 프로젝트에 적용하려면 추가 확인이 필요한 것**: - **재검증 한계로 인한 SSOT 확인 의무**: ca-tmpl 의 `verified` / `published-ready` 승급 전, 원문 직접 재확인하여 paraphrase vs verbatim 명확화 - ca-tmpl 의 `CACHE/INVALIDATION_FAILURE` error code 분류가 본 사례의 "observable failure" 시맨틱과 일치하는지 (재시도 정책, alert 임계값 등) - `@TransactionalEventListener` 채택 시 listener bean 등록 누락에 대한 build-time/test-time 검출 가드 (silent drop 방지) ## 메모 / Notes (내 프로젝트 해석) > 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석. - ca-tmpl 결정과의 정합성: - "tx 내부 또는 tx 미참여 상태에서의 cache mutation 은 forbidden" ← 같은 문제의식 (WOOWA-CACHE-C1). - "invalidation 실패가 조용히 무시되면 실패" 테스트 ← 우아한형제들 사례의 후속 문제 (afterCommit 외부 실패 처리, WOOWA-CACHE-C3). - 두 가지 구현 옵션 (둘 다 본 사례에서 언급): 1. `TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { afterCommit() { … } })` — adapter 에서 직접 등록. 2. domain event 발행 + `@TransactionalEventListener(phase = AFTER_COMMIT)` — 더 선언적, 그러나 listener 가 등록 안 된 환경에서 silent drop 위험. - ca-tmpl test 계약 매핑: - "tx rollback 시 cache 에 stale write 가 남으면 실패" ← afterCommit 강제로 자동 만족. - "invalidation 실패가 조용히 무시되면 실패" ← afterCommit 안에서 발생한 `RedisConnectionException` 을 swallow 하면 실패. ca-tmpl 은 별도 error code (`CACHE/INVALIDATION_FAILURE`) 로 분류 권장. - **취급 주의**: 회사 기술블로그는 "공식 best-practice 가 아님" (CLAUDE.md §5). 패턴 자체는 Spring 공식 문서가 권장 (`@TransactionalEventListener` Javadoc — 별도 official-vendor-doc 인용 필요). - 시사점: ca-tmpl 의 결정은 우아한형제들 사례 + Spring 공식 메커니즘의 교집합. 임의 결정 아님 — 단, 본 raw 단독으로는 사례 근거이며 official-vendor-doc raw 와 corroborate 시에만 일반화 가능. ## Related / 관련 - 같은 주제 다른 raw: - [[raw/official-docs/spring-transactional-event-listener]] (Spring `@TransactionalEventListener` 공식 정의 + phase 시맨틱 → 본 사례의 "더 선언적" 대안의 official-vendor-doc 근거) - [[raw/official-docs/cache-redisson-rlock-vs-setnx]] (cache stampede 방지 도구 선택) - 적용 ca-tmpl branch-note: - [[raw/branch-notes/feature-cache-consistency-contract]] - [[raw/branch-notes/feature-transaction-concurrency-contract]] - canonical contract 섹션: - [[raw/project-notes/ca-skeleton-operational-contract]] (cache consistency 관련 섹션) - 대안 그룹: **Group G-C — Cache consistency** (invalidation timing: a) inline in tx [forbidden] / b) afterCommit registerSynchronization [ca-tmpl] / c) `@TransactionalEventListener` AFTER_COMMIT / d) async outbox 로 위임) — 본 source 는 **b 채택 사례 + c 대안 언급**. - 인용한 wiki 요약: (미작성)