
@Service @RequiredArgsConstructor public class LikeService { private final LikeRepository likeRepository; private final RedisTemplate<String, String> redisTemplate; ... public void likePostWithRedis(LikePostRequestDto likePostRequestDto) { // Redis에 넣을 value 값 생성 // (원래는 데이터를 JSON으로 직렬화를 많이 하는 편이지만, 편의상 간단한 문자열 조합으로 대체) Long userId = likePostRequestDto.getUserId(); Long postId = likePostRequestDto.getPostId(); String value = userId + ":" + postId; // like_queue라는 List에 데이터를 오른쪽으로 Push해서 넣는다. // Redis 명령어에서 'RPUSH like_queue [value]'와 동일하게 작동한다. redisTemplate.opsForList().rightPush("like_queue", value); } }
@RestController @RequiredArgsConstructor @RequestMapping("/likes") public class LikeController { private final LikeService likeService; ... @PostMapping("/redis") public void likePostWithRedis( @RequestBody LikePostRequestDto likePostRequestDto ) { likeService.likePostWithRedis(likePostRequestDto); } }
@EnableScheduling @SpringBootApplication public class RedisIntermediateProjectApplication { public static void main(String[] args) { SpringApplication.run(RedisIntermediateProjectApplication.class, args); } }
@Component @RequiredArgsConstructor @Slf4j public class LikeScheduler { private final RedisTemplate<String, String> redisTemplate; private final LikeRepository likeRepository; // 1초마다 실행 (실무에서는 데이터 양에 따라 조절) @Scheduled(fixedDelay = 1000) public void saveLikesToDb() { // 1. Redis에서 데이터를 1000개 꺼내오기 // (큐에 있는 데이터를 하나씩 꺼내서 리스트에 담기) List<Like> likesToSave = new ArrayList<>(); while (true) { // like_queue라는 List에서 왼쪽(첫 부분)에서 데이터를 꺼내온다. // Redis 명령어에서 'LPOP like_queue'와 동일하게 작동한다. String value = redisTemplate.opsForList().leftPop("like_queue"); if (value == null) break; String[] split = value.split(":"); Long userId = Long.parseLong(split[0]); Long postId = Long.parseLong(split[1]); likesToSave.add(new Like(userId, postId)); if (likesToSave.size() >= 1000) break; } likeRepository.saveAll(likesToSave); log.info("DB 저장 완료: {} 건", likesToSave.size()); } }
saveAll() 대신에 jdbcTemplate의 batchUpdate 쿼리를 사용하면 더 효율화를 시킬 수 있다. 하지만 이번 실습에서는 간단하게 테스트를 하기 위해 saveAll()을 사용했다. 
script_1-1.js)와 코드가 대부분 똑같고, 새로 만든 API에 맞게 주소만 변경해주었다. import http from 'k6/http'; import { check, sleep } from 'k6'; import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; export const options = { // 가상 유저(VUs) 1000명으로 설정 vus: 1000, // 테스트를 10초 동안 진행 duration: '10s', }; export default function () { // 랜덤 데이터 생성 // 유저는 1~10,000명, 게시글은 1~100개라고 가정 const userId = randomIntBetween(1, 10000); const postId = randomIntBetween(1, 100); // 좋아요 API const url = 'http://localhost:8080/likes/redis'; const payload = JSON.stringify({ userId: userId, postId: postId, }); const params = { headers: { 'Content-Type': 'application/json', }, }; // POST 요청 전송 const res = http.post(url, payload, params); // 응답 상태 코드 확인 (200 OK가 왔는 지 체크) check(res, { 'status is 200': (r) => r.status === 200, }); }
$ k6 run scripts/script_1-2.js


이로써 좋아요 수 폭증으로 인한 DB 부하를 Redis로 해결했다.