πŸ”§ 쑰회 μ„±λŠ₯ κ°œμ„ μ„ μœ„ν•œ 캐싱 및 Redis ν™œμš© λ°©μ•ˆ 뢄석

HyojinΒ·2024λ…„ 11μ›” 7일
0

κ°œμš”


쑰회 μ„±λŠ₯이 μ €ν•˜λ˜λŠ” 쿼리에 λŒ€ν•œ 캐싱 및 Redisλ₯Ό ν™œμš©ν•˜μ—¬ μ„±λŠ₯을 κ°œμ„ ν•  수 μžˆλŠ” λ°©μ•ˆμ„ μ‘°μ‚¬ν•©λ‹ˆλ‹€.

μ£Όμš” λ‚΄μš©


μ½˜μ„œνŠΈ λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ—μ„œλŠ” νŠΉμ • μ½˜μ„œνŠΈ 일정 데이터가 자주 쑰회 λ©λ‹ˆλ‹€. 같은 μ½˜μ„œνŠΈμ— λŒ€ν•΄ λ‹€μˆ˜μ˜ μ‚¬μš©μžκ°€ λ™μ‹œμ— 쑰회 μš”μ²­μ„ λ³΄λ‚΄λŠ” 경우, λ°μ΄ν„°λ² μ΄μŠ€μ— λΆ€ν•˜κ°€ μ§‘μ€‘λ˜μ–΄ μ„±λŠ₯ μ €ν•˜λ‚˜ 지연이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό ν•΄κ²°ν•˜κ³ μž μ½˜μ„œνŠΈ 일정 쑰회 μ‹œ Redis 캐싱을 μ μš©ν•˜μ—¬, λ°μ΄ν„°λ² μ΄μŠ€μ˜ λΆ€ν•˜λ₯Ό 쀄이고 응닡 속도λ₯Ό κ°œμ„ ν•˜κ³ μž ν•©λ‹ˆλ‹€.

Redis 캐싱 μ„ μ • 이유

  1. λ°μ΄ν„°λ² μ΄μŠ€ λΆ€ν•˜ κ°μ†Œ: RedisλŠ” λ©”λͺ¨λ¦¬ 기반 데이터 μ €μž₯μ†Œλ‘œ 쑰회 속도가 맀우 λΉ λ¦…λ‹ˆλ‹€. 이λ₯Ό 톡해 λ°μ΄ν„°λ² μ΄μŠ€μ— 직접 μ ‘κ·Όν•˜λŠ” λΉˆλ„λ₯Ό 쀄이고, λ°μ΄ν„°λ² μ΄μŠ€μ˜ λΆ€ν•˜λ₯Ό κ°μ†Œμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

  2. λΉ λ₯Έ 응닡 μ‹œκ°„: μΊμ‹œλœ 데이터λ₯Ό μ‚¬μš©ν•˜λ©΄ μ‚¬μš©μž μš”μ²­μ— μ¦‰κ°μ μœΌλ‘œ 응닡할 수 μžˆμ–΄ μ„±λŠ₯을 κ°œμ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  3. TTL μ„€μ • κ°€λŠ₯: RedisλŠ” μΊμ‹œλœ 데이터에 유효 기간을 μ„€μ •ν•  수 μžˆλŠ” TTL을 μ§€μ›ν•©λ‹ˆλ‹€.

  4. λ‹€μ–‘ν•œ 데이터 ꡬ쑰 지원: RedisλŠ” λ¬Έμžμ—΄λΏλ§Œ μ•„λ‹ˆλΌ 리슀트, ν•΄μ‹œ λ“± λ‹€μ–‘ν•œ 데이터 ꡬ쑰λ₯Ό μ§€μ›ν•˜λ―€λ‘œ, λ‹€μ–‘ν•œ 데이터λ₯Ό 효과적으둜 μ €μž₯ν•˜κ³  μ‘°νšŒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ ˆλ””μŠ€ 캐싱 적용

λ ˆλ””μŠ€ 캐싱 μ„€μ •

@EnableCaching
@Configuration
public class RedissonConfig {

    @Bean
    public CacheManager contentCacheManager(RedisConnectionFactory cf) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) 
                .entryTtl(Duration.ofMinutes(3L)); // μΊμ‹œ 수λͺ… 3λΆ„ μ„€μ •

        return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(redisCacheConfiguration).build();
    }

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}

