-
Notifications
You must be signed in to change notification settings - Fork 2
[refactor] 동시성 이슈 #202
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
[refactor] 동시성 이슈 #202
Changes from 8 commits
6bf63e6
ce8d8b9
76b60f0
f91da9c
5392572
a4760b8
3c91432
ba634f5
5871614
9bff600
12a3d3c
be58d45
6e27397
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,33 @@ | ||
| package com.pitchain.common.redis; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.redis.core.HashOperations; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.data.redis.core.script.RedisScript; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class RedisHashRepository { | ||
|
|
||
| private final RedisTemplate<String, String> redisTemplate; | ||
| private final RedisScript<List> script; | ||
|
|
||
| public void increment(String key, String hashKey, Long value) { | ||
| redisTemplate.opsForHash().increment(key, hashKey, value); | ||
| } | ||
|
|
||
| public Map<String, String> findAll(String key) { | ||
| HashOperations<String, String, String> ops = redisTemplate.opsForHash(); | ||
| return ops.entries(key); | ||
| } | ||
|
|
||
| public List<String> getAndDeleteAll(String key) { | ||
| return redisTemplate.execute(script, Collections.singletonList(key)); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package com.pitchain.sp.application; | ||
|
|
||
| import com.pitchain.common.redis.RedisHashRepository; | ||
| import com.pitchain.sp.infrastucture.SpRepositoryCustom; | ||
| import com.pitchain.sp.infrastucture.dto.SpViewsDto; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.scheduling.annotation.Scheduled; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class SpViewsService { | ||
|
|
||
| private final SpRepositoryCustom spRepositoryCustom; | ||
| private final RedisHashRepository redisHashRepository; | ||
|
|
||
| private static final String SP_VIEW_REDIS_KEY = "spView"; | ||
|
|
||
| /** | ||
| * Redis에 Sp 조회수 증가 | ||
| * @param spId | ||
| */ | ||
| public void updateSpView(Long spId) { | ||
| redisHashRepository.increment(SP_VIEW_REDIS_KEY, String.valueOf(spId), 1L); | ||
| } | ||
|
|
||
| /** | ||
| * Redis에서 DB로 Sp 조회수 업데이트 | ||
| */ | ||
| @Transactional | ||
| public void updateSpViews() { | ||
| List<String> spViewsResult = redisHashRepository.getAndDeleteAll(SP_VIEW_REDIS_KEY); | ||
| List<SpViewsDto> spViewsDtoList = parseResult(spViewsResult); | ||
|
|
||
| for (SpViewsDto spViewsDto : spViewsDtoList) { | ||
| spRepositoryCustom.updateSpView(spViewsDto.spId(), spViewsDto.views()); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 1분마다 Redis에서 DB로 Sp 조회수 업데이트하는 작업 수행 | ||
| */ | ||
| @Scheduled(cron = "0 */1 * * * *") | ||
| public void runUpdateSpViews() { | ||
| try { | ||
| updateSpViews(); | ||
| } catch (Exception e) { | ||
| log.error("Scheduling task [runUpdateSpViews] failed", e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Redis에서 가져온 Sp 조회수 String 리스트를 Dto 리스트로 파싱 | ||
| * @param List<String> | ||
| * @return List<SpViewsDto> | ||
| */ | ||
| private List<SpViewsDto> parseResult(List<String> spViewsStringList) { | ||
| if (spViewsStringList.size() % 2 != 0){ | ||
| log.error("spViewsStringList 개수가 올바르지 않습니다."); | ||
| throw new IllegalArgumentException("spViewsStringList 개수가 올바르지 않습니다."); | ||
| } | ||
|
|
||
| List<SpViewsDto> spViewsDtoList = new ArrayList<>(); | ||
| for (int i = 0; i < spViewsStringList.size(); i += 2) { | ||
| Long spId = Long.parseLong(spViewsStringList.get(i)); | ||
| Long views = Long.parseLong(spViewsStringList.get(i + 1)); | ||
|
|
||
| SpViewsDto spViewsDto = new SpViewsDto(spId, views); | ||
| spViewsDtoList.add(spViewsDto); | ||
| } | ||
|
|
||
| return spViewsDtoList; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package com.pitchain.sp.infrastucture.dto; | ||
|
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. 💡 Verification agent 🧩 Analysis chain패키지명 오타 확인 요청 패키지명에 "infrastucture"가 "infrastructure"의 오타로 보입니다. 프로젝트 전체에서 일관된 명명 규칙을 사용하고 있는지 확인해 주세요. 🏁 Script executed: #!/bin/bash
# 프로젝트 내 infrastructure/infrastucture 패키지명 사용 현황 확인
fd -t d "infrastucture"
fd -t d "infrastructure"Length of output: 771 패키지명 철자 통일 필요 프로젝트에 com.pitchain.* 아래에 영향 받는 오타 패키지 경로:
🤖 Prompt for AI Agents |
||
|
|
||
| public record SpViewsDto(Long spId, Long views) { | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| local data = redis.call('HGETALL', KEYS[1]) | ||
| redis.call('DEL', KEYS[1]) | ||
| return data |
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.
🛠️ Refactor suggestion
파싱 로직에 예외 처리 개선 필요
기본적인 검증은 있지만
Long.parseLong()실행 시 발생할 수 있는NumberFormatException에 대한 처리가 필요합니다.private List<SpViewsDto> parseResult(List<String> spViewsStringList) { if (spViewsStringList.size() % 2 != 0){ log.error("spViewsStringList 개수가 올바르지 않습니다."); throw new IllegalArgumentException("spViewsStringList 개수가 올바르지 않습니다."); } List<SpViewsDto> spViewsDtoList = new ArrayList<>(); for (int i = 0; i < spViewsStringList.size(); i += 2) { - Long spId = Long.parseLong(spViewsStringList.get(i)); - Long views = Long.parseLong(spViewsStringList.get(i + 1)); + try { + Long spId = Long.parseLong(spViewsStringList.get(i)); + Long views = Long.parseLong(spViewsStringList.get(i + 1)); + + SpViewsDto spViewsDto = new SpViewsDto(spId, views); + spViewsDtoList.add(spViewsDto); + } catch (NumberFormatException e) { + log.error("Invalid number format in Redis data: spId={}, views={}", + spViewsStringList.get(i), spViewsStringList.get(i + 1), e); + // 잘못된 데이터는 건너뛰고 계속 처리 + } - SpViewsDto spViewsDto = new SpViewsDto(spId, views); - spViewsDtoList.add(spViewsDto); } return spViewsDtoList; }📝 Committable suggestion
🤖 Prompt for AI Agents