- 목표: 데이터베이스의 락과 트랜잭션을 최대한 활용하여 동시성 문제를 해결하고, 캐싱과 Redis를 이용하여 시스템의 성능을 개선할 수 있는 부분을 깊이 있게 연구합니다.
- 내용: 동시성 이슈 파악, 캐싱 및 Redis 적용 대상 선정, 적용 방안 및 합리적인 이유를 분석하고 정리합니다.
- 시스템 성능 향상: 대용량 트래픽을 처리하기 위해서는 데이터베이스 부하를 줄이고 응답 속도를 향상시키는 것이 중요합니다.
- 사용자 경험 개선: 빠른 응답 속도는 사용자 만족도를 높이고 서비스의 경쟁력을 강화합니다.
- 문제점: 콘서트 정보 조회, 좌석 정보 조회, 대기열 토큰 관리 등에서 데이터베이스에 빈번한 접근이 발생하여 부하가 증가하고 있습니다.
- 영향:
- 응답 속도 저하
- 데이터베이스 리소스 과다 사용
- 동시성 이슈로 인한 데이터 무결성 문제 발생 가능성
- 문제점: 대기열에서 많은 사용자가 동시에 접속할 경우 응답 속도가 느려지고, 사용자 경험이 저하됩니다.
- 영향:
- 사용자 이탈 증가
- 서비스 신뢰도 하락
- 특징:
- 변경 빈도가 낮음
- 다수의 사용자가 공통으로 조회
- 선정 이유:
- 캐싱을 통해 데이터베이스 부하를 크게 줄일 수 있음
- 높은 캐시 히트율 기대
- 특징:
- 특정 시간대에 조회 요청 집중
- 좌석 상태는 실시간으로 변경됨
- 선정 이유:
- 좌석의 기본 정보(좌석 번호, 가격 등)는 캐싱 가능
- 좌석 상태는 캐싱하지 않음으로써 데이터 일관성 유지
- 특징:
- 대기열 토큰은 일시적인 데이터
- 빠른 읽기/쓰기가 필요함
- 선정 이유:
- Redis를 활용하여 빠른 성능 제공
- TTL 설정을 통해 자동 만료 관리 가능
- 대기열 토큰 관리에 Redis 적용
- 콘서트 정보 조회 캐싱
- 좌석 정보 조회 캐싱
- 현황: 대기열 토큰을 데이터베이스에 저장하여 관리 중
- 문제점:
- 데이터베이스 부하 증가
- 대기열 처리 속도 저하
- Redis 사용:
- 대기열 토큰을 Redis에 저장
- 토큰에 TTL(Time To Live)을 설정하여 자동 만료 관리
- 데이터 구조:
- Redis의 **Sorted Set(ZSet)**을 사용하여 대기열 순서를 관리
- 사용자 ID를 멤버로, 입장 시간을 점수(score)로 설정
- 성능 향상:
- Redis는 메모리 기반의 데이터 저장소로, 매우 빠른 읽기/쓰기 성능을 제공합니다.
- DB 부하 감소:
- 데이터베이스 대신 Redis를 사용함으로써 DB 부하를 줄입니다.
- TTL 관리 용이:
- Redis는 TTL 설정을 지원하여 일시적인 데이터 관리에 적합합니다.
- 캐시 지역성 활용:
- 대기열에 자주 접근하는 사용자들의 데이터를 Redis에 저장하여 빠른 응답 제공
- 현황: 콘서트 정보 조회 시마다 데이터베이스를 조회
- 문제점:
- 다수의 사용자가 동시에 조회할 경우 데이터베이스 부하 증가
- 응답 속도 저하
- Redis 캐싱:
- 콘서트 정보를 Redis에 캐싱
- 캐시 키:
concert:{concertId}
- TTL 설정:
- 콘서트 정보의 변경 주기가 길므로 긴 TTL 설정 가능
- 캐시 무효화 전략:
- 콘서트 정보 변경 시 해당 캐시 키 삭제 또는 갱신
- DB 부하 감소:
- 캐시 히트 시 데이터베이스 접근을 피할 수 있습니다.
- 응답 속도 향상:
- 메모리에서 데이터를 가져오기 때문에 빠른 응답이 가능합니다.
- 캐시 지역성 활용:
- 동일한 콘서트 정보를 여러 사용자가 조회하므로 캐시 효율이 높습니다.
- 현황: 좌석 정보 조회 시마다 데이터베이스를 조회
- 문제점:
- 특정 시간대에 조회 요청이 집중되어 데이터베이스 부하 증가
- 좌석 상태를 실시간으로 확인해야 하므로 캐싱 어려움
- 기본 정보 캐싱:
- 좌석 번호, 가격 등 변경되지 않는 정보를 캐싱
- 캐시 키:
seat:{concertId}:{seatNumber}:info
- 좌석 상태는 캐싱하지 않음:
- 실시간성이 요구되는 좌석 상태는 데이터베이스에서 직접 조회
- 응답 속도 향상:
- 좌석의 기본 정보를 캐싱하여 빠르게 제공
- 데이터 일관성 유지:
- 좌석 상태는 실시간 조회로 일관성 보장
- 캐시 지역성 활용:
- 특정 콘서트의 좌석 정보를 여러 사용자가 조회하므로 캐시 효율 높음
- DB 쿼리 감소:
- 대기열 토큰 관리를 데이터베이스에서 Redis로 이관하여 DB 쿼리 횟수 감소
- 응답 속도 개선:
- Redis의 빠른 성능으로 대기열 처리 속도 향상
- 부하 분산:
- 데이터베이스에 집중된 부하를 Redis로 분산하여 시스템 안정성 향상
- 캐시 히트율 증가:
- 콘서트 정보는 변경이 적으므로 캐시 히트율이 높게 유지
- DB 부하 감소:
- 캐시에서 데이터를 제공하여 데이터베이스 접근 감소
- 응답 속도 향상:
- 평균 응답 시간 단축 예상 (예: 200ms → 50ms)
- 응답 속도 개선:
- 좌석 기본 정보를 캐싱하여 조회 속도 향상
- 데이터 일관성 유지:
- 좌석 상태는 실시간 조회로 일관성 유지
- 캐시 효율성:
- 특정 시간대에 집중된 조회 요청을 효과적으로 처리
-
Spring Boot Redis Starter 의존성 추가
<!-- build.gradle 또는 pom.xml --> dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' }
-
Redis 설정
# application.yaml spring: redis: host: localhost port: 6379
-
대기열 서비스 인터페이스
interface QueueService { fun addToQueue(userId: Long, concertId: Long): Boolean fun isUserInQueue(userId: Long, concertId: Long): Boolean fun getUserPosition(userId: Long, concertId: Long): Long? fun removeFromQueue(userId: Long, concertId: Long) }
-
Redis를 이용한 구현
@Service class RedisQueueService( private val redisTemplate: StringRedisTemplate ) : QueueService { private fun getQueueKey(concertId: Long): String = "queue:$concertId" override fun addToQueue(userId: Long, concertId: Long): Boolean { val key = getQueueKey(concertId) val score = System.currentTimeMillis().toDouble() return redisTemplate.opsForZSet().add(key, userId.toString(), score) } override fun isUserInQueue(userId: Long, concertId: Long): Boolean { val key = getQueueKey(concertId) return redisTemplate.opsForZSet().rank(key, userId.toString()) != null } override fun getUserPosition(userId: Long, concertId: Long): Long? { val key = getQueueKey(concertId) val rank = redisTemplate.opsForZSet().rank(key, userId.toString()) return rank?.plus(1) // Rank는 0부터 시작하므로 +1 } override fun removeFromQueue(userId: Long, concertId: Long) { val key = getQueueKey(concertId) redisTemplate.opsForZSet().remove(key, userId.toString()) } }
-
TTL 설정
override fun addToQueue(userId: Long, concertId: Long): Boolean { val key = getQueueKey(concertId) val score = System.currentTimeMillis().toDouble() val added = redisTemplate.opsForZSet().add(key, userId.toString(), score) redisTemplate.expire(key, Duration.ofMinutes(30)) // 30분 TTL 설정 return added }
-
캐시 설정
spring: cache: type: redis
-
캐시 어노테이션 사용
@Service class ConcertService( private val concertRepository: ConcertRepository ) { @Cacheable(value = ["concert"], key = "#concertId") fun getConcertById(concertId: Long): Concert { return concertRepository.findById(concertId) ?: throw ResourceNotFoundException("Concert not found") } }
-
캐시 무효화
@Service class ConcertService( private val concertRepository: ConcertRepository ) { @CachePut(value = ["concert"], key = "#concert.id") fun updateConcert(concert: Concert): Concert { return concertRepository.save(concert) } @CacheEvict(value = ["concert"], key = "#concertId") fun deleteConcert(concertId: Long) { concertRepository.deleteById(concertId) } }
-
좌석 기본 정보 캐싱
@Service class SeatService( private val seatRepository: SeatRepository ) { @Cacheable(value = ["seatInfo"], key = "#concertId + ':' + #seatNumber") fun getSeatInfo(concertId: Long, seatNumber: Int): SeatInfo { val seat = seatRepository.findByConcertIdAndSeatNumber(concertId, seatNumber) ?: throw ResourceNotFoundException("Seat not found") return SeatInfo(seat.seatNumber, seat.price) } }
-
좌석 상태 실시간 조회
@Service class SeatService( private val seatRepository: SeatRepository ) { fun getSeatStatus(concertId: Long, seatNumber: Int): SeatStatus { val seat = seatRepository.findByConcertIdAndSeatNumber(concertId, seatNumber) ?: throw ResourceNotFoundException("Seat not found") return seat.status } }
- 시스템 성능 향상:
- Redis를 활용하여 데이터베이스 부하를 줄이고, 응답 속도를 향상시킬 수 있습니다.
- 사용자 경험 개선:
- 빠른 응답으로 사용자 만족도가 높아집니다.
- 시스템 안정성 향상:
- 부하 분산을 통해 시스템의 안정성을 높일 수 있습니다.
- Redis 모니터링 및 장애 대응 방안 마련:
- Redis 장애 시 대비책을 마련하여 SPOF 문제를 해결합니다.
- 캐싱 전략 고도화:
- 캐시 무효화 정책 및 TTL 최적화를 통해 캐시 효율을 높입니다.
- DB 인덱싱 및 최적화 병행:
- 캐싱과 함께 데이터베이스의 인덱싱 등 최적화를 진행하여 근본적인 성능 개선을 도모합니다.
- Redis 공식 문서: https://redis.io/documentation
- Spring Data Redis 참고 자료: https://docs.spring.io/spring-data/redis/docs/current/reference/html/
- 캐시 지역성 이론: https://moris0712.tistory.com/57
- 캐싱 전략 및 패턴: https://martinfowler.com/articles/caching.html
- 데이터 일관성 유지:
- 캐싱된 데이터와 데이터베이스 간의 일관성을 유지하기 위한 전략 필요
- TTL 설정:
- 데이터 특성에 맞는 적절한 TTL 설정으로 캐시 효율 극대화
- 캐시 무효화 전략:
- 데이터 변경 시 캐시를 어떻게 갱신하거나 무효화할지에 대한 명확한 정책 수립
- SPOF 문제 해결:
- Redis Sentinel 또는 Cluster를 이용한 고가용성 구성
- 모니터링 도구 활용:
- Redis의 성능과 상태를 모니터링하여 이슈 발생 시 신속 대응
- 적절한 메모리 관리:
- Redis는 메모리 기반이므로, 메모리 사용량을 주기적으로 확인하고 관리 필요