Redis Cache 실습(Aka. Write Back)

2024. 9. 14. 16:44·DB/Redis

Write-Back Cache 패턴 실습: Redis와 MySQL을 활용한 구현

Write-Back Cache 패턴은 데이터를 캐시에 먼저 쓰고, 데이터베이스로는 일정 주기로 배치 작업을 통해 동기화하는 패턴입니다. 이 패턴은 쓰기 성능을 극대화할 수 있는 장점이 있지만, 캐시 손실 시 데이터 일관성 문제를 발생시킬 수 있습니다. 이번 실습에서는 Spring Boot와 Redis, 그리고 MySQL을 사용하여 Write-Back 패턴을 구현하고, 주기적인 동기화 작업을 배치로 처리하는 방법을 다룹니다.


1. Write-Back Cache 패턴 실습 개요

핵심 동작 원리:

  1. 쓰기 작업: 데이터는 먼저 Redis 캐시에 저장. 데이터베이스에는 바로 반영하지 않음.
  2. 배치 작업: 주기적으로 Redis 캐시에 저장된 데이터를 MySQL 데이터베이스에 동기화.
  3. 읽기 작업: Redis에서 데이터를 먼저 읽고, 캐시에 없을 경우에만 MySQL에서 데이터를 조회한 후 캐시에 저장.

2. 환경 설정

Docker로 Redis와 MySQL 실행

# MySQL 실행
docker run -d --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=fastsns mysql:8.0

# Redis 실행
docker run -d --name redis-server -p 6379:6379 redis

3. Spring Boot 설정

application.yaml 설정

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/fastsns
    username: root
    password: root

  jpa:
    hibernate.ddl-auto: update
    show-sql: true

  redis:
    host: localhost
    port: 6379

4. Write-Back 패턴 구현

4.1. RedisConfig 클래스

Redis와의 연결을 설정하기 위한 클래스입니다. Jedis 라이브러리를 사용해 Redis Pool을 구성합니다.

package com.example.writeback;

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 jedisPool() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setJmxEnabled(false);  // JMX 비활성화
        return new JedisPool(poolConfig, "127.0.0.1", 6379);
    }
}

4.2. User 엔티티

User 엔티티는 MySQL 데이터베이스에 저장되는 사용자 정보를 나타내며, JPA를 사용해 관리됩니다.

package com.example.writeback;

import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 50)
    private String name;

    @Column(length = 100)
    private String email;

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}

4.3. UserController 클래스

사용자의 데이터를 저장할 때 Redis에 먼저 저장하고, 주기적으로 배치 작업을 통해 MySQL에 동기화하는 흐름을 처리합니다.

package com.example.writeback;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.time.LocalDateTime;

@RestController
@RequiredArgsConstructor
public class UserController {

    private final JedisPool jedisPool;
    private final UserRepository userRepository;

    // 사용자 저장: Redis에 먼저 저장되고, 배치 작업을 통해 MySQL에 반영됨
    @PostMapping("/users")
    public String saveUser(@RequestParam String name, @RequestParam String email) {
        try (Jedis jedis = jedisPool.getResource()) {
            Long id = System.currentTimeMillis();  // 임시로 시간값을 ID로 사용
            String redisKey = String.format("users:%d", id);

            User user = User.builder()
                .id(id)
                .name(name)
                .email(email)
                .createdAt(LocalDateTime.now())
                .updatedAt(LocalDateTime.now())
                .build();

            // Redis에 데이터 저장
            jedis.hset(redisKey, "name", user.getName());
            jedis.hset(redisKey, "email", user.getEmail());
            jedis.hset(redisKey, "createdAt", user.getCreatedAt().toString());
            jedis.hset(redisKey, "updatedAt", user.getUpdatedAt().toString());

            return "User saved in Redis!";
        }
    }

    // 사용자 조회: Redis에서 먼저 조회하고, 없으면 MySQL에서 조회
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        try (Jedis jedis = jedisPool.getResource()) {
            String redisKey = String.format("users:%d", id);

            if (jedis.exists(redisKey)) {
                return User.builder()
                        .id(id)
                        .name(jedis.hget(redisKey, "name"))
                        .email(jedis.hget(redisKey, "email"))
                        .createdAt(LocalDateTime.parse(jedis.hget(redisKey, "createdAt")))
                        .updatedAt(LocalDateTime.parse(jedis.hget(redisKey, "updatedAt")))
                        .build();
            } else {
                return userRepository.findById(id).orElse(null);
            }
        }
    }
}

4.4. UserRepository 인터페이스

JPA를 사용하여 MySQL과 연동하여 사용자 데이터를 관리합니다.

package com.example.writeback;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

5. 배치 작업 (Spring Scheduler 사용)

5.1. WriteBackScheduler 클래스

Redis에 저장된 데이터를 주기적으로 MySQL에 동기화하는 작업을 배치 작업으로 처리합니다. Spring Scheduler를 통해 배치 작업이 실행됩니다.

package com.example.writeback;

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.time.LocalDateTime;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class WriteBackScheduler {

    private final JedisPool jedisPool;
    private final UserRepository userRepository;

    // 1분마다 실행하여 Redis의 데이터를 MySQL로 동기화
    @Scheduled(fixedRate = 60000)
    public void syncRedisToDb() {
        try (Jedis jedis = jedisPool.getResource()) {
            // Redis의 모든 사용자 데이터 키 가져오기
            for (String key : jedis.keys("users:*")) {
                Map<String, String> userData = jedis.hgetAll(key);
                Long id = Long.valueOf(key.split(":")[1]);

                // Redis에서 가져온 데이터를 MySQL에 저장
                User user = User.builder()
                    .id(id)
                    .name(userData.get("name"))
                    .email(userData.get("email"))
                    .createdAt(LocalDateTime.parse(userData.get("createdAt")))
                    .updatedAt(LocalDateTime.parse(userData.get("updatedAt")))
                    .build();

                userRepository.save(user);

                // 데이터가 MySQL로 저장되면 Redis에서 삭제
                jedis.del(key);
            }
        }
    }
}

5.2. Spring Scheduler 활성화

@EnableScheduling을 사용해 Spring에서 스케줄링 기능을 활성화합니다.

package com.example.writeback;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class WriteBackApplication {

    public static void main(String[] args) {
        SpringApplication.run(WriteBackApplication.class, args);
    }
}

6. 테스트 방법

  1. 데이터 저장 (POST 요청):

    curl -X POST "localhost:8080/users?name=John&email=john@example.com"

    이 요청은 사용자 데이터를 Redis에 먼저 저장합니다.

  2. 데이터 조회 (GET 요청):

    curl "localhost:8080/users/{id}"

    사용자 데이터는 Redis에서 먼저 조회되며, 캐시가 만료되거나 배치 작업이 진행된 경우 MySQL에서 조회됩니다.

  3. 배치 작업 실행 후 MySQL 확인:
    1분 후 배치 작업이 실행되면, Redis에 있던 데이터가 MySQL로 동기화됩니다.

    mysql> select * from user;

7. 결론

Write-Back Cache 패턴을 통해 데이터를 Redis에 먼저 저장하고, 배치 작업

을 통해 주기적으로 MySQL로 동기화하는 방식을 구현했습니다. 이를 통해 쓰기 성능을 크게 향상시킬 수 있으며, 데이터베이스의 부하를 줄일 수 있습니다. 하지만 캐시 손실 시 데이터 손실을 방지하기 위한 추가적인 복구 메커니즘이 필요할 수 있으며, 주기적인 동기화 주기를 설정하는 데도 주의가 필요합니다.

이번 실습을 통해 Write-Back 패턴을 성공적으로 구현하고, 이를 실무에 적용할 수 있는 방법을 학습했습니다.

저작자표시 (새창열림)

'DB > Redis' 카테고리의 다른 글

Redis 모니터링 (feat. Prometheus, Grafana, Redis 및 Redis Exporter)  (5) 2024.09.15
Spring Boot Cache 실습  (3) 2024.09.14
Redis Cache 실습(Aka. Jedis, Cache Aside)  (1) 2024.09.14
Redis Cache 활용법: 성능 최적화를 위한 캐시 전략  (0) 2024.09.12
Redis Key와 Scan 명령어: 대규모 데이터 검색  (0) 2024.09.11
'DB/Redis' 카테고리의 다른 글
  • Redis 모니터링 (feat. Prometheus, Grafana, Redis 및 Redis Exporter)
  • Spring Boot Cache 실습
  • Redis Cache 실습(Aka. Jedis, Cache Aside)
  • Redis Cache 활용법: 성능 최적화를 위한 캐시 전략
hyeseong-dev
hyeseong-dev
안녕하세요. 백엔드 개발자 이혜성입니다.
  • hyeseong-dev
    어제 오늘 그리고 내일
    hyeseong-dev
  • 전체
    오늘
    어제
    • 분류 전체보기 (283)
      • 여러가지 (108)
        • 알고리즘 & 자료구조 (73)
        • 오류 (4)
        • 이것저것 (29)
        • 일기 (1)
      • 프레임워크 (39)
        • 자바 스프링 (39)
        • React Native (0)
      • 프로그래밍 언어 (39)
        • 파이썬 (31)
        • 자바 (3)
        • 스프링부트 (5)
      • 컴퓨터 구조와 운영체제 (3)
      • DB (17)
        • SQL (0)
        • Redis (17)
      • 클라우드 컴퓨팅 (2)
        • 도커 (2)
        • AWS (0)
      • 스케쥴 (65)
        • 세미나 (0)
        • 수료 (0)
        • 스터디 (24)
        • 시험 (41)
      • 트러블슈팅 (1)
      • 자격증 (0)
        • 정보처리기사 (0)
      • 재태크 (0)
        • 암호화폐 (0)
        • 기타 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    #개발자포트폴리오 #개발자이력서 #개발자취업 #개발자취준 #코딩테스트 #항해99 #취리코 #취업리부트코스
    Docker-compose
    Python
    완전탐색
    celery
    docker
    그리디
    항해99
    EC2
    mybatis
    Spring WebFlux
    ecs
    시험
    spring
    취업리부트
    자바
    java
    SAA
    RDS
    Redis
    Spring Boot
    파이썬
    reactor
    AWS
    FastAPI
    DP
    프로그래머스
    백준
    WebFlux
    OOP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
hyeseong-dev
Redis Cache 실습(Aka. Write Back)
상단으로

티스토리툴바