๐ ๋ชฉ์ฐจ
ํญ๊ณต๊ถ ํฐ์ผํ ์๋น์ค๋ฅผ ๊ตฌํํ๋ ์ค, ํญ๊ณต๊ถ ์กฐํ ์๋๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด Redis Cache๋ฅผ ์ ์ฉํ๊ฒ ๋์๋ค.
ํ์ง๋ง, ์บ์๋ฅผ ์ ์ฅํด์ ๊บผ๋ด์ค๋ ๊ณผ์ ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ํ์ธํ๋ค.
์ด๋ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ณ , ์ด๋ป๊ฒ ํด๊ฒฐํ๋์ง ๊ณผ์ ์ ์ ๋ฆฌํด ๋ณด์๋ค.
๐ Redis Cache๋ฅผ ์ ์ฉํ๊ฒ ๋ ์ด์
์ฐ์ Spring Boot MSA ์ํคํ ์ฒ ๊ธฐ๋ฐ์ผ๋ก ๋์ฉ๋ ํธ๋ํฝ์๋ ์์ ์ ์ธ ์๋น์ค๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ด ํ๋ก์ ํธ์ ๋ชฉํ์๊ณ , ๊ทธ ๋ชฉํ๋ฅผ ๊ฐ์ง๊ณ ํญ๊ณต๊ถ ์๋งค ์๋น์ค๋ฅผ ๊ตฌํํ๊ณ ์์๋ค. ์ด๋, ๋๊ธฐ์ด๊ณผ ์์ฝ ์๋น์ค ๊ทธ๋ฆฌ๊ณ ์ค์ ํญ๊ณตํธ API(Amadeus API)๋ฅผ ์ฐ๋ํ ํตํฉ ์ฑ๋ฅ ํ ์คํธ๋ฅผ ์งํํ๋ฉด์ ๋ฌธ์ ์ ์ ๋ฐ๊ฒฌํ๊ฒ ๋์๋ค.
๐ก Jmeter๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ ์กฐ๊ฑด์ผ๋ก ํ ์คํธ๋ฅผ ํด๋ณธ ๊ฒฐ๊ณผ,
โ๏ธ ์ค๋ ๋ ์: 100
โ๏ธ Ramp-up ์๊ฐ: 1
โ๏ธ ๋ฃจํ ์นด์ดํธ: 10
* ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์์์ ์ฒ๋ฆฌ๋์ 200.0/sec ์ด์์ด์ด์ผ ํ์ง๋ง, ์ง์ ํ ์คํธ๋ฅผ ํด๋ดค์ ๋ ์ฒ๋ฆฌ๋์ 160.0/sec๋ก ํด๋น ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ์ง ๋ชปํ๋ค.

์ด๊ธฐ ๋ถ์์์๋ ๋๊ธฐ์ด ์ ์ ์คํจ ์ ๋ฐํ๋๋ ์๋ต ๋ก์ง์์ ์ง์ฐ์ด ๋ฐ์ํ๋ค๊ณ ํ๋จํ์ฌ ํด๋น ๋ก์ง์ ์์ ํด ๋ดค์ง๋ง, ์ฒ๋ฆฌ๋์ ์ผ๋ถ ๊ฐ์ ๋ ๋ฟ ๊ธฐ๋๋งํผ ํฅ์๋์ง ์์๋ค.

ํ์ธ ๊ฒฐ๊ณผ, ์ฑ๋ฅ ์ ํ์ ๊ทผ๋ณธ์ ์ธ ์์ธ์ ๋๊ธฐ์ด ์ ์ ๊ณผ์ ์์ ํญ๊ณตํธ ์์ธ ์กฐํ API๊ฐ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถ๋๋ฉฐ ๋ถํ์ํ DB ์กฐํ๊ฐ ๋ค์ ๋ฐ์ํ ๋ฐ ์์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํญ๊ณตํธ ์์ธ ์กฐํ API์ Redis Cache๋ฅผ ์ ์ฉํ์ฌ ๋์ผ ์์ฒญ์ ๋ํ DB ์ ๊ทผ ์์ด ์บ์์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์๋๋ก ๊ฐ์ ํ๊ณ ์ ํ๋ค.
๐ @Cacheable์ ์ ์ฉ
Redis ์์กด์ฑ์ ์ถ๊ฐํ๊ณ , ObjectMapper์ CacheConfig๋ฅผ ์ค์ ํ์ฌ ํด๋น API์ @Cacheable ์ด๋ ธํ ์ด์ ์ ์ ์ฉํ๋ค.
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// json serialization & deserialization
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.core:jackson-core'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
@Configuration
public class CacheConfig {
@Bean
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ObjectMapper objectMapper) {
// ๊ธฐ๋ณธ ์ง๋ ฌํ๊ธฐ
GenericJackson2JsonRedisSerializer genericSerializer = new GenericJackson2JsonRedisSerializer(
objectMapper);
...
// flights ์บ์ ์ค์ (10์ด TTL)
RedisCacheConfiguration flightConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(flightSerializer))
.disableCachingNullValues()
.entryTtl(Duration.ofSeconds(10));
...
}
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory, ObjectMapper objectMapper
) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
return template;
}
}
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());// Java 8 ๋ ์ง ๋ชจ๋ ๋ฑ๋ก
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // ISO-8601๋ก ์ถ๋ ฅ
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // ์ ์ ์๋ ์์ฑ ๋ฌด์
return objectMapper;
}
}
@Cacheable(value = "flights", key = "#flightId")
public FlightDetailResponse getFlight(UUID flightId) {
FlightEntity flight = getFlightById(flightId);
return FlightDetailResponse.from(flight);
}
ํ์ง๋ง ์ด๋ ๊ฒ @Cacheable ์ด๋ ธํ ์ด์ ์ ์ ์ฉํ ๋ค๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
๐ ์บ์ ์ ์ฉ ํ, ๋ฐ์ํ ๋ฌธ์ ์
๊ณตํญ, ํญ๊ณต์ฌ, ํญ๊ณตํธ ์กฐํ API์ @Cacheable ์ด๋ ธํ ์ด์ ์ ์ ์ฉํ ๋ค, ํด๋น ๊ธฐ๋ฅ์ด ์ ๋๋ก ์ ์ฉ๋์๋์ง ํ ์คํธ๋ฅผ ํ๋ ๋์ค์ 'ClassCastException'์ด ๋ฐ์ํ๋ค.



์บ์๊ฐ ์ ์ฅ๋๋ ๊ฒ์ ํ์ธํ๊ณ ๋ค์ ํด๋น API๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด, ์๋์ ๊ฐ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.


์์ธ์ ๋ถ์ํด ๋ณด๋ ๋คํ์ฑ์ด๊ฑฐ๋ ์ปค์คํ ๊ฐ์ฒด(AirportResponse ๊ฐ์ DTO)๊ฐ ๋ค์ด์๋ ๊ฒฝ์ฐ, ์ญ์ง๋ ฌํ ์ ํ์ ์ ๋ณด๊ฐ ๋๋ฝ๋๋ฉด Jackson์ ObjectMapper๋ ์ด๋ฅผ ์ ํํ ํ๋จํ์ง ๋ชปํ๊ณ ๋ด๋ถ์ ์ผ๋ก LinkedHashMap์ผ๋ก ์ฒ๋ฆฌํ๊ฒ๋์ด ๋ฐ์ํ ๋ฌธ์ ์๋ค.
๐ ์ญ์ง๋ ฌํ ๋ฌธ์ ํด๊ฒฐ ์๋
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Jackson ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๋คํ์ฑ ํ์ ์ ๋ณด๋ฅผ ๋ช ์์ ์ผ๋ก ํฌํจํ์ฌ ์ฒ๋ฆฌํ๋๋กObjectMapper ์ค์ ์ PolymorphicTypeValidator๋ฅผ ์ถ๊ฐํ์ฌ activateDefaultTyping์ ์ค์ ํ๋ค.
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
// Jackson ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ๋คํ์ฑ ํ์
์ ๋ณด ๋ช
์์ ํฌํจ ๊ธฐ๋ฅ
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
ObjectMapper objectMapper = new ObjectMapper();
...
// ํ์
์ ๋ณด๋ฅผ ํฌํจํ๋๋ก ์ค์ (๊ธฐ์กด ์ค์ ๋ก์ง์ ptv ์ค์ ๋ง ์ถ๊ฐ)
objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);
...
return objectMapper;
}
}
ํด๋น ์ค์ ํ ๋ค์ ํ ์คํธ๋ฅผ ์งํํ์ ๋, ํญ๊ณต ์๋น์ค ๋ด์์ ClassCastException ์ค๋ฅ๋ ์ฌ๋ผ์ก์ง๋ง ํ์ ์ ๋ณด๊ฐ ์ถ๊ฐ๋๋ฉด์ Response type์ด ๊ธฐ์กด๊ณผ ๋ฌ๋ผ์ก๊ณ , ์ด๋ก ์ธํด ๋ค๋ฅธ ์๋น์ค์์ ํด๋น APIํธ์ถ ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.

