[BE-03] FeatureFlag Redis 캐시 + pub/sub 전파 + 로컬 스냅샷
작업 내용 (설계 의도)
변경 사항
변경 전파 방안 A(Redis 캐시 look-aside + pub/sub 브로드캐스트 + 인스턴스 로컬 인메모리 스냅샷)의 인프라 구현. Redis pub/sub는 신규 인프라(RedisMessageListenerContainer 레포 0건). 근거 TDD: ../TDD.md “방안 비교 — 변경 전파”·Sequence(변경 전파). pub/sub 리스너는 도메인 비즈니스 로직이 아닌 캐시 코히런스 메커니즘이므로 infrastructure에만 둔다(Kafka Consumer의 presentation 규칙 비적용 — 명시적 근거는 TDD 서버 토폴로지).
RedisFeatureFlagCacheStore(FeatureFlagCacheStore구현): 키featureflag:flag:{key}, JSON 값(FeatureFlagSnapshot), TTL 10분(안전망). put/get/evict. StringRedisTemplate.RedisFeatureFlagChangeBroadcaster(FeatureFlagChangeBroadcaster구현): 채널featureflag:changes에 변경 flagKey를convertAndSend.FeatureFlagRedisPubSubConfig(infrastructure/config, 신규 파일):RedisMessageListenerContainer빈 +MessageListener등록(featureflag:changes구독).FeatureFlagChangeSubscriber(MessageListener): 수신 시LocalFeatureFlagStore.refresh(key)호출(멱등 — 재조회 후 덮어쓰기). 도메인/UseCase 미경유(캐시 갱신 전용). 수신 시각과 메시지의 변경 occurredAt 차이를 전파 지연으로 기록(feature_flag_propagation_lag_seconds— BE-12가 게이지화).LocalFeatureFlagStore: ConcurrentHashMap 기반 L1 + 마지막 성공 스냅샷.refresh(key)(캐시→미스 시 Repository 재조회 후 로컬·캐시 채움),get(key), 기동 시findAllActive로 부트스트랩 워밍,@Scheduled(30초) 전체 리프레시(pub/sub 유실 안전망). Redis 채널 계약은 REDIS-01(redis-implementer 계약 문서) 준수.- 지표 발신(senior-pm 정합 #2): 캐시 접근마다 Micrometer counter
feature_flag_cache_access_total{layer=local|redis,result=hit|miss}증가. Redis 접근 실패(DataAccessException)를 예외 전파 없이 삼키고redis-degraded(warning,source=feature-flag) 이벤트를 발신한다.
의존
- BE-01 (Gateway 인터페이스·FeatureFlagSnapshot)
- REDIS-01 (키·채널 계약 — redis-implementer, 병행 선행)
- (런타임) BE-02 Repository — 부트스트랩/미스 폴백 시. 컴파일은 domain interface(BE-01)만.
다이어그램
처리 흐름
sequenceDiagram participant W as 쓰기 인스턴스 participant R as Redis(채널 featureflag:changes) participant S as FeatureFlagChangeSubscriber participant L as LocalFeatureFlagStore W->>R: broadcast(key) R-->>S: onMessage(key) S->>L: refresh(key) L->>R: cacheStore.get(key) L-->>L: 로컬 스냅샷 갱신
테스트 케이스 (Testcontainers Redis)
- cacheStore.put 후 get이 동일 스냅샷을 반환하고 TTL이 설정된다
- broadcast(key) 발행 시 구독 중인 subscriber가 수신해 LocalStore.refresh를 호출한다
- 동일 key를 pub/sub로 2회 수신해도 로컬 상태가 동일하다(멱등)
- LocalStore 부트스트랩은 findAllActive 결과를 전부 로컬에 적재한다
- 로컬 미스 시 refresh가 Redis 캐시를 먼저 조회하고, 미스면 Repository로 폴백한다
- evict(key) 후 get은 null을 반환한다
- Redis 연결 실패 시 cacheStore.get이 예외를 전파하지 않고 null로 처리되며 redis-degraded 경보가 발신된다(평가 폴백 전제)
- 캐시 히트/미스 시 layer·result 태그가 붙은 카운터가 각각 증가한다
- subscriber 수신 시 변경 occurredAt과 수신 시각의 전파 지연이 기록된다