Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redis 동시성 제어 실습 프로젝트

Redis는 싱글 스레드인데, 왜 동시성 제어가 필요할까?

이 프로젝트는 Redis의 싱글 스레드 특성에도 불구하고 분산 환경에서 발생할 수 있는 동시성 문제를 실제로 검증하고, 해결 방법을 비교하는 데모입니다.

Redis Spring Boot Docker Java

📖 블로그 포스트

자세한 설명은 블로그를 참고해주세요: 레디스는 싱글스레드인데 왜 동시성 제어가 필요할까?


🎯 프로젝트 목적

Redis가 싱글 스레드로 동작한다는 것은 각 명령이 원자적 으로 처리된다는 의미입니다. 하지만 애플리케이션 레벨에서 Read → Modify → Write 패턴을 사용하면, 여러 서버 인스턴스가 동시에 작동하는 분산 환경에서 Race Condition이 발생합니다.

이 프로젝트는:

  • ❌ Before: RMW 패턴의 동시성 문제를 재현
  • ✅ After: Redis 원자 연산을 사용한 올바른 해결 방법 제시

🏗️ 프로젝트 구조

.
├── before/                    # 동시성 문제가 있는 버전
│   ├── src/
│   │   └── main/java/com/example/before/
│   │       ├── config/        
│   │       ├── controller/    
│   │       └── service/       
│   ├── Dockerfile
│   └── build.gradle
│
├── after/                     # 원자 연산을 사용한 해결 버전
│   ├── src/
│   │   └── main/java/com/example/after/
│   │       ├── config/        
│   │       ├── controller/    
│   │       └── service/       
│   ├── Dockerfile
│   └── build.gradle
│
├── test-scripts/              # 동시성 테스트 스크립트
│   ├── test-before.sh         # Before 버전 테스트
│   └── test-after.sh          # After 버전 테스트
│
└── docker-compose.yml  

🔍 핵심 차이점

❌ Before: Read-Modify-Write 패턴

// 1️⃣ READ: Redis에서 현재 값 조회
Object currentObject = redisTemplate.opsForHash().get(key, "attemptCount");
int currentAttempts = (currentObject != null) ? Integer.parseInt(currentObject.toString()) : 0;

// ⏸️ 이 사이에 다른 서버가 끼어들 수 있음!
Thread.sleep(100);  // Race Window 발생

// 2️⃣ MODIFY: 애플리케이션에서 계산
int nextAttempts = currentAttempts + 1;

// 3️⃣ WRITE: Redis에 저장
redisTemplate.opsForHash().put(key, "attemptCount", String.valueOf(nextAttempts));

문제점:

  • 3단계가 분리되어 있어 다른 요청이 중간에 끼어들 수 있음
  • 서버 A와 B가 동시에 currentAttempts = 3을 읽으면 둘 다 4로 저장
  • Lost Update 발생: 12번 요청했는데 카운트가 7만 증가하는 등의 문제

✅ After: Redis 원자 연산

// 1️⃣ Redis 내부에서 단일 명령으로 처리
Long currentAttempts = redisTemplate.opsForHash().increment(key, "attemptCount", 1);

해결 원리:

  • HINCRBY 명령은 Redis 내부에서 원자적 으로 실행
  • Redis 싱글 스레드 특성상 다른 명령이 끼어들 수 없음
  • 분산 환경(여러 서버)에서도 정확한 카운팅 보장

🚀 실행 방법

1️⃣ 사전 요구사항

  • Docker & Docker Compose
  • curl, jq (테스트 스크립트 실행 시)

2️⃣ 환경 실행

# 전체 환경 시작 (Redis + Before 2대 + After 2대)
docker-compose up -d --build

# 상태 확인
docker-compose ps

서비스 포트:

  • Redis: 16379
  • Before 서버 1: 28081
  • Before 서버 2: 28082
  • After 서버 1: 29081
  • After 서버 2: 29082

3️⃣ 동시성 테스트

❌ Before 버전 테스트 (문제 재현)

chmod +x test-scripts/test-before.sh
./test-scripts/test-before.sh

예상 결과:

총 요청 수: 12
Redis 카운트: 7 
차단 여부: Failed

✅ After 버전 테스트 (해결 확인)

chmod +x test-scripts/test-after.sh
./test-scripts/test-after.sh

예상 결과:

총 요청 수: 12
Redis 카운트: 12     
차단 여부: Blocked   

📊 테스트 시나리오

🎯 시나리오: 이메일 인증 코드 검증

사용자가 이메일 인증 코드를 입력할 때:

  • 최대 5회까지만 시도 가능
  • 초과 시 보안을 위해 차단

🧪 테스트 환경

  • 2대의 서버 (Docker Compose로 분산 환경 시뮬레이션)
  • 12번의 동시 요청 (각 서버에 6번씩)
  • 잘못된 코드로 시도하여 카운트 증가 검증

📈 측정 지표

  1. 정합성: Redis에 저장된 attemptCount가 실제 요청 수(12)와 일치하는가?
  2. 보안: MAX_ATTEMPTS(5) 초과 시 정상적으로 차단되는가?

결과

스크린샷 2026-01-27 170313 스크린샷 2026-01-27 170435

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages