-
Notifications
You must be signed in to change notification settings - Fork 2
서버가 동기화 될 때 Pre-Warm Redis 정보가 유실 대응 및 Miss 시 대응 전략 도입 #195
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
8d10873
e3786b6
1bfb5d1
0a8affc
9b9f023
3f8ed46
40ba56c
bad7294
c79bfed
9e72062
4d9804f
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,51 @@ | ||
| package life.mosu.mosuserver.application.exam.cache; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.stream.Collectors; | ||
| import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; | ||
| import life.mosu.mosuserver.domain.exam.entity.ExamJpaRepository; | ||
| import life.mosu.mosuserver.domain.exam.projection.SchoolExamCountProjection; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class ExamQuotaLoadService { | ||
|
|
||
| private final ExamJpaRepository examJpaRepository; | ||
| private final ExamQuotaCacheManager examQuotaCacheManager; | ||
|
|
||
| @Async | ||
| @Transactional(readOnly = true) | ||
| public CompletableFuture<Void> loadMaxCapacities() { | ||
| var maxCapacities = examJpaRepository.findByExamDateAfter(LocalDate.now()) | ||
| .stream() | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.toMap( | ||
| ExamJpaEntity::getId, | ||
| e -> e.getCapacity().longValue() | ||
| )); | ||
| examQuotaCacheManager.loadMaxCapacities(maxCapacities); | ||
| return CompletableFuture.completedFuture(null); | ||
| } | ||
|
|
||
| @Async | ||
| @Transactional(readOnly = true) | ||
| public CompletableFuture<Void> loadCurrentApplications() { | ||
| var currentApplications = examJpaRepository.countApplicationsGroupedByExamId() | ||
| .stream() | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.toMap( | ||
| SchoolExamCountProjection::examId, | ||
| SchoolExamCountProjection::applicationCount | ||
| )); | ||
| examQuotaCacheManager.loadCurrentApplications(currentApplications); | ||
| return CompletableFuture.completedFuture(null); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package life.mosu.mosuserver.application.exam.cache; | ||
|
|
||
| public enum OperationType { | ||
| INCREMENT, DECREMENT; | ||
|
|
||
| public String key() { | ||
| return this.name().toLowerCase(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package life.mosu.mosuserver.application.exam.initializer; | ||
|
|
||
| import java.util.concurrent.CompletableFuture; | ||
| import life.mosu.mosuserver.application.exam.cache.ExamQuotaLoadService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.boot.ApplicationArguments; | ||
| import org.springframework.boot.ApplicationRunner; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class ExamQuotaCacheInitializer implements ApplicationRunner { | ||
|
|
||
| private final ExamQuotaLoadService loadService; | ||
|
|
||
| /** | ||
| * 병렬 실행으로 초기 Cache 로드 속도 최적화 | ||
| */ | ||
| @Override | ||
| public void run(ApplicationArguments args) { | ||
| CompletableFuture<Void> maxCapFuture = loadService.loadMaxCapacities(); | ||
| CompletableFuture<Void> currAppFuture = loadService.loadCurrentApplications(); | ||
|
|
||
| CompletableFuture.allOf(maxCapFuture, currAppFuture).join(); | ||
| } | ||
|
Comment on lines
+20
to
+25
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. 🛠️ Refactor suggestion Consider adding timeout and error handling for startup robustness. The current implementation blocks indefinitely on Consider adding timeout and graceful error handling: @Override
public void run(ApplicationArguments args) {
+ try {
CompletableFuture<Void> maxCapFuture = loadService.loadMaxCapacities();
CompletableFuture<Void> currAppFuture = loadService.loadCurrentApplications();
- CompletableFuture.allOf(maxCapFuture, currAppFuture).join();
+ CompletableFuture.allOf(maxCapFuture, currAppFuture)
+ .orTimeout(30, TimeUnit.SECONDS)
+ .join();
+ log.info("Cache initialization completed successfully");
+ } catch (Exception e) {
+ log.error("Cache initialization failed, application may have degraded performance", e);
+ // Consider whether to fail fast or continue with empty cache
+ }
}You'll need to add these imports: import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;🤖 Prompt for AI Agents |
||
| } | ||
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.
While
CompletableFuture.allOf(...).join()correctly waits for initialization and propagates exceptions, it would be beneficial for maintainability and debugging to wrap this call in a try-catch block. This allows for explicit logging of success or failure of the cache initialization process. A clear log message upon failure can significantly speed up diagnosing startup issues.For example: