운영 중인 앱: 축잘알 테스트 (구글 플레이)
🎯 랜덤 퀴즈 데이터 추출, 어떤 방식이 가장 빠를까?
MySQL vs Java vs Redis – 실전 성능 비교
내가 만든 축구 퀴즈 앱에서는
사용자가 5, 10, 15, 20문제를 랜덤으로 풀 수 있는 기능이 핵심이다.
처음에는 그냥 MySQL의 RAND() 함수 쓰면 끝 아니야? 싶었는데...
퀴즈가 쌓이고, 유저 수가 많아지다 보니 점점 느려지기 시작했다.
그래서 아래 세 가지 방식으로 직접 성능을 비교해봤다:
✅ 방식 1: MySQL RAND()
@Query(nativeQuery=true, value="SELECT * FROM quiz_info ORDER BY rand() LIMIT ?1")
List<QuizInfo> findRandomQuiz(@Param("number")Long number);
- ✅ 장점: 코드가 정말 간단함
- ❌ 단점: 전체 테이블에 난수 계산 + 정렬이 들어가서 O(n log n)
→ 인덱스도 못 쓰고, 데이터가 많아질수록 무조건 느려짐
✅ 방식 2: Java Collections.shuffle()
@Query("SELECT q.id FROM QuizInfo q") List<Long> findAllIds();
Collections.shuffle(allQuizIds); List<Long> randomIds = allQuizIds.stream().limit(10000).toList();
List<QuizInfo> quizList = quizRepository.findByIdIn(randomIds);
- ✅ 메모리 안에서 처리하니까 DB 부하 적음
- ❌ 전체 ID를 다 불러와야 해서, ID 수가 많으면 비효율적
✅ 방식 3: Redis SRANDMEMBER
Set<String> randomIds = redisTemplate.opsForSet().distinctRandomMembers("quiz:id:set", 10000);
List<Long> ids = randomIds.stream().map(Long::valueOf).toList();
List<QuizInfo> quizList = quizRepository.findByIdIn(ids);
- ✅ Redis가 알아서 랜덤하게 골라주기 때문에 속도 빠름
- ❌ 퀴즈 추가/수정 시 Redis와 DB 간 정합성 문제 생길 수 있음
→ 그래서 Redis에는 ID만 저장해두고, 실제 퀴즈 데이터는 DB에서 조회
⏱️ 성능 테스트 결과 (10회 반복, 각각 10,000개 퀴즈 요청)
@Test
void compareQuizLoadPerformanceMultipleTimes() {
int iterations = 10;
List<Double> dbRandTimes = new ArrayList<>();
List<Double> redisTimes = new ArrayList<>();
List<Double> javaRandTimes = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
StopWatch stopWatch = new StopWatch();
// 1. DB RAND()
stopWatch.start("DB RAND()");
List<QuizInfo> randQuiz = quizRepository.findRandomQuiz(10000L);
stopWatch.stop();
dbRandTimes.add((double) stopWatch.getLastTaskTimeMillis());
// 2. Java Random + DB IN
stopWatch.start("Java Random + DB IN");
List<Long> allQuizIds = quizRepository.findAllIds(); // 전체 ID
Collections.shuffle(allQuizIds);
List<Long> randomIdsJava = allQuizIds.stream().limit(10000).toList();
List<QuizInfo> javaRandomQuiz = quizRepository.findByIdIn(randomIdsJava);
stopWatch.stop();
javaRandTimes.add((double) stopWatch.getLastTaskTimeMillis());
// 3. Redis + DB IN
stopWatch.start("Redis + DB IN");
Set<String> randomIds = redisTemplate.opsForSet().distinctRandomMembers("quiz:id:set", 10000);
List<Long> idsFromRedis = randomIds.stream().map(Long::valueOf).toList();
List<QuizInfo> redisQuiz = quizRepository.findByIdIn(idsFromRedis);
stopWatch.stop();
redisTimes.add((double) stopWatch.getLastTaskTimeMillis());
System.out.printf("==== %d회차 테스트 결과 ====\n%s\n", i + 1, stopWatch.prettyPrint());
}
printStats("DB RAND()", dbRandTimes);
printStats("Java Random + DB IN", javaRandTimes);
printStats("Redis + DB IN", redisTimes);
}
📊 성능 비교 결과
방식 | 평균(ms) | 최소(ms) | 최대(ms) | 표준편차 |
MySQL RAND() | 102.50 | 60.00 | 352.00 | 83.98 |
Java Random | 146.30 | 98.00 | 331.00 | 66.32 |
Redis SRANDMEMBER | 15.00 | 11.00 | 23.00 | 3.6 |
- ✅ Redis가 평균적으로 가장 빠름 심지어 편차도 낮고, 안정성도 좋음
- 하지만 외부 서버인 Redis의 latency 이슈로 인해 간헐적으로 가장 느려지는 경우도 있음
- ⚠️ 하지만 주의할 점도 있다실제로 몇 번은 Java 방식보다도 느려지기도 했다.
그래서 무조건 Redis가 정답!은 아니라는 걸 느꼈다. - Redis는 보통 빠르지만, 서버가 idle 상태였다가 처음 요청이 들어오는 순간, latency가 확 튀는 경우도 있었다.
💡 정리하며
이번 실험을 하면서 얻은 인사이트:
평균 속도만 보면 Redis가 당연히 좋아 보이지만,
항상 ‘최악의 상황’까지 고려해야 실제 운영 환경에서 문제가 안 생긴다.
서비스 성능은 단순 수치가 아니라
데이터 특성 + 인프라 상태 까지 다 보고 판단해야 한다.
✍️ 마무리
랜덤 퀴즈처럼 자주 호출되는 기능은
데이터를 어떤 방식으로 가져오느냐에 따라 사용자 경험이 확 달라진다.
이번 테스트를 통해, 단순한 기능에도
성능 최적화와 구조 설계에 대한 고민이 얼마나 중요한지 느낄 수 있었다.
앞으로도 사용자 입장에서 더 나은 경험을 주기 위해
지속적으로 성능 테스트 → 구조 개선을 반복할 예정이다.
'혼자공부한거' 카테고리의 다른 글
혼자 만든 앱에 무중단 배포 적용해보기 – Jenkins, Docker, Nginx (0) | 2025.04.24 |
---|---|
Redis로 유저 랭킹을 불러오면 정말 더 빠를까? (0) | 2025.04.17 |
키클락 취약점 살펴보기 (CVE-2024-8698) (1) | 2024.12.01 |
SSO 란 무엇일까 (0) | 2024.11.22 |
JWT 토큰 (0) | 2024.05.24 |