From 3664161a523d3078105f604fbd141f80dc142506 Mon Sep 17 00:00:00 2001 From: kimjb Date: Mon, 1 Jun 2026 23:36:27 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20=EC=BA=90=EC=8B=9C=20=EC=A7=81=EB=A0=AC?= =?UTF-8?q?=ED=99=94=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catcheat-backend-dashboard.json | 4 +-- .../catcheat-loadtest-dashboard.json | 2 +- .../catchtable/global/config/CacheConfig.java | 27 ++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/grafana/provisioning/dashboards/catcheat-backend-dashboard.json b/grafana/provisioning/dashboards/catcheat-backend-dashboard.json index 389f848..6af3d7a 100644 --- a/grafana/provisioning/dashboards/catcheat-backend-dashboard.json +++ b/grafana/provisioning/dashboards/catcheat-backend-dashboard.json @@ -1219,13 +1219,13 @@ "targets": [ { "datasource": { "type": "prometheus", "uid": "DS_PROMETHEUS" }, - "expr": "sum(rate(kafka_consumer_fetch_manager_records_consumed_total{job=\"catchtable-app\"}[1m])) by (topic)", + "expr": "sum(rate(kafka_consumer_fetch_manager_records_consumed_total{job=\"catchtable-app\", topic!~\".*_.*\"}[1m])) by (topic)", "legendFormat": "소비 - {{topic}}", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "DS_PROMETHEUS" }, - "expr": "sum(rate(kafka_producer_record_send_total{job=\"catchtable-app\"}[1m])) by (topic)", + "expr": "sum(rate(kafka_producer_record_send_total{job=\"catchtable-app\", topic!~\".*_.*\"}[1m])) by (topic)", "legendFormat": "생산 - {{topic}}", "refId": "B" } diff --git a/grafana/provisioning/dashboards/catcheat-loadtest-dashboard.json b/grafana/provisioning/dashboards/catcheat-loadtest-dashboard.json index e77c6fd..29d6200 100644 --- a/grafana/provisioning/dashboards/catcheat-loadtest-dashboard.json +++ b/grafana/provisioning/dashboards/catcheat-loadtest-dashboard.json @@ -2192,7 +2192,7 @@ "type": "timeseries", "targets": [ { - "expr": "sum by (topic) (rate(kafka_consumer_fetch_manager_records_consumed_total[1m]))", + "expr": "sum by (topic) (rate(kafka_consumer_fetch_manager_records_consumed_total{topic!~\".*_.*\"}[1m]))", "legendFormat": "{{topic}}", "refId": "A", "datasource": { "type": "prometheus", "uid": "DS_PROMETHEUS" } diff --git a/src/main/java/com/catchtable/global/config/CacheConfig.java b/src/main/java/com/catchtable/global/config/CacheConfig.java index d18b95a..41e7a9e 100644 --- a/src/main/java/com/catchtable/global/config/CacheConfig.java +++ b/src/main/java/com/catchtable/global/config/CacheConfig.java @@ -1,5 +1,9 @@ package com.catchtable.global.config; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,9 +23,12 @@ public class CacheConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { + GenericJackson2JsonRedisSerializer valueSerializer = + new GenericJackson2JsonRedisSerializer(buildCacheObjectMapper()); + RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) - .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer)) .disableCachingNullValues(); Map cacheConfigs = Map.of( @@ -33,4 +40,22 @@ public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) .withInitialCacheConfigurations(cacheConfigs) .build(); } + + // record 클래스는 final 이라 GenericJackson2JsonRedisSerializer 의 기본 NON_FINAL typing 에서는 + // @class 메타 필드가 안 붙어서 역직렬화 시 "expected VALUE_STRING for type id" 실패. + // EVERYTHING typing 으로 record/List 모두 type info 포함되도록 명시. + // BasicPolymorphicTypeValidator 로 Object 하위 타입만 허용해 deserialization gadget 위험 차단. + private ObjectMapper buildCacheObjectMapper() { + BasicPolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(Object.class) + .build(); + + return new ObjectMapper() + .registerModule(new JavaTimeModule()) + .activateDefaultTyping( + validator, + ObjectMapper.DefaultTyping.EVERYTHING, + JsonTypeInfo.As.PROPERTY + ); + } }