[Cache]Mysql(JPA)과 Redis를 함께 사용해보자

안녕하세요 오늘은 Redis와 Mysql(JPA)과 이용하는 방법에 대해 설명해보도록 하겠습니다!

왜 Redis와 Mysql을 연동?

Redis는 저장소로 사용될 때도 있지만,
DB의 부하를 줄이기 위해, 혹은 select 를 빠르게 하기 위해 사용될 때도 있습니다

위 그림과 같이 캐시 서버에 조회하려는 데이터가 없는 경우에 DB에서 직접 조회를 하게 되는데,
IO 작업이 필요한 DB와 다르게 캐시 서버인 Redis는 인메모리 저장소이기 때문에 더 빠르게 조회를 할 수 있습니다


redis + springboot + mysql 세팅

springboot와 redis를 연동하는 작업은 지난 번에 해보았으니, 오늘은 지난 번 작업에 이어서 진행을 해보도록 하겠습니다!
일단 맨 처음 mysql을 이용하기 위해 JPA를 연결해주도록 했습니다

pom.xml
       <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.6.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
application.yml
spring:
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
  datasource:
    url: jdbc:mysql://localhost:3306/redis
    username: DB 아이디
    password : DB 비밀번호 
    driver-class-name: com.mysql.jdbc.Driver

그리고 지난 번에 만들었던 Student 객체를 entity로 만들어주었습니다

Student.java
//@RedisHash("Student")
@Entity
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Setter
public class Student implements Serializable {

    public enum Gender{
        MALE, FEMALE
    }

    @Id
    private String id;
    private String name;
    private Gender gender;
    private int grade;

}
  • id를 primary key 값으로 설정하도록 했습니다
  • @RedisHash 값은 주석처리를 했습니다(지난번에는 redis를 이용하기 위해 설정해둠)
  • redis에 직렬화하여 저장할 수 있도록 Serializable 을 상속했습니다

참고로 직렬화를 하면 다음과 같이 저장됩니다


Config 파일에 @EnableCaching을 달아서 캐싱이 가능하도록 했습니다

RedisConfig.java

@Configuration
@EnableCaching
public class RedisConfig {
    @Bean
    JedisConnectionFactory jedisConnectionFactory() {

        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName("192.168.0.20");
        redisConfig.setPort(6379);

        return new JedisConnectionFactory(redisConfig);

    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(jedisConnectionFactory());
        return template;
    }
}

그리고 캐싱을 이용한 select를 하기 위해 Controller을 만들었습니다

StudentController.class
@Controller
public class StudentController {

    @Autowired
    StudentRepository studentRepository;

    @Cacheable(value = "Student")
    public Student findStudentById(String id){

        return studentRepository.findById(id)
                .orElseThrow(RuntimeException::new);
    }

}
  • @Cacheable(value = "저장될이름")을 통해 값을 캐싱할 수 있도록 했습니다
  • DB에 해당 값이 없을 경우에 exception이 발생합니다

효과가 있나 구경해봅시다

값을 저장하고, select를 하는 test 코드를 만들었습니다
이 때 select를 두번 해서 시간을 비교해보았습니다(첫번째는 DB select, 두번째는 redis select)

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class RedisWithMysqlTest {

    @Autowired
    StudentRepository studentRepository;

    @Autowired
    StudentController studentController;

    @Test
    @Order(1)
    void saveTest(){
        Student student1 = new Student("1", "zzarbttoo1", Student.Gender.FEMALE, 1);
        Student student2 = new Student("2", "zzarbttoo2", Student.Gender.FEMALE, 2);
        Student student3 = new Student("3", "zzarbttoo3", Student.Gender.FEMALE, 3);
        Student student4 = new Student("4", "zzarbttoo4", Student.Gender.FEMALE, 4);
        Student student5 = new Student("5", "zzarbttoo5", Student.Gender.FEMALE, 5);

        studentRepository.save(student1);
        studentRepository.save(student2);
        studentRepository.save(student3);
        studentRepository.save(student4);
        studentRepository.save(student5);

    }


    @Test
    @Order(2)
    void selectTest(){

        long start1 = System.currentTimeMillis();

        System.out.println(studentController.findStudentById("1").toString());
        System.out.println(studentController.findStudentById("2").toString());
        System.out.println(studentController.findStudentById("3").toString());
        System.out.println(studentController.findStudentById("4").toString());
        System.out.println(studentController.findStudentById("5").toString());

        long end1 = System.currentTimeMillis();
        System.out.println(end1 - start1);

        long start2 = System.currentTimeMillis();

        System.out.println(studentController.findStudentById("1").toString());
        System.out.println(studentController.findStudentById("2").toString());
        System.out.println(studentController.findStudentById("3").toString());
        System.out.println(studentController.findStudentById("4").toString());
        System.out.println(studentController.findStudentById("5").toString());

        long end2 = System.currentTimeMillis();

        System.out.println(end2 - start2);

    }
}
  • @TestInstance(TestInstance.Lifecycle.PER_CLASS)를 하여 테스트 간에 영향을 받도록 했습니다
  • @Order을 이용해 test간 순서를 지정해서 save 이후 select가 되도록 했습니다

일단 select를 처음 하면 다음과 같이 redis에도 값이 저장되는 것을 확인할 수 있었습니다

그리고 첫번째와 두번째의 select 시간을 확인해보면

와우~
확실히 두번째에 획기적으로 select 시간 단축이 된 것을 확인할 수 있었습니다
(참고로 여러번 해도 첫번째만 오래 걸리고 두번째부터는 빠르게 진행됩니다)


정말 redis는 만능일까?(mysql과의 동기화 문제)

하지만 redis를 캐시서버로 이용하는 것에는 아주 큰 문제가 있으니
캐시 서버와 DB 간의 동기화가 이루어지지 않을 수 있다는 것!

이를 보이기 위해 아래와 같은 테스트 케이스를 짜보았습니다

    @Test
    @Order(3)
    void updateTest(){

        Student updateStudent = studentRepository.findById("1").get();
        updateStudent.setName("updated Name");
        studentRepository.save(updateStudent);


        Student selectStudent = studentRepository.findById("1").get();
        System.out.println(selectStudent.toString());

        Student redisStudent = studentController.findStudentById("1");
        System.out.println(redisStudent.toString());

    }
  • update를 한 후 직접 DB에서 select하는 방식과 캐시서버를 이용해서 select 하는 방식 두가지 진행

결과를 보면 업데이트가 되지 않은 것을 확인할 수 있습니다
다음 글에는 이러한 문제를 해결하는 방식에 대해 설명해보도록 하겠습니다!


출처
https://www.baeldung.com/spring-boot-redis-cache
https://www.youtube.com/watch?v=92NizoBL4uA

좋은 웹페이지 즐겨찾기