[#60] Trace Context의 초기화 오류로 발생하는 OOM 문제 해결 (#63)

<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) -->

## 📝 관련 이슈 및 PR

**PR과 관련된 이슈 번호**
- #60

##  작업 내용
<!-- 어떤 작업을 했는지 알려주세요! -->
- JVM Heapdump 분석 결과 Opentelemetry의 Context가 과도한 메모리를 사용하고 있었음.(Retain 925MB / 파드 메모리 할당 Limit 2GB 중)
- 원인은, 하나의 요청이 끝날 때 Trace Context가 초기화 되지 않았고, 동일한 Tomcat 스레드에 서로 다른 HTTP 요청의 SPAN이 계속 쌓인 것.
- Slow-query 를 기록하기 위한 Datasource-Proxy와 충돌이 그 이유였고, 슬로우쿼리 기록은 필요하기에 CurrentTraceContext를 이용하여 필터의 Finally 과정에서 컨텍스트를 정리하도록 수정

## 🧪 테스트
<!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! -->
- JVM의 Old, Survivor Space에서의 메모리 사용량 감소 확인
- 동일한 환경 테스트에서 OOM 발생하지 않음.

## 📚 참고 자료 및 기타
<!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! -->

Reviewed-on: #63
Co-authored-by: pricelees <priceelees@gmail.com>
Co-committed-by: pricelees <priceelees@gmail.com>
This commit is contained in:
이상진 2025-10-14 00:38:46 +00:00 committed by 이상진
parent bba3266f3f
commit 5f2e44bb11
2 changed files with 10 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.sangdol.common.web.asepct.ControllerLoggingAspect import com.sangdol.common.web.asepct.ControllerLoggingAspect
import com.sangdol.common.web.servlet.HttpRequestLoggingFilter import com.sangdol.common.web.servlet.HttpRequestLoggingFilter
import com.sangdol.common.web.support.log.WebLogMessageConverter import com.sangdol.common.web.support.log.WebLogMessageConverter
import io.micrometer.tracing.CurrentTraceContext
import org.springframework.boot.web.servlet.FilterRegistrationBean import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
@ -17,9 +18,10 @@ class WebLoggingConfig {
@Bean @Bean
@DependsOn(value = ["webLogMessageConverter"]) @DependsOn(value = ["webLogMessageConverter"])
fun filterRegistrationBean( fun filterRegistrationBean(
webLogMessageConverter: WebLogMessageConverter webLogMessageConverter: WebLogMessageConverter,
currentTraceContext: CurrentTraceContext
): FilterRegistrationBean<OncePerRequestFilter> { ): FilterRegistrationBean<OncePerRequestFilter> {
val filter = HttpRequestLoggingFilter(webLogMessageConverter) val filter = HttpRequestLoggingFilter(webLogMessageConverter, currentTraceContext)
return FilterRegistrationBean<OncePerRequestFilter>(filter) return FilterRegistrationBean<OncePerRequestFilter>(filter)
.apply { this.order = Ordered.HIGHEST_PRECEDENCE + 2 } .apply { this.order = Ordered.HIGHEST_PRECEDENCE + 2 }

View File

@ -5,6 +5,7 @@ import com.sangdol.common.utils.MdcStartTimeUtil
import com.sangdol.common.web.support.log.WebLogMessageConverter import com.sangdol.common.web.support.log.WebLogMessageConverter
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.micrometer.tracing.CurrentTraceContext
import jakarta.servlet.FilterChain import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse import jakarta.servlet.http.HttpServletResponse
@ -15,7 +16,8 @@ import org.springframework.web.util.ContentCachingResponseWrapper
private val log: KLogger = KotlinLogging.logger {} private val log: KLogger = KotlinLogging.logger {}
class HttpRequestLoggingFilter( class HttpRequestLoggingFilter(
private val messageConverter: WebLogMessageConverter private val messageConverter: WebLogMessageConverter,
private val currentTraceContext: CurrentTraceContext
) : OncePerRequestFilter() { ) : OncePerRequestFilter() {
override fun doFilterInternal( override fun doFilterInternal(
request: HttpServletRequest, request: HttpServletRequest,
@ -32,9 +34,12 @@ class HttpRequestLoggingFilter(
try { try {
filterChain.doFilter(cachedRequest, cachedResponse) filterChain.doFilter(cachedRequest, cachedResponse)
cachedResponse.copyBodyToResponse() cachedResponse.copyBodyToResponse()
} catch (e: Exception) {
throw e
} finally { } finally {
MdcStartTimeUtil.clear() MdcStartTimeUtil.clear()
MdcPrincipalIdUtil.clear() MdcPrincipalIdUtil.clear()
currentTraceContext.maybeScope(null)
} }
} }
} }