728x90

cache의, cache를 위한, cache에 의한

cache의 3단 변신..

 

Vegeta로 하염없는 Request를 보내보니 알겠더군요..

* [대기열 시스템] HTTP load testing tool, Vegeta 설치(Window)

Cache가 중요하다는 것을요..

 

메모리에 저장해두고 누구보다 빠르게 남들과는 다르게 데이터를 전달해주는 Redis..

 

 

다양한 방법, 3.5단 변신을 정리해보고자 합니다..🤖

 

1단 변신 준비. RedisConfig - userRedisTemplate

Redis에 데이터를 저장하고 가져올 때, User 객체를 쉽게 직렬화/역직렬화하여 처리할 수 있도록 RedisTemplate을 설정

RedisConfig.java
@Configuration
public class RedisConfig {

  @Bean
  RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    // RedisConnectionFactory: Redis 서버와의 연결을 관리하는 클래스
    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    // ObjectMapper: JSON 데이터를 Java 객체로 변환하거나, Java 객체를 JSON 데이터로 변환
    // DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false: JSON에 Java 클래스에 정의되지 않은 필드가 있어도 에러를 발생시키지 않도록 설정
    // JavaTimeModule: java.time 패키지의 날짜/시간 클래스(LocalDate, LocalDateTime 등)를 처리할 수 있는 모듈을 추가
    // SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS
    // : 날짜를 타임스탬프가 아닌 ISO-8601 포맷(예: 2023-01-18T10:15:30)으로 직렬화하도록 설정

    // Object Mapper 설정 이유
    // Java 객체의 구조에 맞지 않는 JSON 필드가 있을 경우 에러가 발생할 수 있음
    // Java의 날짜/시간 클래스(LocalDate, LocalDateTime 등)는 기본적으로 직렬화/역직렬화가 제대로 지원되지 않음
    // 기본적으로 날짜와 시간을 타임스탬프 형식(예: 1674042600000, 밀리초 단위)으로 직렬화
    // 날짜를 사람이 읽을 수 있는 ISO 형식(예: 2025-01-18T15:30:00)으로 변환

    RedisTemplate<String, User> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    template.setKeySerializer(new StringRedisSerializer()); // 키를 문자열(String)로 직렬화
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, User.class)); // JSON 포맷으로 데이터를 저장

      return template;
  	}
  }

 

 

1단 변신. RedisConfig - objectRedisTemplate

RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장

RedisConfig.java
@Configuration
public class RedisConfig {

  @Bean
  RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)
        .build();
    // PolymorphicTypeValidator: allowIfSubType을 통해 역직렬화 허용 클래스 타입을 정의
    
    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    // activateDefaultTyping: Jackson에서 직렬화/역직렬화 시 객체 타입 정보를 포함하도록 설정하는 메서드
    // DefaultTyping: 비최종 클래스(Non-final class)에 대해서만 타입 정보를 포함

    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
    // RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장
    // GenericJackson2JsonRedisSerializer
    // : JSON 데이터를 역직렬화할 때, 대상 객체의 타입 정보를 명시적으로 제공하지 않으면 기본적으로 Map 타입(즉, LinkedHashMap)으로 역직렬화

    return template;
  }
}

 

 

2단 변신. RedisHashUser

@Redishash를 사용해 객체를 Redis의 Hash 데이터 구조로 저장

RedisHashUser.java
@RedisHash(value = "redishash-user", timeToLive = 30L)
// @RedisHash를 사용해 Redis에 저장할 객체를 정의
public class RedisHashUser {

  @Id
  private Long id;
  // HSET redishash-user:3 id 3
  private String name;
  // HSET redishash-user:3 name "hi3"
  @Indexed
  private String email;
  // HSET redishash-user:3 email "hi3@email.com"
  // "SADD" "redishash-user:email:hi3@email.com" 3
  private LocalDateTime createdAt;
  private LocalDateTime updatedAt;
}

 

RedisHashUserRepository.java
public interface RedisHashUserRepository extends CrudRepository<RedisHashUser, Long> {
	// ...
}

 

JpaRepository는 Redis가 아닌 RDBMS(예: MySQL, PostgreSQL 등)와 상호작용하기 위한 Repository 

CrudRepository / RedisRepository를 상속하는 Repository 이용 필수

기능 JpaRepository CrudRepository
저장소 대상 관계형 데이터베이스 (RDBMS) Redis
데이터 모델 테이블과 레코드 Key-Value 또는 Hash 등
내부 구현 JPA(EntityManage, Hibernate 등) 기반 RedisTemplate 기반
동작 방식 SQL 쿼리 생성 및 실행 Redis 명령어(SET, GET 등) 실행
@RedisHash 지원 여부 지원하지 않음 지원함

 

 

3단 변신(최종 변신). @Cacheable

캐시에 데이터가 없을 경우, 캐시에 데이터를 추가하고, 있을 경우, 캐시의 데이터 반환

CacheConfig.java
@EnableCaching
// Spring의 캐싱 기능을 활성화
// 캐싱 관련 애너테이션(@Cacheable, @CacheEvict, @CachePut)이 동작하도록 설정
// 애플리케이션 컨텍스트에 한 번만 추가되면 전체 프로젝트에서 동작
@Configuration
public class CacheConfig {

  public static final String CACHE1 = "cache1";
  public static final String CACHE2 = "cache2";

  @AllArgsConstructor
  @Getter
  public static class CacheProperty{
    private String name;
    private Integer ttl;
  }

  @Bean
  public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(){
    PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
        .builder()
        .allowIfSubType(Object.class)
        .build();

    ObjectMapper objectMapper = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule())
        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
        .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

    List<CacheProperty> properties = List.of(
        new CacheProperty(CACHE1, 300),
        new CacheProperty(CACHE2, 30)
    );

    return (builder -> {
      properties.forEach(i ->
          builder.withCacheConfiguration(i.getName(),
              RedisCacheConfiguration.defaultCacheConfig()
              .disableCachingNullValues()
              .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
              .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)))
              .entryTtl(Duration.ofSeconds(i.getTtl()))));
    });
    // RedisCacheConfiguration.defaultCacheConfig(): 기본 Redis 캐시 설정을 가져옴
    // disableCachingNullValues(): null 값을 캐싱하지 않도록 설정
    // serializeKeysWith: Redis의 키를 직렬화하는 방법을 설정, 키를 문자열로 직렬화
    // serializeValuesWith: Redis의 값을 직렬화하는 방법을 설정, GenericJackson2JsonRedisSerializer를 사용하여 값을 JSON으로 직렬화
    // entryTtl: 캐시의 만료 시간을 설정
  }
}

 

UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
  @Cacheable(cacheNames = CACHE1, key = "'users:' + #id")
  // cacheNames: 캐시를 구분하고 각각의 캐시에 대해 커스터마이징된 설정을 적용하기 위해 사용
  public User getUser3(final Long id) {
    return userRepository.findById(id).orElseThrow();
  }
}

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

728x90