이번 블로그 글에서는 Docker를 활용하여 MySQL 및 Redis 서버를 구축하고, 이를 Spring Boot, JPA, Redis (Jedis)와 함께 사용하여 간단한 캐싱 시스템을 구현하는 방법을 소개합니다. Cache Aside 패턴을 적용하여 데이터베이스에 저장된 사용자 데이터를 Redis에 캐싱하고, 성능을 향상시키는 방법을 실습합니다.
1. 개요**
- 목표: MySQL에 저장된 사용자 데이터를 조회할 때, 이를 Redis에 캐싱하여 성능을 향상시키는 방법을 실습합니다.
- 기술 스택:
- Spring Boot: 애플리케이션 프레임워크
- MySQL: 데이터베이스
- Redis: 캐싱 솔루션
- Jedis: Redis와의 통신을 위한 클라이언트
- JPA: 데이터베이스 접근을 위한 ORM
2. 환경 설정
2.1. Docker를 이용한 MySQL 서버 구축
MySQL 서버를 Docker로 실행합니다.
docker run -d --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=fastsns mysql:8.0
-p 3306:3306
: MySQL의 기본 포트 3306을 호스트에 연결합니다.MYSQL_ROOT_PASSWORD=root
: MySQL의 root 계정 비밀번호를root
로 설정합니다.MYSQL_DATABASE=fastsns
:fastsns
라는 데이터베이스를 생성합니다.
2.2. Docker를 이용한 Redis 서버 구축
Redis 서버를 Docker로 실행합니다.
docker run -d --name redis-server -p 6379:6379 redis
-p 6379:6379
: Redis의 기본 포트 6379를 호스트에 연결합니다.
2.3. application.yaml
Spring Boot 애플리케이션에서 MySQL과 Redis를 연결하는 설정 파일입니다.
spring:
application:
name: jediscache
datasource:
url: "jdbc:mysql://localhost:3306/fastsns"
username: root
password: root
jpa:
hibernate.ddl-auto: create
show-sql: true
redis:
host: localhost
port: 6379
주요 설정:
- datasource: MySQL 데이터베이스 연결 정보.
- redis: Redis 서버 연결 정보.
2.4. build.gradle
Spring Boot 프로젝트에서 필요한 의존성을 설정합니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'redis.clients:jedis:5.1.3'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
주요 의존성:
- Jedis: Redis와의 통신을 위한 클라이언트.
- Spring Data JPA: MySQL과의 데이터 통신을 담당.
- MySQL Connector: MySQL과의 연결을 담당.
3. Redis 설정
RedisConfig 클래스
Redis와 Spring Boot를 연결하기 위한 JedisPool 설정을 정의합니다.
package com.example.jediscache;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Component
public class RedisConfig {
@Bean
public JedisPool createJedisPool() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setJmxEnabled(false); // JMX 등록 비활성화
return new JedisPool(poolConfig, "127.0.0.1", 6379);
}
}
4. User 엔티티
User 엔티티는 MySQL에 저장될 사용자 정보를 나타내며, @CreatedDate 및 @LastModifiedDate를 사용해 생성일과 수정일을 자동으로 관리합니다.
package com.example.jediscache;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Getter
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 50)
private String name;
@Column(length = 100)
private String email;
@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
5. UserController: Redis 캐싱 적용
UserController는 사용자 이메일을 조회하는 API를 제공합니다. Redis를 활용하여 캐시된 이메일 데이터를 먼저 조회하고, 캐시 미스 시 데이터베이스에서 조회한 후 캐시에 저장합니다.
package com.example.jediscache;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@RestController
@RequiredArgsConstructor
public class UserController {
private final JedisPool jedisPool;
private final UserRepository userRepository;
@GetMapping("/users/{id}/email")
public String getUserEmail(@PathVariable Long id) {
try (Jedis jedis = jedisPool.getResource()) {
String userEmailRedisKey = String.format("users:%d:email", id);
String userEmail = jedis.get(userEmailRedisKey);
if (userEmail != null) return userEmail;
userEmail = userRepository.findById(id).orElse(User.builder().build()).getEmail();
jedis.setex(userEmailRedisKey, 30, userEmail); // 캐시 TTL 30초
return userEmail;
}
}
}
주요 기능:
- 캐시 조회: Redis에서 이메일 데이터를 먼저 조회.
- 캐시 미스 처리: 캐시에 데이터가 없을 경우, 데이터베이스에서 사용자 정보를 조회.
- 캐시에 저장: 데이터베이스에서 조회한 이메일을 Redis에 캐시하며, 캐시 TTL은 30초로 설정.
6. UserRepository
UserRepository
는 JPA를 활용해 사용자 데이터를 관리하는 리포지토리 인터페이스입니다.
package com.example.jediscache;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
7. 애플리케이션 실행
Spring Boot 애플리케이션을 실행하면, MySQL 데이터베이스와 Redis 캐시를 사용해 이메일 데이터를 관리하는 시스템이 동작합니다. 캐시 적중(Cache Hit) 시 성능이 크게 향상되며, 캐시 미스(Cache Miss)일 경우 MySQL에서 데이터를 가져와 Redis에 캐시합니다.
8. API 테스트와 DB, Redis 간의 요청 및 응답 분석
이번 테스트는 Spring Boot, MySQL, Redis (Jedis)를 사용하여 간단한 이메일 조회 API를 구현하고, Redis를 활용한 캐싱 기능을 테스트하는 과정입니다. curl
을 사용하여 여러 번 API를 호출하며 데이터베이스와 Redis 간의 상호작용을 확인했습니다.
8.1. 프로세스 흐름 요약
- 사용자는
/users/{id}/email
API를 호출하여 특정 사용자의 이메일을 조회합니다. - 서버는 우선 Redis에서 이메일을 조회하고, 캐시 미스가 발생하면 MySQL에서 이메일을 가져옵니다.
- MySQL에서 가져온 이메일을 Redis에 캐시하여, 이후 동일한 요청에 대해 Redis 캐시에서 빠르게 응답합니다.
- Redis는 TTL(Time To Live)을 30초로 설정하여, 캐시 데이터가 30초 동안 유지된 후 자동으로 삭제됩니다.
8.2. API 호출 및 로그 기록 분석
8.2.1. MySQL 데이터 확인
다음은 MySQL에 저장된 user
테이블의 데이터입니다. 이 데이터는 Redis 캐시에서 캐시 미스가 발생할 때 사용됩니다.
mysql> select * from user;
+----------------------------+----+----------------------------+----------+--------------------+
| created_at | id | updated_at | name | email |
+----------------------------+----+----------------------------+----------+--------------------+
| 2024-09-12 16:19:43.066219 | 1 | 2024-09-12 16:19:43.066219 | minsu | minsu@gmail.com |
| 2024-09-12 16:19:43.100124 | 2 | 2024-09-12 16:19:43.100124 | minyoung | minyoung@gmail.com |
| 2024-09-12 16:19:43.106684 | 3 | 2024-09-12 16:19:43.106684 | minil | minone@gmail.com |
| 2024-09-12 16:19:43.112250 | 4 | 2024-09-12 16:19:43.112250 | mine | mine@gmail.com |
+----------------------------+----+----------------------------+----------+--------------------+
8.2.2. Redis 명령 실행 및 캐시 키 확인
docker exec
명령어를 사용하여 Redis에 접속한 후, 캐시된 이메일 데이터를 확인하였습니다. 이 로그는 Redis가 정상적으로 이메일 데이터를 캐시하고 있음을 보여줍니다.
docker exec -it redis-server redis-cli
127.0.0.1:6379> keys *
1) "users:4:email"
2) "users:2:email"
3) "users:3:email"
8.2.3. Redis TTL(Time to Live) 확인
캐시된 이메일 데이터의 TTL을 확인한 결과, 캐시된 데이터의 TTL이 30초로 설정되었고 시간이 지남에 따라 TTL이 감소하는 것을 확인할 수 있었습니다.
127.0.0.1:6379> TTL users:4:email
(integer) 27
127.0.0.1:6379> TTL users:4:email
(integer) 22
127.0.0.1:6379> TTL users:4:email
(integer) 21
8.2.4. API 호출 로그 분석 (curl 요청)
다음은 curl
명령어로 /users/{id}/email
API를 호출한 결과입니다. 캐시 미스와 적중 시의 동작을 분석합니다.
1. 캐시 미스 (MySQL에서 데이터 조회 및 Redis에 캐싱)
- 처음 호출 시 Redis에 데이터가 없어 캐시 미스가 발생하고, MySQL에서 데이터를 조회하여 Redis에 저장합니다.
curl localhost:8080/users/4/email
mine@gmail.com%
docker exec -it redis-server redis-cli
127.0.0.1:6379> monitor
1726125638.554357 [0 172.17.0.1:64144] "GET" "users:4:email" # 캐시 미스 발생 (데이터 없음)
1726125638.586310 [0 172.17.0.1:64144] "SETEX" "users:4:email" "30" "mine@gmail.com" # MySQL에서 가져온 데이터 Redis에 캐싱
2. 캐시 적중 (Redis에서 데이터 바로 반환)
- 두 번째 이후 동일한 API 요청 시, Redis에서 캐시된 데이터를 사용하여 빠르게 응답합니다.
curl localhost:8080/users/4/email
mine@gmail.com%
docker exec -it redis-server redis-cli monitor
1726125640.060030 [0 172.17.0.1:64144] "GET" "users:4:email" # 캐시 적중
1726125642.632512 [0 172.17.0.1:64144] "GET" "users:4:email" # 캐시 적중
8.3. 요청과 응답의 흐름
첫 번째 요청 (캐시 미스)
- API 요청:
/users/4/email
호출. - Redis 캐시 확인:
GET users:4:email
→ 캐시 미스 발생. - MySQL 조회: MySQL에서 사용자
id=4
의 이메일 조회. - Redis에 데이터 저장: 조회된 이메일
mine@gmail.com
을 Redis에 30초 TTL로 저장 (SETEX
). - API 응답:
mine@gmail.com
.
두 번째 이후 요청 (캐시 적중)
- API 요청: 동일한
/users/4/email
호출. - Redis 캐시 확인:
GET users:4:email
→ 캐시 적중. - API 응답: Redis에서 데이터를 반환하여 빠르게 응답 (
mine@gmail.com
).
8.4. 예상된 결과 vs 실제 결과
- 예상된 결과:
- 첫 번째 요청 시 캐시 미스가 발생하고, MySQL에서 데이터를 가져온 후 Redis에 저장.
- 이후 30초 동안은 Redis에서 캐시된 데이터를 빠르게 반환.
- 실제 결과:
- 예상대로 첫 번째 요청에서 캐시 미스가 발생하고, 이후 API 요청에서는 Redis에서 캐시된 데이터가 적중하여 빠르게 응답되었습니다.
- Redis TTL이 정확히 30초로 설정되어, TTL이 만료되기 전까지 데이터가 빠르게 캐시에서 제공되었습니다.
9. 결론
이번 테스트에서는 Cache Aside 패턴을 구현하여, Redis 캐시와 MySQL 데이터베이스 간의 효율적인 데이터 처리를 확인했습니다.
- 첫 번째 요청에서는 캐시 미스가 발생했으며, MySQL에서 데이터를 조회한 후 Redis에 저장되었습니다.
- 두 번째 이후 요청에서는 캐시 적중이 발생하여 Redis에서 데이터를 빠르게 제공했고, 데이터베이스의 부하를 줄였습니다.
- TTL 확인을 통해 캐시가 30초 동안 유지되며, 이후에는 데이터가 삭제되거나 새롭게 캐시된다는 점을 확인할 수 있었습니다.
Redis 캐시는 데이터 조회 성능을 크게 향상시키며, MySQL과 함께 사용될 때 효율적으로 작동함을 이번 실습에서 확인할 수 있었습니다.
'DB > Redis' 카테고리의 다른 글
Spring Boot Cache 실습 (1) | 2024.09.14 |
---|---|
Redis Cache 실습(Aka. Write Back) (1) | 2024.09.14 |
Redis Cache 활용법: 성능 최적화를 위한 캐시 전략 (0) | 2024.09.12 |
Redis Key와 Scan 명령어: 대규모 데이터 검색 (0) | 2024.09.11 |
Redis 트랜잭션: 안전한 데이터 변경 처리하기 (0) | 2024.09.11 |