-
Notifications
You must be signed in to change notification settings - Fork 0
[DEPLOY] production-release(v.0.3.3) #377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3254a06
ea569c0
03e21f1
1e1fa0e
56f259f
c82af3f
1e75b57
c92eecf
0620fc4
326942d
e90e6b8
d8b4a12
49ba5f0
55a8d1c
4cb0248
a0921f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package issueissyu.backend.domain.map.cache; | ||
|
|
||
| import issueissyu.backend.domain.location.repository.PinLocationRepository; | ||
| import issueissyu.backend.domain.map.dto.res.MapPinView; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.boot.context.event.ApplicationReadyEvent; | ||
| import org.springframework.context.event.EventListener; | ||
| import org.springframework.core.annotation.Order; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| // ์๋ฒ ์์ ์ DB์ ํ์ฑ ํ์ Redis GEO ์บ์์ ์ ์ฒด ์ ์ฌํฉ๋๋ค. | ||
| // {@link ApplicationReadyEvent} ์ดํ ๋น๋๊ธฐ๋ก ์คํ๋์ด ์๋ฒ ๊ธฐ๋ ์ง์ฐ์ด ์์ต๋๋ค. | ||
| // ์ด๊ธฐํ ๋์ค ํ ์กฐํ API ํธ์ถ์ด ์ค๋ฉด isGeoSetReady() == false ์ด๋ฏ๋ก DB์์ ์๋ตํฉ๋๋ค. | ||
|
|
||
| // ๋ค์ค ์ธ์คํด์ค ํ๊ฒฝ ๊ณ ๋ ค์ฌํญ: | ||
| // - ๋กค๋ง ๋ฐฐํฌ์ฒ๋ผ ์ธ์คํด์ค๊ฐ ์์ฐจ์ ์ผ๋ก ๊ธฐ๋๋๋ ๊ฒฝ์ฐ, geo:pins:ready ํค๋ก ์ค๋ณต ์ด๊ธฐํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค. | ||
| // - ์ธ์คํด์ค๊ฐ ์์ ํ ๋์์ ๊ธฐ๋๋๋๋ผ๋ bulkPopulate ๋ ๋ฉฑ๋ฑ(idempotent)ํ๊ฒ ์ค๊ณ๋์ด | ||
| // ๋์ผ ๋ฐ์ดํฐ๋ฅผ ์ค๋ณต ์ ์ฌํ ๋ฟ ๋ฐ์ดํฐ ์์์ ๋ฐ์ํ์ง ์์ต๋๋ค. | ||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| 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()); | ||
| } | ||
| } | ||
| } | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package issueissyu.backend.domain.map.cache; | ||
|
|
||
| import issueissyu.backend.domain.location.repository.PinLocationRepository; | ||
| import issueissyu.backend.domain.map.dto.res.MapPinView; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.scheduling.annotation.Scheduled; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.List; | ||
|
|
||
| // Redis GEO ์บ์ ์ ํฉ์ฑ ์ ์ง๋ฅผ ์ํ ์ผ๊ฐ ๋ฐฐ์น ์ค์ผ์ค๋ฌ. | ||
|
|
||
| // ๋ค์ค ์๋ฒ ํ๊ฒฝ์์ ๋ชจ๋ ์ธ์คํด์ค๊ฐ ๋์์ ์คํ๋๋ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด | ||
| // Redis SET NX PX ๊ธฐ๋ฐ ๋ถ์ฐ ๋ฝ์ ์ฌ์ฉํฉ๋๋ค. | ||
| // ๋ฝ์ ํ๋ํ ์ธ์คํด์ค๋ง ์ฌ๊ตฌ์ฑ์ ์ํํ๊ณ , ๋๋จธ์ง๋ ์ฆ์ ๊ฑด๋๋๋๋ค. | ||
| // ๋ฝ TTL ์ด ๋ง๋ฃ๋๋ฉด (ํ๋ก์ธ์ค ๋น์ ์ ์ข ๋ฃ ๋ฑ) ๋ค์ ์คํ ์ ์๋์ผ๋ก ํ๋ ๊ฐ๋ฅํฉ๋๋ค. | ||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class PinGeoScheduler { | ||
|
|
||
| private static final String REBUILD_LOCK_KEY = "lock:geo:rebuild"; | ||
| // ์ฌ๊ตฌ์ฑ ์์ ์์ ์๊ฐ๋ณด๋ค ์ถฉ๋ถํ ๊ธธ๊ฒ ์ค์ (๋น์ ์ ์ข ๋ฃ ์ ์๋ ๋ง๋ฃ์ฉ) | ||
| private static final Duration REBUILD_LOCK_TTL = Duration.ofMinutes(10); | ||
|
|
||
| private final PinLocationRepository pinLocationRepository; | ||
| private final PinGeoRedisService pinGeoRedisService; | ||
| private final RedisTemplate<String, String> redisTemplate; | ||
|
|
||
| @Scheduled(cron = "0 0 5 * * *", zone = "Asia/Seoul") | ||
| public void rebuildGeoCache() { | ||
| // SET lock:geo:rebuild 1 NX PX 600000 โ ์์์ ๋ช ๋ น์ผ๋ก ๋ ์ด์ค ์ปจ๋์ ์์ | ||
| Boolean acquired = redisTemplate.opsForValue() | ||
| .setIfAbsent(REBUILD_LOCK_KEY, "1", REBUILD_LOCK_TTL); | ||
| if (!Boolean.TRUE.equals(acquired)) { | ||
| log.info("[Redis GEO] ๋ค๋ฅธ ์ธ์คํด์ค๊ฐ ์ผ๊ฐ ์บ์ ์ฌ๊ตฌ์ฑ ์ค์ด๋ฏ๋ก ๊ฑด๋๋๋๋ค."); | ||
| return; | ||
| } | ||
| try { | ||
| log.info("[Redis GEO] ์ผ๊ฐ ์บ์ ์ฌ๊ตฌ์ฑ ์์ ..."); | ||
| List<MapPinView> views = pinLocationRepository.findAllActivePins(); | ||
| pinGeoRedisService.bulkPopulate(views); | ||
| log.info("[Redis GEO] ์ผ๊ฐ ์บ์ ์ฌ๊ตฌ์ฑ ์๋ฃ: {}๊ฐ ํ", views.size()); | ||
| } catch (Exception e) { | ||
| log.error("[Redis GEO] ์ผ๊ฐ ์บ์ ์ฌ๊ตฌ์ฑ ์คํจ: {}", e.getMessage(), e); | ||
| } finally { | ||
| // ์ ์ยท๋น์ ์ ์ข ๋ฃ ๋ชจ๋ ๋ฝ ์ฆ์ ํด์ (๋ค์ ์์ฝ ์คํ๊น์ง ๊ธฐ๋ค๋ฆด ํ์ ์์) | ||
| redisTemplate.delete(REBUILD_LOCK_KEY); | ||
| } | ||
| } | ||
|
Comment on lines
+47
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ผ๊ฐ ๋ฐฐ์น ์ค์ผ์ค๋ฌ๊ฐ ์คํ๋ ํ } catch (Exception e) {\n log.error(\"[Redis GEO] ์ผ๊ฐ ์บ์ ์ฌ๊ตฌ์ฑ ์คํจ: {}\", e.getMessage(), e);\n }\n }\n} |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package issueissyu.backend.domain.map.dto.res; | ||
|
|
||
| public record MapPinCacheStatusResDTO(long geoPinCount) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package issueissyu.backend.domain.map.service.query; | ||
|
|
||
| import issueissyu.backend.domain.map.dto.res.MapPinCacheStatusResDTO; | ||
|
|
||
| public interface MapPinCacheQueryService { | ||
|
|
||
| MapPinCacheStatusResDTO getCacheStatus(String uid); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package issueissyu.backend.domain.map.service.query; | ||
|
|
||
| import issueissyu.backend.domain.map.cache.PinGeoRedisService; | ||
| import issueissyu.backend.domain.map.dto.res.MapPinCacheStatusResDTO; | ||
| import issueissyu.backend.domain.map.exception.MapException; | ||
| import issueissyu.backend.domain.map.exception.code.MapErrorCode; | ||
| import issueissyu.backend.domain.user.entity.User; | ||
| import issueissyu.backend.domain.user.enums.UserRole; | ||
| import issueissyu.backend.domain.user.repository.UserRepository; | ||
| import issueissyu.backend.global.api.code.GeneralErrorCode; | ||
| import issueissyu.backend.global.exception.GeneralException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional(readOnly = true) | ||
| public class MapPinCacheQueryServiceImpl implements MapPinCacheQueryService { | ||
|
|
||
| private final UserRepository userRepository; | ||
| private final PinGeoRedisService pinGeoRedisService; | ||
|
|
||
| @Override | ||
| public MapPinCacheStatusResDTO getCacheStatus(String uid) { | ||
| User user = userRepository.findById(uid) | ||
| .orElseThrow(() -> GeneralException.of(GeneralErrorCode.USER_NOT_FOUND)); | ||
| if (user.getRole() != UserRole.ADMIN) { | ||
| throw MapException.of(MapErrorCode.MAP_CACHE_403); | ||
| } | ||
| return new MapPinCacheStatusResDTO(pinGeoRedisService.getGeoPinCount()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ฌ๋ฌ ์ธ์คํด์ค๊ฐ ๋์์ ๊ธฐ๋๋๋ ํ๊ฒฝ(์: ๋ฌด์ค๋จ ๋ฐฐํฌ ์ค ๋ค์ค ์ปจํ ์ด๋ ๊ธฐ๋)์์
isGeoSetReady()์ฒดํฌ์bulkPopulate()์คํ ์ฌ์ด์ ๋ ์ด์ค ์ปจ๋์ (Race Condition)์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ ์ธ์คํด์ค๊ฐ ๊ฑฐ์ ๋์์isGeoSetReady()๋ฅผfalse๋ก ํ๋จํ๋ฉด, ๋ ๋คbulkPopulate()๋ฅผ ํธ์ถํ๊ฒ ๋๊ณ ๋ด๋ถ์ ์ผ๋กclearGeoKeys()๊ฐ ์คํ๋์ด ์บ์๊ฐ ๋น์์ง๊ฑฐ๋ ๋ฐ์ดํฐ๊ฐ ์ ์ค๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.\n\n์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ด๊ธฐํ ์์ ์๋ Redis ๋ถ์ฐ ๋ฝ์ ํ์ฉํ์ฌ ๋จ ํ๋์ ์ธ์คํด์ค๋ง ์ด๊ธฐํ ์์ ์ ์ํํ๋๋ก ๋ณด์ฅํ๋ ๊ฒ์ด ์์ ํฉ๋๋ค.