Redis μΊμ‹œλ₯Ό κ΅¬μ„±ν•˜λŠ” 데 ν•„μš”ν•œ CacheManager, RedisCacheConfiguration, RedisConnectionFactory 등을 μ •μ˜ν•©λ‹ˆλ‹€.

  • serializeKeysWith: Redis μΊμ‹œμ—μ„œ μ‚¬μš©λ  μΊμ‹œ ν‚€μ˜ 직렬화 방식을 μ„€μ •ν•©λ‹ˆλ‹€. 이 μ˜ˆμ œμ—μ„œλŠ” StringRedisSerializerλ₯Ό μ‚¬μš©ν•˜μ—¬, μΊμ‹œμ˜ ν‚€λ₯Ό λ¬Έμžμ—΄ ν˜•νƒœλ‘œ μ§λ ¬ν™”ν•©λ‹ˆλ‹€.

  • serializeValuesWith: μΊμ‹œ κ°’μ˜ 직렬화 방식을 μ„€μ •ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” GenericJackson2JsonRedisSerializerλ₯Ό μ‚¬μš©ν•˜μ—¬, μΊμ‹œμ˜ 값을 JSON ν˜•μ‹μœΌλ‘œ μ§λ ¬ν™”ν•©λ‹ˆλ‹€. μ΄λŠ” λ³΅μž‘ν•œ 객체듀을 Redis에 μ €μž₯ν•  λ•Œ μœ μš©ν•˜λ©°, μ§λ ¬ν™”λœ 데이터λ₯Ό JSON ν˜•μ‹μœΌλ‘œ λ³€ν™˜ν•˜μ—¬ Redis에 μ €μž₯ν•©λ‹ˆλ‹€.

  • entryTtl(Duration.ofMinutes(3L)): μΊμ‹œ ν•­λͺ©μ˜ TTL을 μ„€μ •ν•©λ‹ˆλ‹€. μΊμ‹œ ν•­λͺ©μ€ 3뢄이 μ§€λ‚˜λ©΄ λ§Œλ£Œλ˜μ–΄ Redisμ—μ„œ μžλ™μœΌλ‘œ μ‚­μ œλ©λ‹ˆλ‹€.

λ ˆλ””μŠ€ 캐싱 μ„œλΉ„μŠ€

    @Cacheable(value = "concertSchedules")
    public List<Schedule> redisCacheableSchedule(Long concertId) {
        List<Schedule> schedules = scheduleRepository.existsSchedule(concertId);
        Schedule.existSchedules(schedules);
        return schedules;
    }

@Cacheable μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ Redis μΊμ‹œμ—μ„œ μŠ€μΌ€μ€„μ„ μ‘°νšŒν•˜κ±°λ‚˜, μΊμ‹œκ°€ μ—†μœΌλ©΄ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ‘°νšŒν•˜μ—¬ κ²°κ³Όλ₯Ό μΊμ‹±ν•©λ‹ˆλ‹€.

λ ˆλ””μŠ€ 캐싱 ν…ŒμŠ€νŠΈ


    @Test
    void μŠ€μΌ€μ€„_쑰회_성곡() {
    	// Given
        Long concertId = 1L;
        long startTime = System.nanoTime();

        // When
        List<Schedule> schedules = scheduleService.schedule(concertId);

        // Then
        long endTime = System.nanoTime();
        long duration = endTime - startTime;

        System.out.println("μŠ€μΌ€μ€„ 쑰회 κ²½κ³Ό μ‹œκ°„ (λ‚˜λ…Έμ΄ˆ): " + duration);

        Assertions.assertThat(schedules).hasSize(2);
    }


    @Test
    void λ ˆλ””μŠ€_캐싱_μŠ€μΌ€μ€„_쑰회_성곡() {
    	// Given
        Long concertId = 1L;

        long startTime = System.nanoTime();

        // When
        List<Schedule> schedules = scheduleService.redisCacheableSchedule(concertId);

        // Then
        long endTime = System.nanoTime();
        long duration = endTime - startTime;

        System.out.println("λ ˆλ””μŠ€ 캐싱 μŠ€μΌ€μ€„ 쑰회 κ²½κ³Ό μ‹œκ°„ (λ‚˜λ…Έμ΄ˆ): " + duration);

        Assertions.assertThat(schedules).hasSize(2);
    }

λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 직접 μŠ€μΌ€μ€„μ„ μ‘°νšŒν•˜λŠ” μ„±λŠ₯κ³Ό Redis μΊμ‹œμ—μ„œ μŠ€μΌ€μ€„μ„ μ‘°νšŒν•˜λŠ” μ„±λŠ₯을 λΆ„μ„ν•˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ—¬ Redis 캐싱이 μ„±λŠ₯에 λ―ΈμΉ˜λŠ” 영ν–₯을 ν‰κ°€ν•˜κ³ , μΊμ‹œ ν™œμš©μœΌλ‘œ 응닡 μ‹œκ°„μ΄ μ–Όλ§ˆλ‚˜ κ°œμ„ λ˜λŠ”μ§€ ν™•μΈν•˜λ €κ³  ν•©λ‹ˆλ‹€.

λ ˆλ””μŠ€ 데이터 쑰회

GET concertSchedules::1 λͺ…λ Ήμ–΄λ₯Ό 톡해 λ‹€μŒκ³Ό 같이 캐싱 데이터λ₯Ό 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

μ„±λŠ₯ 비ꡐ 뢄석

  • μŠ€μΌ€μ€„ 쑰회 κ²½κ³Ό μ‹œκ°„ (λ‚˜λ…Έμ΄ˆ): 338860000
  • λ ˆλ””μŠ€ 캐싱 μŠ€μΌ€μ€„ 쑰회 κ²½κ³Ό μ‹œκ°„ (λ‚˜λ…Έμ΄ˆ): 259237400

ν…ŒμŠ€νŠΈ κ²°κ³Ό, Redis μΊμ‹œλ₯Ό ν™œμš©ν•œ μ‘°νšŒλŠ” λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 직접 μ‘°νšŒν•˜λŠ” 것보닀 λΉ λ₯Έ μ„±λŠ₯을 λ³΄μ˜€μŠ΅λ‹ˆλ‹€. ꡬ체적으둜, λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 직접 μ‘°νšŒν•œ κ²½κ³Ό μ‹œκ°„μ€ μ•½ 338.86msμ˜€κ³ , Redis μΊμ‹œμ—μ„œ μ‘°νšŒν•œ κ²½κ³Ό μ‹œκ°„μ€ μ•½ 259.24m둜 μ•½ 22.6% λΉ λ₯Έ μ„±λŠ₯을 λ³΄μ˜€μŠ΅λ‹ˆλ‹€.

Redis μΊμ‹œκ°€ λ°μ΄ν„°λ² μ΄μŠ€μ˜ 뢀담을 쀄여주고, 반볡적으둜 μš”μ²­λ˜λŠ” 데이터λ₯Ό λΉ λ₯΄κ²Œ λ°˜ν™˜ν•  수 μžˆλ‹€λŠ” 것을 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. μΊμ‹œλœ λ°μ΄ν„°λŠ” λ©”λͺ¨λ¦¬μ—μ„œ λ°”λ‘œ 쑰회되기 λ•Œλ¬Έμ— λ°μ΄ν„°λ² μ΄μŠ€ μ ‘κ·Ό μ‹œκ°„μ„ λ‹¨μΆ•μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

κ²°λ‘ 

Redis 캐싱은 λ°μ΄ν„°λ² μ΄μŠ€ 쑰회 μ„±λŠ₯을 κ°œμ„ ν•˜λŠ” 기술둜, 반볡적인 데이터 μš”μ²­μ— λŒ€ν•œ 응닡 속도λ₯Ό ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€. Redisλ₯Ό ν™œμš©ν•œ μΊμ‹œ μ‘°νšŒκ°€ λ°μ΄ν„°λ² μ΄μŠ€ μ‘°νšŒλ³΄λ‹€ λΉ λ₯΄λ‹€λŠ” 점은 큰 μž₯점이라고 μƒκ°ν•©λ‹ˆλ‹€.

profile
μ–΄μ œμ˜ λ‚˜λ³΄λ‹€ μ„±μž₯ν•œ μ‚¬λžŒμ΄ 될 수 μžˆλ„λ‘ λ…Έλ ₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

0개의 λŒ“κΈ€

κ΄€λ ¨ μ±„μš© 정보