Skip to content

[DEPLOY] production-release(v.0.3.3)#377

Merged
taerimiiii merged 16 commits into
mainfrom
develop
Jun 18, 2026
Merged

[DEPLOY] production-release(v.0.3.3)#377
taerimiiii merged 16 commits into
mainfrom
develop

Conversation

@taerimiiii

@taerimiiii taerimiiii commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🔗 Related Issue

✨ 작업 개요

작업 내용을 간략하게 작성해주세요.

찐막 v.0.3.3 버전 배포합니다~!

체크리스트

  • Reviewers, Assignees, Labels를 모두 등록했나요?
  • .gitignore 설정을 하였나요?
  • PR 머지 전 반드시 CI가 정상적으로 작동하는지 확인해주세요!

📷 이미지 첨부 (선택)

  • 작업 결과를 확인할 수 있는 이미지나 GIF를 첨부해주세요.
  • UI 변경, API 응답 샘플, 테스트 결과 등이 포함되면 좋아요!

🧐 집중 리뷰 요청

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

…te() 완료 시 SET geo:pins:ready 1을 다른 인스턴스에 전달하며, 삭제도 함께 함
@taerimiiii taerimiiii self-assigned this Jun 18, 2026
@taerimiiii taerimiiii added the 🚀 deploy 배포 label Jun 18, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a Redis GEO caching mechanism for map pins, including initializer and scheduler components to manage cache population and nightly rebuilds. It also integrates cache-aside logic for bounding box queries and clustering, adds an admin endpoint to monitor cache status, and updates the user solver repository query. The reviewer feedback highlights three key areas for improvement: addressing a startup race condition in the initializer using a Redis lock, optimizing performance by reducing frequent ZCARD calls in isGeoSetReady(), and preventing duplicate scheduler runs due to clock drift by letting the rebuild lock expire naturally instead of deleting it immediately.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +26 to +50
public class PinGeoRedisInitializer {

private final PinLocationRepository pinLocationRepository;
private final PinGeoRedisService pinGeoRedisService;

@Async
@Order(1)
@EventListener(ApplicationReadyEvent.class)
public void initialize() {
try {
// geo:pins:ready 키가 존재하면 다른 인스턴스가 이미 캐시를 적재한 상태입니다.
// 롤링 배포 등 순차 기동 환경에서 불필요한 DB 조회와 Redis 중복 적재를 방지합니다.
if (pinGeoRedisService.isGeoSetReady()) {
log.info("[Redis GEO] 캐시가 이미 초기화되어 있어 초기화를 건너뜁니다.");
return;
}
log.info("[Redis GEO] 캐시 초기화 시작 ...");
List<MapPinView> views = pinLocationRepository.findAllActivePins();
pinGeoRedisService.bulkPopulate(views);
log.info("[Redis GEO] 캐시 초기화 완료: {}개 핀 적재", views.size());
} catch (Exception e) {
log.warn("[Redis GEO] 캐시 초기화 실패 (서버는 정상 기동, DB 폴백 사용): {}", e.getMessage());
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

여러 인스턴스가 동시에 기동되는 환경(예: 무중단 배포 중 다중 컨테이너 기동)에서 isGeoSetReady() 체크와 bulkPopulate() 실행 사이에 레이스 컨디션(Race Condition)이 발생할 수 있습니다. 두 인스턴스가 거의 동시에 isGeoSetReady()false로 판단하면, 둘 다 bulkPopulate()를 호출하게 되고 내부적으로 clearGeoKeys()가 실행되어 캐시가 비워지거나 데이터가 유실되는 문제가 발생할 수 있습니다.\n\n이를 방지하기 위해 초기화 시점에도 Redis 분산 락을 활용하여 단 하나의 인스턴스만 초기화 작업을 수행하도록 보장하는 것이 안전합니다.

public class PinGeoRedisInitializer {\n\n    private final PinLocationRepository pinLocationRepository;\n    private final PinGeoRedisService pinGeoRedisService;\n    private final org.springframework.data.redis.core.RedisTemplate<String, String> redisTemplate;\n\n    @Async\n    @Order(1)\n    @EventListener(ApplicationReadyEvent.class)\n    public void initialize() {\n        try {\n            if (pinGeoRedisService.isGeoSetReady()) {\n                log.info(\"[Redis GEO] 캐시가 이미 초기화되어 있어 초기화를 건너뜁니다.\");\n                return;\n            }\n\n            Boolean acquired = redisTemplate.opsForValue().setIfAbsent(\"lock:geo:init\", \"1\", java.time.Duration.ofMinutes(5));\n            if (!Boolean.TRUE.equals(acquired)) {\n                log.info(\"[Redis GEO] 다른 인스턴스가 이미 초기화를 진행 중이므로 건너뜁니다.\");\n                return;\n            }\n\n            try {\n                log.info(\"[Redis GEO] 캐시 초기화 시작 ...\");\n                List<MapPinView> views = pinLocationRepository.findAllActivePins();\n                pinGeoRedisService.bulkPopulate(views);\n                log.info(\"[Redis GEO] 캐시 초기화 완료: {}개 핀 적재\", views.size());\n            } finally {\n                redisTemplate.delete(\"lock:geo:init\");\n            }\n        } catch (Exception e) {\n            log.warn(\"[Redis GEO] 캐시 초기화 실패 (서버는 정상 기동, DB 폴백 사용): {}\", e.getMessage());\n        }\n    }\n}

Comment on lines +136 to +151
public boolean isGeoSetReady() {
if (isReady) {
// 로컬 플래그가 true 라도 재구성 중 GEO Set 이 비어 있으면 DB 폴백합니다.
return getGeoPinCount() > 0;
}
// 로컬 플래그 미설정 → 다른 인스턴스가 이미 적재했는지 Redis 글로벌 키로 확인합니다.
try {
if (Boolean.TRUE.equals(redisTemplate.hasKey(GEO_READY_KEY)) && getGeoPinCount() > 0) {
isReady = true;
return true;
}
} catch (Exception e) {
log.warn("Redis GEO 상태 확인 실패: {}", e.getMessage());
}
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

현재 isGeoSetReady() 메서드는 로컬 isReady 플래그가 true이더라도 매 요청마다 Redis에 ZCARD 명령(getGeoPinCount())을 동기적으로 호출하여 데이터 존재 여부를 확인하고 있습니다.\n\n이 방식은 캐시 재구성 중 DB 폴백을 정확하게 처리할 수 있지만, 트래픽이 몰릴 때 모든 지도 조회 API 요청마다 Redis 네트워크 왕복(RTT)이 발생하여 성능 저하 및 Redis 부하를 유발합니다.\n\n개선 방안:\n로컬에서 isReady 상태를 판단할 때, Redis 조회 결과를 일정 시간(예: 5초~10초) 동안 로컬 캐싱하거나, 캐시 재구성 시작 시 Redis Pub/Sub 등을 통해 다른 인스턴스에 알리는 방식을 고려해 주세요. 최소한 매번 ZCARD를 호출하는 대신 로컬 플래그를 최대한 신뢰하고, 주기적으로만 Redis 상태를 동기화하는 것이 효율적입니다.

Comment on lines +47 to +53
} catch (Exception e) {
log.error("[Redis GEO] 야간 캐시 재구성 실패: {}", e.getMessage(), e);
} finally {
// 정상·비정상 종료 모두 락 즉시 해제 (다음 예약 실행까지 기다릴 필요 없음)
redisTemplate.delete(REBUILD_LOCK_KEY);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

야간 배치 스케줄러가 실행된 후 finally 블록에서 락을 즉시 해제(redisTemplate.delete)하고 있습니다. 다중 서버 환경에서 서버 간의 미세한 시간 차이(Clock Drift)가 존재할 경우, 먼저 시작한 서버가 작업을 빠르게 끝내고 락을 해제하면, 약간 늦게 기동된 다른 서버가 다시 락을 획득하여 중복으로 캐시 재구성을 실행할 수 있습니다.\n\n야간 배치는 하루에 한 번만 실행되므로, finally 블록에서 락을 수동으로 삭제하지 않고 REBUILD_LOCK_TTL (10분) 동안 자연 만료되도록 두는 것이 안전합니다. 이렇게 하면 시간 차이가 수 초~수 분 나더라도 중복 실행을 확실히 방지할 수 있습니다.

        } catch (Exception e) {\n            log.error(\"[Redis GEO] 야간 캐시 재구성 실패: {}\", e.getMessage(), e);\n        }\n    }\n}

@taerimiiii taerimiiii merged commit df1b0dc into main Jun 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant