대용량 트래픽 처리(Redis-Mysql 연동)

이정원·2024년 10월 27일
post-thumbnail

실험 개요

Docker를 이용해 Redis와 MySQL 컨테이너 서버를 구동하고, Spring Boot 웹 애플리케이션을 통해 Java 객체를 Redis 또는 MySQL에 직렬화하여 저장하고, 역직렬화할 수 있도록 RedisTemplate, RedisHashUserRepository, 그리고 Spring Cache를 구현했다. 마지막으로 오픈 소스 도구인 Vegeta를 사용하여 초당 5000개의 요청을 발생시켜 캐시 적용 전후의 CPU 사용량을 비교하는 실험을 진행했다.

1.JPA를 통한 MYSQL 데이터 처리 로직 구축

JPA는 엔티티 객체의 상태를 추적하여 영속성 컨텍스트(Persistence Context)에 포함되면 데이터베이스와 동기화한다. 이 과정에서 JPA는 리플렉션을 사용하여 엔티티 객체의 필드 값을 직접 읽어온다. 리플렉션을 통해 객체의 필드에 접근하여 데이터를 가져온 후 데이터를 SQL 쿼리로 변환하여 데이터베이스(MySQL)에 저장한다. 쿼리 조건을 확인할 때는 Getter 메서드를 통해 값을 읽어야 하기 때문에, 엔티티 클래스에 Getter 메서드를 추가해야 한다.

리플렉션(Reflection): 자바에서 런타임 시 객체의 정보에 접근하고 조작할 수 있는 기능.(ex.사용자 입력에 따라 동적으로 하위 타입을 변환하거나 필드 값을 수정할 때 사용됨)

2.캐시 서버 구축

질의할 때마다 DB에 직접 쿼리를 날리는 대신, 캐시 서버(cache aside pattern)를 통해 효율적으로 데이터 운용이 가능하다.

2-1. RedisTemplate

RedisTemplate은 Spring Data Redis에서 제공하는 Redis 클라이언트 객체로, Redis와의 상호작용을 여러 기능으로 추상화하여 쉽게 사용할 수 있도록 도와준다. RedisTemplate의 대표적인 3가지 특징은 다음과 같다.

1.Abstraction(추상화 기능):

  • Redis의 다양한 명령어를 추상화하여 간편하게 사용할 수 있게 해준다. 예를 들어, 값 저장(SET), 값 조회(GET), 리스트 조작, 해시 맵 관리 등 Redis의 기본 기능을 쉽게 접근할 수 있도록 한다.

2.Connection(연결 관리)

  • Redis 서버와의 연결을 관리한다. 연결 풀링(Connection Pooling)을 사용해 성능을 최적화하고, Redis 서버와 안정적으로 통신할 수 있게 한다.

3.Serializer (직렬화)

  • 자바 객체와 Redis 저장소 간의 데이터 교환 시, 서로 다른 형식의 데이터를 변환하는 역할을 한다. 즉, 자바 객체를 Redis에 저장 가능한 형식으로 변환하거나, Redis에 저장된 데이터를 자바 객체로 변환하는 기능을 제공한다. 기본적으로 문자열, JSON, 바이너리 등의 다양한 직렬화 방식을 지원한다.

Java 객체를 Redis에 JSON 형태로 저장하려면, (Jackson2JsonRedisSerializer, GenericJackson2JsonRedisSerializer)와 엔티티 클래스의 필드 값을 정확히 처리할 수 있는 커스텀 ObjectMapper가 필요하다.

ObjectMapper란 Jackson 라이브러리에서 제공하는 핵심 클래스로, Java 객체를 JSON으로 변환하거나 JSON을 Java 객체로 변환하는 데 사용된다.

2-2. RedisHash

