이전 포스트에서 공통 GNB를 배포 후, 브라우저 캐싱으로 인한 장애를 해결하려 했던 내용에 관해 이야기했습니다.
(이전 글을 읽으셔야 다음 내용이 이해됩니다.)
브라우저 캐싱 - 이슈 첫번째 이야기 (feat. 공통 GNB) 링크
본 포스트에서는 앞에서 선택했던 해결방법으로 인해서, 이후 발생했던 내용과 개발 지식을 약간 섞어 이야기합니다.
우리는 이전 포스트(링크)에서 GNB 리소스를 로드할 때, 리소스 주소에 쿼리스트링 값을 넣어 브라우저가 GNB 리소스를 새로운 리소스라고 인식하게 만들고, 캐시하지 못하도록 하여 문제를 해결했습니다.
목적은 달성했지만, 커뮤니케이션 비용이 증가했습니다.
첫번째, 배포요청
GNB를 수정하고 배포 할 때마다 각 솔루션에서 사용자에게 수정된 내용을 빠르게 보여주고자 하면, 각 솔루션마다 빌드해야 했습니다.
왜?
그래야 쿼리스트링 값(v={특정값})이 변경되어 새로운 버전의 GNB를 받아올 테니까요.
이는 즉, GNB를 수정하면 각 솔루션 담당자에게 빌드를 다시 해달라고 요청해야 했기에 커뮤니케이션이 증가했습니다.
그런데 몇 년 동안 각 솔루션 담당자에게 빌드를 요청하지 않아도 당장 문제는 없었습니다. 첫번째 문제 해결 이후, 일정 시간 동안 GNB에 수정된 작업은 주로 간단한 유지보수성 UI/UX 변경 작업뿐이었기 때문이였죠.
GNB 배포 후, (예측할 수 없는) 시간이 지나면 사용자의 브라우저에서는 캐시된 데이터를 사용하지 않고 리소스를 새로 불러왔습니다.
그래서 눈앞에 보이는 큰 문제라고 생각하지 않았던 것 같습니다.
두번째, 디버깅
회사에서 서비스를 배포 하기 전에 feature 환경, dev 환경, stage 환경 별로 테스트를 진행하고 고객 환경(prod)으로 배포됩니다.
어느 시점부터 GNB 수정 작업이 빈번하게 진행되었습니다. 수정하면 각 배포 환경에서 테스트를 진행합니다.
이 과정에서 디버깅을 해주시는 분의 브라우저가 GNB에 대한 리스소를 캐싱하는 문제가 발생합니다.
문의가 옵니다…
“수정이 안 된 거 같은데요?”, “뭐가 바뀐 거죠?”
그럴 때마다 “강려크한 새로고침을 해보세요” 로 답장을 드립니다.
일부는 해도 안 된다고 합니다…
이 과정에서도 불필요한 커뮤니케이션 비용이 발생합니다.
그리고 커뮤니케이션 비용 문제뿐만 아니라 똑같은 장애가 발생할지 모르는 상황입니다.
GNB수정 작업이 빈번하게 발생하고 있고, 영향범위가 큰 작업이 들어올 거라 예상했습니다.
결국 첫 번째 문제에서 발생했던 문제가 각 솔루션에서 빌드 후 배포하지 않으면 반복될 것이기 때문이었죠.
이제 해결하러 갑시다. 이번에도 간단하게 문제를 해소했습니다.
결론부터 말하면, GNB 리소스 요청의 응답 헤더에 Cache-Control 값으로 no-cache 를 내려주었습니다.

Header → Cache-Control: no-cache
Cache-Control 값으로 브라우저에서의 캐시 정책을 설정할 수 있습니다.
no-cache는 캐시를 하지 않는다는 의미가 아닙니다.
‘항상 서버에 확인해라’라는 의미가 있습니다.
그래서 브라우저는 캐시하지 않고 계속 GNB리소스를 요청합니다.
동작 흐름
처음 GNB리소스를 요청하면 리소스와 함께 200 상태 코드를 응답합니다.

그 이후 요청은 304 상태 코드를 응답합니다.
이때는 브라우저에 캐시 데이터를 가져다가 사용하게 됩니다.
요청 리소스 크기가 305 kb 에서 0.2kb 로 변경된 모습을 볼 수 있습니다.

그럼, 브라우저는 어떻게 리소스 차이를 인지하고 새로운 리소스를 내려줄 수 있을까요?
저희는 Last-Modified 와 Etag 중 Etag 를 이용하고 있습니다.
Etag 값을 요청한 값과 비교하여 다르면 새로운 리소스와 함께 200 응답을 내려줍니다.
처음 GNB 리소스를 요청하면 다음 이미지와 같이 Etag 값을 응답 헤더에 내려줍니다. 브라우저는 해당 Etag 를 기억합니다.

이후 호출에는 요청헤더 값 If-None-Match 에 이전에 요청 했을 때 받은 Etag 값을 넣어서 요청합니다.

GNB 웹 서버에서의 Etag 값과 If-None-Match 값과 비교 후, 같은지 다른지 확인합니다.
다르다면 최신 데이터가 있다고 생각하고 새 리소스와 200 상태 코드를 응답합니다.
같다면 304 상태 코드값을 응답하여 브라우저에 캐시 되어있는 데이터를 사용하도록 합니다.
Last-Modified
마지막에 수정된 날짜를 기준으로 변경되었는지 확인하는 방법도 있습니다.
이때는 브라우저가 If-Modified-Since 날짯값과 Last-Modified 날짯값을 비교합니다.
Nginx 의 Etag
GNB리소스는 nginx 웹서버를 이용하여 제공합니다.
Nginx는 기본적으로 정적 파일을 서빙할 때, 자동으로(기본 설정값) Etag를 생성합니다.
Etag 값은 다음 3가지를 조합하여 생성합니다.
파일의 마지막 수정 시간(last-modifted time)
파일의 크기(inode size)
파일의 inode 번호
디스크/메모리 캐시 와 304 응답 차이
디스크/메모리 캐시는 리소스 요청 자체를 브라우저에서 진행하지 않습니다.
반면, 304 응답은 브라우저에게 웹 서버에게 리소스 요청을 진행합니다.
304 응답은 Etag 값을 확인해서 변동이 없으면 응답 값만 내려주기 때문에 전송 크기가 매우 작습니다.
no-cache vs max-age=0
둘은 동일한 의미를 뜻합니다.
서버 부하 체크
기존(문제해결 전)에는 브라우저에서 캐시 되어있을 확률이 높았습니다. 그래서 GNB 웹서버에 요청하는 빈도수가 많지 않았습니다.
하지만, no-cache로 설정한 후부터는 무조건 웹 서버에 확인 요청을 진행하게 됩니다.
이에 따라 요청량이 많아져 웹 서버에서 다량의 호출을 버틸 수 있는지 의문이 들었습니다.
이에 2가지를 진행했습니다.
첫번째로 부하테스트를 진행했습니다. 사내에서 주로 nGrinder 를 이용하여 부하 테스트를 진행하고 있습니다.
부하 테스트를 데브옵스 팀에게 요청하여 결과 TPS(초당 트랜젝션 수, Transactions Per Second)를 확인해 보니 저희 기준에 통과되었고, 데브옵스팀에서도 괜찮다고 답변받았습니다.
두번째로 서비스 배포 후, 모니터링 지원 요청을 해놓았습니다. 장애가 발생했을 때, 빠른 롤백을 지원하도록 하고,
다량의 요청을 대비해 일정 기준 요청이 넘어가면 pods(웹서버)를 오토 스케일링할 수 있도록 준비해두었습니다.
다행히 배포 후, 특이 사항 없었습니다.
이전에 문제를 한차례 해결했지만,
이후 커뮤니케이션 비용이 증가하는 현상과 곧 똑같은 장애가 발생할 것이라는 예상되는 문제를 해결하고자 합니다.
간단하게 GNB 리소스를 내려주는 Nginx 서버 응답의 헤더 값 cache-control 에 no-cache 값을 내려주어, 브라우저가 항상 웹 서버에 리소스를 요청하도록 합니다.
그렇게 최신 버전의 GNB 리소스가 있는지 확인하여 사용자에게 최신 리소스를 보여줄 수 있게 되었고, GNB를 수정했다고 각 솔루션에서 빌드를 할 필요가 없어졌습니다.