๊ธฐ์กด์๋ DTO ํ์ ์ ๋ณด๊ฐ ํฌํจ๋์ง ์์์ง๋ง activateDefaultTyping ์ค์ ์ผ๋ก ์ธํด @class ํ๋๊ฐ ์ฝ์ ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๐ ์์ธ ๋ถ์ ๋ฐ ํด๊ฒฐ
ํด๋น ๋ฌธ์ ์ ์์ธ์ ํ์ ํด ๋ณด๋, @class ํ๋๊ฐ ์ฝ์ ๋์ด ์ธ๋ถ ์๋น์ค์์ JSON ํ์ฑ์ ์คํจํด์ ๋ฐ์ํ ๋ฌธ์ ์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ญ์ง๋ ฌํ ์ค๋ฅ์ ์์ธ์ด ๋ ์ ์๋ ๋ถํ์ํ ํ์ ์ ๋ณด๋ ์ ์ฅํ๋ ํด๋น ๊ธฐ๋ฅ(activateDefaultTyping)์ ObjectMapper ์ค์ ์์ ์ ๊ฑฐํ๊ณ , CacheConfig์์ ๊ฐ ํ์ ๋ณ๋ก ์ง์ Serializer๋ฅผ ๋ช ์์ ์ผ๋ก ๋ฑ๋กํ์ฌ ๋ถํ์ํ ํ์ ์ ๋ณด ์์ด ์ง๋ ฌํ๊ฐ ์ด๋ค์ง๋๋ก ์ค์ ํด ์ฃผ์๋ค.
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ObjectMapper objectMapper) {
// ๊ธฐ๋ณธ ์ง๋ ฌํ๊ธฐ
GenericJackson2JsonRedisSerializer genericSerializer = new GenericJackson2JsonRedisSerializer(
objectMapper);
// ํน์ DTO์ฉ ์ง๋ ฌํ๊ธฐ
Jackson2JsonRedisSerializer<AirportResponse> airportSerializer = new Jackson2JsonRedisSerializer<>(
objectMapper, AirportResponse.class);
Jackson2JsonRedisSerializer<AirlineResponse> airlineSerializer = new Jackson2JsonRedisSerializer<>(
objectMapper, AirlineResponse.class);
Jackson2JsonRedisSerializer<FlightDetailResponse> flightSerializer = new Jackson2JsonRedisSerializer<>(
objectMapper, FlightDetailResponse.class);
// ๊ธฐ๋ณธ ์บ์ ์ค์
RedisCacheConfiguration baseConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericSerializer))
.disableCachingNullValues();
// airports ์บ์ ์ค์ (12์๊ฐ TTL)
RedisCacheConfiguration airportConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(airportSerializer))
.disableCachingNullValues()
.entryTtl(Duration.ofHours(12));
// airlines ์บ์ ์ค์ (12์๊ฐ TTL)
RedisCacheConfiguration airlineConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(airlineSerializer))
.disableCachingNullValues()
.entryTtl(Duration.ofHours(12));
// flights ์บ์ ์ค์ (10์ด TTL)
RedisCacheConfiguration flightConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(flightSerializer))
.disableCachingNullValues()
.entryTtl(Duration.ofSeconds(10));
// flightOffers ์บ์ ์ค์ (1๋ถ TTL)
RedisCacheConfiguration flightOffersConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericSerializer))
.disableCachingNullValues()
.entryTtl(Duration.ofMinutes(1));
...
}
...
}
๊ทธ ๊ฒฐ๊ณผ, Response type์์ @class๊ฐ ์ฌ๋ผ์ง๋ฉฐ ํด๋น API ํธ์ถ ์ ์ ์์ ์ผ๋ก ์บ์์ ์ ์ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์์๊ณ , ๋ค๋ฅธ ์๋น์ค์์ ํธ์ถ ์์๋ ์ง๋ ฌํ ๋ฐ ์ญ์ง๋ ฌํ ์ค๋ฅ ์์ด ์ฑ๊ณต์ ์ผ๋ก ์บ์์์ ์กฐํํด ์ค๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.



์ฌ๊ธฐ๊น์ง Redis Cache ์ ์ฉ ์์ ๋ฐ์ํ ์ ์๋ ์ง๋ ฌํ ๋ฐ ์ญ์ง๋ ฌํ ๋ฌธ์ ์ ์์ธ๊ณผ ํด๊ฒฐ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์๋ค. ์บ์์ ๋ํด ์ ๋ฆฌํ ๋ด์ฉ๊ณผ ์์์ ์์ฃผ ์ธ๊ธํ๋ ObjectMapper์ ๋ํ ๋ด์ฉ์ ์๋ ํฌ์คํ ์์ ํ์ธํ ์ ์๋ค.
[Redis] Redis Cache๋ฅผ ํ์ฉํด ๋ฐ์ดํฐ ์กฐํ ์๋ ๊ฐ์ ํ๊ธฐ - (1)
๐ ๋ชฉ์ฐจ SpringBoot๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด์ปค๋จธ์ค ์๋น์ค๋ฅผ ๊ตฌํํ๋ ์ค,์ด์ปค๋จธ์ค์ ํน์ฑ์ ์กฐํํ๋ ์ผ์ด ๋ง์๋ฐ ์ฑ๋ฅ์ด ์ ๋์ค์ง ์๋๋ค๋ ๊ฒ์ ํ์ธํ๋ค.๋จผ์ , Redis์ ์ด๋ค ํน์ง ๋๋ฌธ์ ์ฑ๋ฅ์ด ๊ฐ์ ๋
zzudev.tistory.com
[Java] Jackson ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ObjectMapper
zzudev.tistory.com