Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
9 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
import com.loopers.domain.brand.Brand;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductDetail;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import com.loopers.zset.ZSetEntry;
import com.loopers.zset.RedisZSetTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -45,12 +45,20 @@ public class RankingService {
private final RankingKeyGenerator keyGenerator;
private final ProductService productService;
private final BrandService brandService;
private final RankingSnapshotService rankingSnapshotService;

/**
* ๋žญํ‚น์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค (ํŽ˜์ด์ง•).
* <p>
* ZSET์—์„œ ์ƒ์œ„ N๊ฐœ๋ฅผ ์กฐํšŒํ•˜๊ณ , ์ƒํ’ˆ ์ •๋ณด๋ฅผ Aggregationํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
* </p>
* <p>
* <b>Graceful Degradation:</b>
* <ul>
* <li>Redis ์žฅ์•  ์‹œ ์Šค๋ƒ…์ƒท์œผ๋กœ Fallback</li>
* <li>์Šค๋ƒ…์ƒท๋„ ์—†์œผ๋ฉด ๊ธฐ๋ณธ ๋žญํ‚น(์ข‹์•„์š”์ˆœ) ์ œ๊ณต (๋‹จ์ˆœ ์กฐํšŒ, ๊ณ„์‚ฐ ์•„๋‹˜)</li>
* </ul>
* </p>
*
* @param date ๋‚ ์งœ (yyyyMMdd ํ˜•์‹์˜ ๋ฌธ์ž์—ด ๋˜๋Š” LocalDate)
* @param page ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (0๋ถ€ํ„ฐ ์‹œ์ž‘)
Expand All @@ -59,6 +67,47 @@ public class RankingService {
*/
@Transactional(readOnly = true)
public RankingsResponse getRankings(LocalDate date, int page, int size) {
try {
return getRankingsFromRedis(date, page, size);
} catch (DataAccessException e) {
log.warn("Redis ๋žญํ‚น ์กฐํšŒ ์‹คํŒจ, ์Šค๋ƒ…์ƒท์œผ๋กœ Fallback: date={}, error={}",
date, e.getMessage());
// ์Šค๋ƒ…์ƒท์œผ๋กœ Fallback ์‹œ๋„
Optional<RankingsResponse> snapshot = rankingSnapshotService.getSnapshot(date);
if (snapshot.isPresent()) {
log.info("์Šค๋ƒ…์ƒท์œผ๋กœ ๋žญํ‚น ์ œ๊ณต: date={}, itemCount={}", date, snapshot.get().items().size());
return snapshot.get();
}

// ์ „๋‚  ์Šค๋ƒ…์ƒท ์‹œ๋„
Optional<RankingsResponse> yesterdaySnapshot = rankingSnapshotService.getSnapshot(date.minusDays(1));
if (yesterdaySnapshot.isPresent()) {
log.info("์ „๋‚  ์Šค๋ƒ…์ƒท์œผ๋กœ ๋žญํ‚น ์ œ๊ณต: date={}, itemCount={}", date, yesterdaySnapshot.get().items().size());
return yesterdaySnapshot.get();
}

// ์ตœ์ข… Fallback: ๊ธฐ๋ณธ ๋žญํ‚น (๋‹จ์ˆœ ์กฐํšŒ, ๊ณ„์‚ฐ ์•„๋‹˜)
log.warn("์Šค๋ƒ…์ƒท๋„ ์—†์Œ, ๊ธฐ๋ณธ ๋žญํ‚น(์ข‹์•„์š”์ˆœ)์œผ๋กœ Fallback: date={}", date);
return getDefaultRankings(page, size);
} catch (Exception e) {
log.error("๋žญํ‚น ์กฐํšŒ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ๊ธฐ๋ณธ ๋žญํ‚น์œผ๋กœ Fallback: date={}", date, e);
return getDefaultRankings(page, size);
}
}

/**
* Redis์—์„œ ๋žญํ‚น์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ์Šค์ผ€์ค„๋Ÿฌ์—์„œ ์Šค๋ƒ…์ƒท ์ €์žฅ ์‹œ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด public์œผ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @param date ๋‚ ์งœ
* @param page ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
* @param size ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜
* @return ๋žญํ‚น ์กฐํšŒ ๊ฒฐ๊ณผ
* @throws DataAccessException Redis ์ ‘๊ทผ ์‹คํŒจ ์‹œ
*/
public RankingsResponse getRankingsFromRedis(LocalDate date, int page, int size) {
String key = keyGenerator.generateDailyKey(date);
long start = (long) page * size;
long end = start + size - 1;
Expand Down Expand Up @@ -132,18 +181,118 @@ public RankingsResponse getRankings(LocalDate date, int page, int size) {
return new RankingsResponse(rankingItems, page, size, hasNext);
}

/**
* ๊ธฐ๋ณธ ๋žญํ‚น(์ข‹์•„์š”์ˆœ)์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ์ตœ์ข… Fallback์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋žญํ‚น์„ ์ƒˆ๋กœ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ
* ์ด๋ฏธ ์ง‘๊ณ„๋œ ์ข‹์•„์š” ์ˆ˜๋ฅผ ๋‹จ์ˆœ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ DB ๋ถ€ํ•˜๊ฐ€ ํฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
* </p>
*
* @param page ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (0๋ถ€ํ„ฐ ์‹œ์ž‘)
* @param size ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜
* @return ๋žญํ‚น ์กฐํšŒ ๊ฒฐ๊ณผ
*/
private RankingsResponse getDefaultRankings(int page, int size) {
// ์ข‹์•„์š”์ˆœ์œผ๋กœ ์ƒํ’ˆ ์กฐํšŒ
List<Product> products = productService.findAll(null, "likes_desc", page, size);
long totalCount = productService.countAll(null);

if (products.isEmpty()) {
return RankingsResponse.empty(page, size);
}

// ๋ธŒ๋žœ๋“œ ID ์ˆ˜์ง‘
List<Long> brandIds = products.stream()
.map(Product::getBrandId)
.distinct()
.toList();

// ๋ธŒ๋žœ๋“œ ๋ฐฐ์น˜ ์กฐํšŒ
Map<Long, Brand> brandMap = brandService.getBrands(brandIds).stream()
.collect(Collectors.toMap(Brand::getId, brand -> brand));

// ๋žญํ‚น ํ•ญ๋ชฉ ์ƒ์„ฑ (์ข‹์•„์š” ์ˆ˜๋ฅผ ์ ์ˆ˜๋กœ ์‚ฌ์šฉ)
List<RankingItem> rankingItems = new ArrayList<>();
long start = (long) page * size;
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
Long rank = start + i + 1; // 1-based ์ˆœ์œ„

Brand brand = brandMap.get(product.getBrandId());
if (brand == null) {
log.warn("์ƒํ’ˆ์˜ ๋ธŒ๋žœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: productId={}, brandId={}",
product.getId(), product.getBrandId());
continue;
}

ProductDetail productDetail = ProductDetail.from(
product,
brand.getName(),
product.getLikeCount()
);

// ์ข‹์•„์š” ์ˆ˜๋ฅผ ์ ์ˆ˜๋กœ ์‚ฌ์šฉ
double score = product.getLikeCount() != null ? product.getLikeCount().doubleValue() : 0.0;
rankingItems.add(new RankingItem(
rank,
score,
productDetail
));
}

boolean hasNext = (start + size) < totalCount;
return new RankingsResponse(rankingItems, page, size, hasNext);
}

/**
* ํŠน์ • ์ƒํ’ˆ์˜ ์ˆœ์œ„๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ์ƒํ’ˆ์ด ๋žญํ‚น์— ์—†์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
* </p>
* <p>
* <b>Graceful Degradation:</b>
* <ul>
* <li>Redis ์žฅ์•  ์‹œ ์ „๋‚  ๋žญํ‚น์œผ๋กœ Fallback</li>
* <li>์ „๋‚  ๋žญํ‚น๋„ ์—†์œผ๋ฉด null ๋ฐ˜ํ™˜ (๊ธฐ๋ณธ ๋žญํ‚น์—์„œ๋Š” ์ˆœ์œ„ ๊ณ„์‚ฐ ๋ถˆ๊ฐ€)</li>
* </ul>
* </p>
*
* @param productId ์ƒํ’ˆ ID
* @param date ๋‚ ์งœ
* @return ์ˆœ์œ„ (1๋ถ€ํ„ฐ ์‹œ์ž‘, ์—†์œผ๋ฉด null)
*/
@Transactional(readOnly = true)
public Long getProductRank(Long productId, LocalDate date) {
try {
return getProductRankFromRedis(productId, date);
} catch (DataAccessException e) {
log.warn("Redis ์ƒํ’ˆ ์ˆœ์œ„ ์กฐํšŒ ์‹คํŒจ, ์ „๋‚  ๋žญํ‚น์œผ๋กœ Fallback: productId={}, date={}, error={}",
productId, date, e.getMessage());
// ์ „๋‚  ๋žญํ‚น์œผ๋กœ Fallback ์‹œ๋„
try {
LocalDate yesterday = date.minusDays(1);
return getProductRankFromRedis(productId, yesterday);
} catch (DataAccessException fallbackException) {
log.warn("์ „๋‚  ๋žญํ‚น ์กฐํšŒ๋„ ์‹คํŒจ: productId={}, date={}, error={}",
productId, date, fallbackException.getMessage());
// ๊ธฐ๋ณธ ๋žญํ‚น์—์„œ๋Š” ์ˆœ์œ„ ๊ณ„์‚ฐ์ด ์–ด๋ ค์šฐ๋ฏ€๋กœ null ๋ฐ˜ํ™˜
return null;
}
} catch (Exception e) {
log.error("์ƒํ’ˆ ์ˆœ์œ„ ์กฐํšŒ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: productId={}, date={}", productId, date, e);
return null;
}
}

/**
* Redis์—์„œ ์ƒํ’ˆ ์ˆœ์œ„๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
*
* @param productId ์ƒํ’ˆ ID
* @param date ๋‚ ์งœ
* @return ์ˆœ์œ„ (1๋ถ€ํ„ฐ ์‹œ์ž‘, ์—†์œผ๋ฉด null)
* @throws DataAccessException Redis ์ ‘๊ทผ ์‹คํŒจ ์‹œ
*/
private Long getProductRankFromRedis(Long productId, LocalDate date) {
String key = keyGenerator.generateDailyKey(date);
Long rank = zSetTemplate.getRank(key, String.valueOf(productId));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.loopers.application.ranking;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
* ๋žญํ‚น ์Šค๋ƒ…์ƒท ์„œ๋น„์Šค.
* <p>
* Redis ์žฅ์•  ์‹œ Fallback์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋žญํ‚น ๋ฐ์ดํ„ฐ ์Šค๋ƒ…์ƒท์„ ์ธ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
* </p>
* <p>
* <b>์„ค๊ณ„ ์›์น™:</b>
* <ul>
* <li>์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ: ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ์„ฑ๋Šฅ์ด ์šฐ์ˆ˜ํ•จ</li>
* <li>๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ: ์ตœ๊ทผ 7์ผ์น˜๋งŒ ๋ณด๊ด€ํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ</li>
* <li>์Šค๋ƒ…์ƒท ๊ธฐ๋ฐ˜ Fallback: DB ์‹ค์‹œ๊ฐ„ ์žฌ๊ณ„์‚ฐ ๋Œ€์‹  ์Šค๋ƒ…์ƒท ์„œ๋น™์œผ๋กœ DB ๋ถ€ํ•˜ ๋ฐฉ์ง€</li>
* </ul>
* </p>
*
* @author Loopers
* @version 1.0
*/
@Slf4j
@Service
public class RankingSnapshotService {

private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final int MAX_SNAPSHOTS = 7; // ์ตœ๊ทผ 7์ผ์น˜๋งŒ ๋ณด๊ด€

private final Map<String, RankingService.RankingsResponse> snapshotCache = new ConcurrentHashMap<>();

/**
* ๋žญํ‚น ์Šค๋ƒ…์ƒท์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
*
* @param date ๋‚ ์งœ
* @param rankings ๋žญํ‚น ์กฐํšŒ ๊ฒฐ๊ณผ
*/
public void saveSnapshot(LocalDate date, RankingService.RankingsResponse rankings) {
String key = date.format(DATE_FORMATTER);
snapshotCache.put(key, rankings);
log.debug("๋žญํ‚น ์Šค๋ƒ…์ƒท ์ €์žฅ: date={}, key={}, itemCount={}", date, key, rankings.items().size());

// ์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท ์ •๋ฆฌ (๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ)
cleanupOldSnapshots();
}

/**
* ๋žญํ‚น ์Šค๋ƒ…์ƒท์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
*
* @param date ๋‚ ์งœ
* @return ๋žญํ‚น ์กฐํšŒ ๊ฒฐ๊ณผ (์—†์œผ๋ฉด empty)
*/
public Optional<RankingService.RankingsResponse> getSnapshot(LocalDate date) {
String key = date.format(DATE_FORMATTER);
RankingService.RankingsResponse snapshot = snapshotCache.get(key);

if (snapshot != null) {
log.debug("๋žญํ‚น ์Šค๋ƒ…์ƒท ์กฐํšŒ ์„ฑ๊ณต: date={}, key={}, itemCount={}", date, key, snapshot.items().size());
return Optional.of(snapshot);
}

log.debug("๋žญํ‚น ์Šค๋ƒ…์ƒท ์—†์Œ: date={}, key={}", date, key);
return Optional.empty();
}

/**
* ์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ์ตœ๊ทผ 7์ผ์น˜๋งŒ ๋ณด๊ด€ํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.
* </p>
*/
private void cleanupOldSnapshots() {
if (snapshotCache.size() <= MAX_SNAPSHOTS) {
return;
}

// ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท ์ œ๊ฑฐ
LocalDate today = LocalDate.now(ZoneId.of("UTC"));
LocalDate oldestDate = today.minusDays(MAX_SNAPSHOTS);

snapshotCache.entrySet().removeIf(entry -> {
try {
LocalDate entryDate = LocalDate.parse(entry.getKey(), DATE_FORMATTER);
boolean shouldRemove = entryDate.isBefore(oldestDate);
if (shouldRemove) {
log.debug("์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท ์ œ๊ฑฐ: key={}", entry.getKey());
}
return shouldRemove;
} catch (Exception e) {
log.warn("์Šค๋ƒ…์ƒท ํ‚ค ํŒŒ์‹ฑ ์‹คํŒจ, ์ œ๊ฑฐ: key={}", entry.getKey(), e);
return true; // ํŒŒ์‹ฑ ์‹คํŒจํ•œ ํ‚ค๋Š” ์ œ๊ฑฐ
}
});
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.loopers.infrastructure.scheduler;

import com.loopers.application.ranking.RankingService;
import com.loopers.application.ranking.RankingSnapshotService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.ZoneId;

/**
* ๋žญํ‚น ์Šค๋ƒ…์ƒท ์ €์žฅ ์Šค์ผ€์ค„๋Ÿฌ.
* <p>
* ์ฃผ๊ธฐ์ ์œผ๋กœ ๋žญํ‚น ๊ฒฐ๊ณผ๋ฅผ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅํ•˜์—ฌ, Redis ์žฅ์•  ์‹œ Fallback์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
* </p>
* <p>
* <b>์„ค๊ณ„ ์›์น™:</b>
* <ul>
* <li>์Šค๋ƒ…์ƒท ๊ธฐ๋ฐ˜ Fallback: DB ์‹ค์‹œ๊ฐ„ ์žฌ๊ณ„์‚ฐ ๋Œ€์‹  ์Šค๋ƒ…์ƒท ์„œ๋น™์œผ๋กœ DB ๋ถ€ํ•˜ ๋ฐฉ์ง€</li>
* <li>์ฃผ๊ธฐ์  ์ €์žฅ: 1์‹œ๊ฐ„๋งˆ๋‹ค ์ตœ์‹  ๋žญํ‚น์„ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅ</li>
* <li>์—๋Ÿฌ ์ฒ˜๋ฆฌ: ์Šค๋ƒ…์ƒท ์ €์žฅ ์‹คํŒจ ์‹œ์—๋„ ๋‹ค์Œ ์Šค์ผ€์ค„์—์„œ ์žฌ์‹œ๋„</li>
* </ul>
* </p>
* <p>
* <b>์ฃผ๊ธฐ ์„ ํƒ ๊ทผ๊ฑฐ:</b>
* <ul>
* <li>๋น„์šฉ ๋Œ€๋น„ ํšจ๊ณผ: 1์‹œ๊ฐ„ ์ฃผ๊ธฐ๊ฐ€ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰์ด 1/12๋กœ ๊ฐ์†Œํ•˜๋ฉด์„œ๋„ ์‚ฌ์šฉ์ž ์ฒด๊ฐ ์ฐจ์ด๋Š” ๊ฑฐ์˜ ์—†์Œ</li>
* <li>๋žญํ‚น์˜ ์„ฑ๊ฒฉ: ๋น„์ฆˆ๋‹ˆ์Šค ๊ฒฐ์ •์ด ์•„๋‹Œ ์กฐํšŒ์šฉ ํŒŒ์ƒ ๋ฐ์ดํ„ฐ์ด๋ฏ€๋กœ 1์‹œ๊ฐ„ ์ „ ๋ฐ์ดํ„ฐ๋„ ์ถฉ๋ถ„ํžˆ ์œ ์šฉํ•จ</li>
* <li>์šด์˜ ๊ด€์ : ์Šค์ผ€์ค„๋Ÿฌ ์‹คํ–‰ ๋นˆ๋„๊ฐ€ ๋‚ฎ์•„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ถ€๋‹ด ๊ฐ์†Œ</li>
* </ul>
* </p>
*
* @author Loopers
* @version 1.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RankingSnapshotScheduler {

private final RankingService rankingService;
private final RankingSnapshotService rankingSnapshotService;

/**
* ๋žญํ‚น ์Šค๋ƒ…์ƒท์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
* <p>
* 1์‹œ๊ฐ„๋งˆ๋‹ค ์‹คํ–‰๋˜์–ด ์˜ค๋Š˜์˜ ๋žญํ‚น์„ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
* </p>
*/
@Scheduled(fixedRate = 3600000) // 1์‹œ๊ฐ„๋งˆ๋‹ค (3600000ms = 1์‹œ๊ฐ„)
public void saveRankingSnapshot() {
LocalDate today = LocalDate.now(ZoneId.of("UTC"));
try {
// ์ƒ์œ„ 100๊ฐœ ๋žญํ‚น์„ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅ (๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์œ„ 100๊ฐœ ์ด๋‚ด๋งŒ ์กฐํšŒ)
// Redis๊ฐ€ ์ •์ƒ์ผ ๋•Œ๋งŒ ์Šค๋ƒ…์ƒท ์ €์žฅ (์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์Šคํ‚ต)
RankingService.RankingsResponse rankings = rankingService.getRankingsFromRedis(today, 0, 100);

rankingSnapshotService.saveSnapshot(today, rankings);

log.debug("๋žญํ‚น ์Šค๋ƒ…์ƒท ์ €์žฅ ์™„๋ฃŒ: date={}, itemCount={}", today, rankings.items().size());
} catch (org.springframework.dao.DataAccessException e) {
log.warn("Redis ์žฅ์• ๋กœ ์ธํ•œ ๋žญํ‚น ์Šค๋ƒ…์ƒท ์ €์žฅ ์‹คํŒจ: date={}, error={}", today, e.getMessage());
// Redis ์žฅ์•  ์‹œ ์Šค๋ƒ…์ƒท ์ €์žฅ ์Šคํ‚ต (๋‹ค์Œ ์Šค์ผ€์ค„์—์„œ ์žฌ์‹œ๋„)
} catch (Exception e) {
log.warn("๋žญํ‚น ์Šค๋ƒ…์ƒท ์ €์žฅ ์‹คํŒจ: date={}", today, e);
// ์Šค๋ƒ…์ƒท ์ €์žฅ ์‹คํŒจ๋Š” ๋‹ค์Œ ์Šค์ผ€์ค„์—์„œ ์žฌ์‹œ๋„
}
}
}

Loading