Skip to content

[Refactor][P3] UserSnapshot 캐시: NOT_FOUND 음수 캐싱 부재 및 미스 시 single-flight 미적용으로 User Service 부하 집중 #33

Description

@userri

문제

각 MSA의 인가 핵심 경로에서 사용하는 UserSnapshot 캐시(RedisUserSnapshotCacheAdapter)에 두 가지 부하 집중 위험이 있습니다.

  1. 캐시 미스 시 single-flight 부재: TTL(기본 300초) 만료나 콜드 상태에서 동일 keycloakSub에 대한 동시 요청이 모두 Redis 미스를 겪으면 전부 User Service를 호출합니다. 요청별 cache-aside만 있고 락/single-flight가 없습니다. (캐시 key가 keycloakSub 단위이므로 스탬피드는 '동일 사용자'의 동시 요청 폭에 비례합니다.)
  2. 부정(음수) 캐싱 부재: 존재하지 않는 keycloakSub(404 → null → USER_SNAPSHOT_NOT_FOUND)는 캐시에 저장되지 않습니다. 따라서 잘못되거나 만료된 토큰으로 들어오는 반복 요청이 매 요청마다 User Service를 호출합니다.

인가는 @RequireRole이 붙은 모든 메서드 진입 시 실행되는 매 요청 핵심 경로입니다.

근거

검증으로 확인한 실제 파일/라인:

  • src/main/java/com/bbd/securitycore/application/service/GetCurrentUserSnapshotService.java:57-62loadUserSnapshotCachePort.findByKeycloakSub(...).orElseGet(() -> loadFromUserServiceAndCache(...)). 미스 시 동시성 제어 없이 즉시 폴백.
  • src/main/java/com/bbd/securitycore/application/service/GetCurrentUserSnapshotService.java:80-85loadFromUserServiceAndCachesnapshot == null이면 save(...)(line 84-85) 도달 전에 USER_SNAPSHOT_NOT_FOUND 예외를 던짐(line 80-82). 즉 NOT_FOUND 결과는 절대 캐싱되지 않음.
  • src/main/java/com/bbd/securitycore/adapter/out/http/UserServiceSnapshotAdapter.java:45-47 — User Service 404를 null로 변환(부재 사용자).
  • src/main/java/com/bbd/securitycore/adapter/out/redis/RedisUserSnapshotCacheAdapter.java:60-70save는 정상 스냅샷만 TTL과 함께 저장. 락/setIfAbsent 등 없음.
  • src/main/java/com/bbd/securitycore/config/BbdSecurityProperties.java:82userSnapshotCacheTtlSeconds = 300 (기본 300초).
  • src/main/java/com/bbd/securitycore/adapter/in/aop/RoleAuthorizationAspect.java:47-57@Before(@RequireRole) 어드바이스가 매 요청 getCurrentUserSnapshot() 호출 → 핵심 경로 확인.
  • src/main 전체 grep 결과 lock / single-flight / setIfAbsent / setnx / redisson / 음수 캐싱 로직 없음.

제안

우선순위는 낮게 두되(과설계 회피를 위해 실제 부하 측정 후 적용 권장), 다음 순서를 권장합니다.

  1. NOT_FOUND 음수 캐싱(우선): 부재 사용자에 대해 짧은 TTL(예: 30초)의 sentinel/tombstone 값을 캐싱하여 무효 토큰의 반복 호출이 User Service로 새어 나가지 않도록 합니다. 권한 부여 지연을 피하기 위해 양수 TTL(300초)보다 짧게 둡니다.
  2. 필요 시 single-flight: keycloakSub 단위로 짧은 분산락(SET key val NX PX) 또는 in-process per-key 락을 적용해 동일 사용자 동시 미스 시 한 번만 User Service를 호출하도록 완화합니다.

각 단계는 독립 적용 가능하며, 1번이 위험 대비 노력이 가장 낮습니다.

영향 범위

  • bbd-security-core를 의존하는 모든 MSA(sales, inventory, item, procurement 등)의 인가 핵심 경로에 영향.
  • 동작 변경은 캐시 계층에 국한되며 인가 판단 로직은 그대로 유지됨. 단 음수 캐싱 도입 시 사용자 생성/복구가 음수 TTL 동안 반영되지 않으므로 TTL을 짧게 설정해야 함.
  • 변경 파일 후보: RedisUserSnapshotCacheAdapter.java, GetCurrentUserSnapshotService.java, BbdSecurityProperties.java(음수 TTL 설정 추가).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions