browser

브라우저 캐시 정책으로 인한 버전 충돌 에러 해결

하리하링웹 2024. 3. 31. 23:59

개요

얼마전에 회사에 내가 작업한 코드가 배포되었는데 직후 실 서비스에서 엄청난 에러로그가 발생하여 바로 롤백을 하게된 일이 있었다.

 

 원인으로는 새로운 버전이 배포되면서 이전 버전을 사용하고 있던 유저가 페이지를 새로고침 하지 않고 내부 팝업을 열었을때에 새로운 버전의 코드를 가져와 오래된 버전의 코드와 충돌이 발생하는것이였으며, 새로운 버전의 코드를 가져오면 해당 코드로 잘 동작하는것이 당연하다고 생각할 수 있지만 회사 프레임워크에서 어떠한 파일을 실행할때에 내부적으로 클로저로 한번 감싸 오래된 코드가 남아있을 수 있는 가능성이 잠재되어 있었다. 물론 이러한 방식은 캐시가 정상적으로 동작한다고 가정하였을때에 유효한 방식이라고 말할 수 있다.

 

 하지만 캐시가 정상적으로 동작하지 않았으며 내가 배포한 코드가 이 모든 상황에 정확하게 일치하여 실제 서비스에서 엄청난 에러로그를 발생시킨것이다.

 

일단 문제 자체는 너무 흔하게 발생할 수 있는 이슈라고 생각했고 그동안 어떻게 발생하지 않았는지 신기하다고 생각하며 원인을 자세히 분석해보았다. 결과적으로는 핵심적인 부분을 건들지 않는이상은 발생하기 쉽지 않은 이슈였으며, 여러가지의 상황을 모두 만족해야했기에 그동안 발견되지 않을 수 있는 문제였다.

 

원인

 일단 회사에서는 페이지에 접속시에 minified된 여러 파일들의 버전 정보를 담고있는 객체를 서버에 요청하고 서버에서는 version map이라는 하나의 맵 객체로 담아 반환해준다.

 

 이 version map 요청은 브라우저 캐시에 완전히 의존하며, 유저가 초기 접속 하였을 때 timestamp 값을 생성한 뒤 이 값을 request url에 query string으로 같이 보내 version map 요청에 대한 url을 동일하게 유지해주어 브라우저에 캐싱된다. 이후 각 파일 요청시 version map을 기준으로 요청하여 동기화된 파일을 받아온다.

 

 version map은 페이지이동, 팝업 오픈과같은 navigation 요청마다 새로 요청하지만 이는 초기 timestamp값으로 캐싱되어있기에 유저가 강제로 새로고침하지 않는 이상은 불변성을 유지하며 버전 차이로 인한 충돌이 발생하지 않는것이 정상적인 동작이다. 하지만 사실은 같은 url로 보내는 요청인데도 불구하고 특정 상황을 만족한다면 캐싱된 파일을 가져오지 않고있었으며 이로인해 새로운 version map을 받아오고있었다.

 

 이로인해 웹 서버가 새 버전으로 배포된 상태에서 팝업같은것을 열었을 때에 버전이 동기화 되지 않는 상태가 존재하게 되며 버전 차이로 인한 잠재적 에러 가능성이 생기게 되는것이였다.

 

 네트워크 요청을 확인한 결과 실제로 동일 url에 대해 캐싱이 되지 않는것을 확인할 수 있었다.

 

 해당 문제 분석 결과 브라우저의 기본 캐시 정책문제인것으로 밝혀졌으며 자세한 내용은 아래의 글을 참고하면 될 것 같다.  

[참고 글]

 

요약하자면 서버와 클라이언트 모두 별도의 캐시 정책을 설정해주지 않았으며 이로인해 브라우저에서 제공하는 기본 캐시 만료 정책을 사용하게되어 발생한 문제이다.

 

 즉 회사의 version map 요청에 대해 캐시 만료 정책이 따로 설정되어 있지 않은 상태였다.

 

 회사의 각 파일은 s3에 요청을 보내 받아오며 이 파일들의 경우 max-age가 설정되어있고 캐시가 만료 시간이 잘 설정되어 있는것을 확인할 수 있었다.

 

 하지만 오직 version map 요청의 경우에만 회사 웹 서버에서 직접 받아오고있었으며 웹 서버에서 response header를 통해 따로 캐시 만료 정책을 내려주지 않고있는것을 확인할 수 있었다.

 

해결

 이러한 문제를 해결하기 위해서는 클라이언트쪽에서 fetch시에 timestamp 값을 보고 cache 값을 force-cache로 넣는 방법 혹은 서버쪽에서 따로 캐시 만료 정책을 내려주는 두가지의 해결 방안이 있었다.

 

 물론 클라이언트의 fetch를 직접 건드는 방안은 썩 좋은 패턴은 아니며 개발 방향과도 맞지 않았으며 브라우저 캐시를 잘 활용하는것이 정상적인 동작이라고 판단했기에 서버쪽에서 캐시 만료 정책을 잘 내려주는 방안으로 해당 이슈를 해결하기로 결정하였다.

 

 물론 이러한 방식은 혹시라도 브라우저 캐시를 disable해놓는 상황이나 disable하고 사용하는 유저의 경우에는 동일한 문제를 겪을 수 있기에 정말로 근본적인 문제가 해결되었다고는 말할 수 없지만 현재 회사의 서비스 특성이나 일반적인 유저라면 이러한 문제를 겪을 일은 없기에 위의 방식으로 해결하여도 대부분의 경우를 방어할 수 있다고 판단하였으며, 추후 해당 케이스로 인한 문제가 발생하였을 때 다시 대처하는것이 비용적인 측면에서 맞다고 생각하였다.

 

후기

이번 문제로 인해 개발자 경력에 있어 웹에서 가장 복잡하고 해결하기 힘든 정책인 쿠키, 캐시 정책에 관련된 복잡한 이슈를 모두 겪어보았다. 쿠키 이슈의 경우 그 당시 개발 경력이 많지도 않았고 이론도 부족하였기에 해결하는데에 많은 시간이 걸렸던 것 같은데 이번 이슈는 생각보다 금방 원인을 찾았으며 원인을 잘 정리하여 공유해서 타 팀과의 의사소통도 별 문제 없이 이루어져 금방 해결할 수 있었다.

 

 개발자가 상대적으로 경력이 중요하지는 않다고 생각하지만 실무를 해보지 않고는 겪어보기 힘든 상황에서 경력과 쌓아놓은 이론만큼 중요한것은 없다는것을 다시금 느낀 이슈였다.