RedisHash는 Redis 서버의 Hash 데이터 타입을 사용하여 Java 객체의 필드들을 저장하는 데 특화된 메커니즘이다. 이를 통해 객체를 Redis에 쉽게 저장하고 관리할 수 있다. RedisHash를 사용하려면 저장소 클래스에서 CrudRepository<T, ID> 인터페이스를 상속받아야 한다. JPA 클래스와는 다르게, Java 객체를 Redis에 저장하려면 @RedisHash 애노테이션을 클래스에 추가해야 한다. 또한, 객체의 Primary Key를 지정하기 위해 @Id 애노테이션을 사용하고, 검색 효율성을 높이기 위해 필드에 @Indexed 애노테이션을 적용하고 Redis의 Set으로 관리되어 보다 효율적인 검색을 지원한다.

RedisTemplate vs RedisHash

2-3. Spring Cache

start.spring.io에서 Third party Library에 Cache를 검색하면 찾을수 있다. 추상화된 캐시 기능을 제공하며 개발자는 Cache Provider를 Local,Redis 등으로 선택할수 있다. 구성 방식은 RedisCacheManagerBuilderCustomizer을 통해 캐시 설정(캐시 TTL,GenericJackson2JsonRedisSerializer 직렬화,ObjectMapper) 작업을 선행하면 자동으로 application.yaml파일의 redist 서버를 참조하게 되고 어플리케이션 클래스(or 설정 파일)에 @EnableCaching과 캐시를 활용한 메서드에 @Cacheable 애노테이션을 추가해주면된다.

3.성능 테스트

오픈 소스 도구인 Vegeta를 사용하여 Redis와 MySQL과 연동된 Spring Boot 웹 애플리케이션에 초당 5000번의 트래픽을 가하는 성능 테스트를 진행했다. 캐시를 적용한 서버와 단순히 MySQL에 직접 쿼리하는 서버의 응답률과 CPU 사용률을 비교하여 어떤 방식이 서비스 운영에 더 효율적인지 분석했다.

3-1. 단순 Mysql 질의 트래픽(왼쪽 Vegeta,위 Mysql,아래 Redis)

  • 트래픽: Redis 캐시 서버를 사용하지 않고, MySQL에 직접 5000번의 쿼리를 요청

  • CPU 사용률: MySQL 서버의 CPU 사용률이 최대 124%까지 증가했으며, Redis 서버의 CPU 사용률은 변화가 없었다.

  • 응답률: 39%로, 이는 Spring Boot에서 MySQL에 요청을 보냈으나 SQL 서버 내부에서 트래픽이 drop되거나 오류가 발생한 것으로 추정된다.

3-2. Redis-Mysql 연동 질의 트래픽(왼쪽 Vegeta,위 Mysql,아래 Redis)

  • 트래픽: Cache Aside 패턴을 적용하여 초당 5000번의 쿼리 공격, 우선 Redis 캐시 서버에 1차 질의를 하고 데이터가 없을 경우 MySQL에 질의

  • CPU 사용률:
    1.MySQL 서버의 CPU 사용률은 0~6%로 매우 안정적인 수준을 유지
    2.Redis 서버의 CPU 사용률은 0~10%로 안정적

  • 응답률: 95%로, 이는 대부분의 요청이 Redis 캐시에서 처리되었고 필요한 경우에만 MySQL에 접근했기 때문이다.

결론

  • 단순 MySQL 질의: 고부하 상황에서 MySQL 서버의 CPU 사용률이 급격히 상승하며, 응답률이 저하되는 문제가 발생했다. 이는 대규모 트래픽 처리에 비효율적이다.

  • Redis-MySQL 연동 (Cache Aside Pattern 적용): 캐시를 통한 1차 접근으로 인해 MySQL 서버의 부하가 크게 줄었으며, 트래픽 응답률이 95%로 크게 개선되었다. Redis를 사용한 캐시 메커니즘이 고부하 상황에서도 더 안정적인 성능을 제공했다.

이 결과는 대규모 트래픽이 발생하는 서비스에서 캐시를 사용하는 것이 효율적이라는 것을 보여준다. Redis 캐시 서버를 사용하여 MySQL의 부하를 분산시키는 전략이 서비스 안정성과 성능을 모두 향상시켰다.

0개의 댓글