naver / scavenger

A runtime dead code analysis tool
Apache License 2.0
405 stars 43 forks source link

스캐빈저 agent 적용 시 SerializationFailedException 발생 문의 #125

Open JaeHyeonKim19 opened 5 months ago

JaeHyeonKim19 commented 5 months ago

안녕하세요, 스캐빈저 적용 시 레디스 캐시 데이터 deserialize 할 때 SerializationFailedException가 발생하여 관련 내용 문의드립니다.

배경 지식 공유

현재 프로젝트(spring boot)에서 레디스 캐시를

  1. Serializer는 JdkSerializationRedisSerializer를 사용
  2. 캐시되는 클래스는 Serializable 인터페이스 상속 중
  3. 여러 대의 서버가 캐시 목적으로 단일 레디스 서버 사용 중

와 같은 환경에서 사용 중 입니다.

에러 상황

스캐빈저 agent 적용 후 배포 시 SerializationFailedException에러 발생 (stack trace는 본문 마지막에 첨부 드리겠습니다.)

문의 사항

  1. 스캐빈저 소개 PDF를 보면 'byte code 수정을 통해 메소드 실행 전/후 코드를 주입하고 있다'고 되어있는데요, 이로인해 스캐빈저 적용 전/후 동일 클래스에서 jvm에서 생성하는 serialVersionUID가 변경될 가능성이 있을까요?
  2. 실행 환경에 따라 조작되는 byte code 값이 다를 수 있나요?
    1. 개발환경, 로컬환경에서 테스트 했을 때 환경별로 에러 발생 유무가 다른 경우가 있어 질문드립니다.
  3. 여러 대의 서버가 떠 있을 경우 각 서버에서 조작되는 byte code 값이 다를 수 있나요?
    1. 위 2번 질문에 답변이 '예'라면 동일 개발환경에서도 별도 인스턴스라면 조작되는 byte값이 다를 수 있지 않을까하여 질문드립니다. 메소드 실행 전/후 코드 주입 시 각 인스턴스의 고유값이 포함되는 경우 등에 byte code값이 다르게 조작될 수 있다고 생각했습니다. 또한 이 경우 각 클래스의 serialVersionUID 값 또한 다르게 생성 될 수 있을 것으로 보이고 각 다른 서버에서 cache를 write/read 할 경우 동일 에러가 발생될 가능성이 있다고 생각됩니다.

(참고사항) 현재 생각중인 해결 방안

스캐빈저 설정 옵션 중 annotations 옵션을 사용하여 캐시에 사용되는 dto들은 스캔범위에서 제외하고자 합니다. (레디스 serializer를 변경해서 해결할 수 도 있지만 해당 방식은 영향범위가 클것으로 예상되어 serializer 수정없이 해결하고자 합니다.)

에러 상세 stacktrace

사내 프로젝트라 일부 정보(패키지 경로 등)는 삭제 또는 수정하였습니다. 참고 부탁드립니다.


org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.InvalidClassException: com.abc.dto.GoodsDto; local class incompatible: stream classdesc serialVersionUID = -2606788700877345633, local class serialVersionUID = -6040559779750867146
    at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:84)
    at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:48)
    at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.read(RedisSerializationContext.java:272)
    at org.springframework.data.redis.cache.RedisCache.deserializeCacheValue(RedisCache.java:260)
    at org.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:94)
    at org.springframework.cache.support.AbstractValueAdaptingCache.get(AbstractValueAdaptingCache.java:58)
    at org.springframework.cache.transaction.TransactionAwareCacheDecorator.get(TransactionAwareCacheDecorator.java:80)
    at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:73)
    at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:555)
    at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:520)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:402)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:346)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
    at com.abc.mapper.GoodsDtoMapper$$EnhancerBySpringCGLIB$$b61f6dcf.map(<generated>)
    at com.abc.service.GoodsService.findById(GoodsService.java:12)
    at com.abc.service.GoodsService$$FastClassBySpringCGLIB$$6375fce8.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
    at com.abc.service.GoodsService$$EnhancerBySpringCGLIB$$47b5e4cf.findById(<generated>)
    at com.abc.controller.GoodsApiController.findGoods(GoodsApiController.java:58)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.abc.configuration.filter.Filter.doFilterInternal(Filter.java:28)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.abc.configuration.filter.Filter2.doFilterInternal(Filter2.java:37)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.abc.configuration.filter.Filter3.doFilterInternal(Filter3.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.servlet.v3_1.OpenTelemetryHandlerMappingFilter.doFilter(OpenTelemetryHandlerMappingFilter.java:83)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.InvalidClassException: com.abc.goods.dto.GoodsDto; local class incompatible: stream classdesc serialVersionUID = -2606788700877345633, local class serialVersionUID = -6040559779750867146
    at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:78)
    at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:36)
    at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:82)
    ... 98 common frames omitted
Caused by: java.io.InvalidClassException: com.abc.dto.GoodsDto; local class incompatible: stream classdesc serialVersionUID = -2606788700877345633, local class serialVersionUID = -6040559779750867146
    at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)
    at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2020)
    at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1870)
    at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2020)
    at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1870)
    at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2201)
    at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687)
    at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:489)
    at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:447)
    at org.springframework.core.serializer.DefaultDeserializer.deserialize(DefaultDeserializer.java:72)
    at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:73)
    ... 100 common frames omitted
sohyun-ku commented 5 months ago

@JaeHyeonKim19

안녕하세요. scavenger-agent의 instrument 과정에서 serialVersionUID이 변경된 것으로 보입니다. 아래 이슈와 관련이 있어 보이며 제보해주신 이슈는 패치하여 다음 릴리즈에 포함하도록 하겠습니다. 우선 말씀해주신 것 처럼 직렬화가 필요한 DTO 클래스는 제외 후에 사용 부탁드리겠습니다. ( )

https://github.com/raphw/byte-buddy/issues/167

JaeHyeonKim19 commented 5 months ago

안녕하세요 @sohyun-ku 님, 빠른 답변 감사합니다. 혹시 다음 릴리즈 시기는 언제쯤인지 대략적으로 알 수 있을까요?

taeyeon-Kim commented 5 months ago

@sohyun-ku 님 https://stackoverflow.com/questions/54163653/override-doesnt-work-when-using-bytebuddy-disableclassformatchanges 를 보면 disableClassFormatChanges옵션 때문에 예상치 못한 이슈가 더 발생할 수 도 있을 것 같습니다. 또.. bytebuddy로 인해 SerialVersionUID가 바뀌나 테스트해보니까 그런 것 같지도 않네요.. 일단 상황을 재연해보는게 먼저 일 것 같습니다 😭

sohyun-ku commented 5 months ago

@JaeHyeonKim19 님 이슈 재연 및 기능 적용 시 영향도 확인이 필요해 릴리즈 시점을 공유드리기 어렵습니다. 로컬 환경에서 제보해 주신 이슈에 대해서 재연이 되지 않는 상황이라 확인이 더 힘든 상황입니다. 이슈 확인을 위해 사용하시는 환경 설명 및 재연이 되는 상황을 샘플 프로젝트와 같은 형태로 제공해 주실 수 있으실까요?

JaeHyeonKim19 commented 5 months ago

네, 확인해보고 공유드리겠습니다.