D
Archive
목록으로

블로그

블로그 제작기 - 캐싱 고민

·조회 수 33
블로그 제작기 - 캐싱 고민

블로그 만들기의 첫 걸음


1. 일단 만들자#

처음은 MVP 차원에서 성능은 고려하지 않고 UI/기능 구현에 집중하였습니다.

냅다 운영용으로 빌드를 해보았을 때

API 요청 용도로 세팅한 supabase server client에서 동적 함수인 cookies를 호출하고, 이 것이 전역적으로 사용되다보니 전부 동적 페이지로 빌드된 모습입니다. 

이렇게 첫 빌드 후 페이지에 접근을 해보니 역시 느리네요..! 피드, 커리어 페이지를 각각 들어가보니 1.2초 정도로 데이터가 전무한 페이지임에도 TTFB에 꽤나 큰 시간을 잡아 먹고 있었습니다.

TTFB : Time To First Byte의 약자로 네트워크 요청의 응답의 첫 번째 바이트가 도착하기까지 걸린 시간이다.

프론트에서는 HTML을 받고 그 문서 안에 담긴 리소스들을 요청하기에 보통 HTML 문서의 첫 바이트를 받기까지 걸린 시간을 뜻한다.


2. 반드시 서버측 렌더링을 이용한다#

문제의 원인을 짚기 전에,

내가 하고 있는 블로그 개발이라는 작업이 무엇을 위한 것인가를 고민해보면

블로그 콘텐츠는 사람들에게 공유되어야 하므로 무료 마케팅이라고 볼 수 있는 검색엔진에 노출되는 것이 가장 중요한 도메인 분야라고 생각합니다.

버전1 기준으로 현재 이 프로젝트에 존재하는

  1. 피드 페이지
  2. 커리어 페이지
  3. 포스트 상세 페이지

모두 다 SEO가 중요한 페이지이고,

따라서 검색엔진의 크롤러들이 내 콘텐츠를 더 잘 읽어갈 수 있도록 서버측에서 페이지의 정보를 담아둬야 하기 때문에 CSR은 배제하였습니다.


3. 문제 규정과 해결책 고민#

문제의 원인으로는 페이지가 동적 빌드되어 SSR로서 매 요청마다 서버에서 실시간으로 페이지를 그려야하기 때문이라고 판단했습니다.

따라서 정적 빌드와 Next가 제공하는 캐시 기법에 대해 고민했는데 이를 중심으로 해결 수단을 내보자면

1) 데이터 캐싱 🤔#

데이터 캐싱은 한번 요청해서 받아온 응답 데이터에 대해 Next서버가 특정 시간동안 캐싱을 해두는 기법인데요. 즉 아래처럼 요청마다 DB에 액세스하게 되는 문제를 방지합니다.

매 새로고침마다 요청이 들어온 것을 볼 수 있음
매 새로고침마다 요청이 들어온 것을 볼 수 있음

RTT를 줄일 수 있기에 너무나 확실한 개선 수단이라 후보 1로 두었습니다.

RTT : Round Trip Time의 약자로 요청이 시작점에서 목적지로 갔다가 다시 시작점으로 돌아오는 데 걸리는 시간을 뜻합니다.

데이터 캐싱으로 API 요청을 스킵할 수 있어 RTT를 줄일 수 있습니다.

2) 풀라우트 캐싱#

1)에서 말한 매번 데이터를 요청하지 않고 캐싱해두는 것도 좋지만, 유저입장에서 더 가까운 단계에서 캐싱을 해두면 어떨까요?

일반적으로 유저가 SSR 페이지를 받기 위해서

  1. 유저가 프론트 서버에 요청
  2. 프론트는 요청을 받아 페이지를 그리기 시작함
  3. 페이지를 그리기 위해 데이터가 필요하다면 API 요청/응답까지 진행

하는 절차를 거칩니다.

위에서 TTFB가 1.2초가 걸렸던 이유는 이 모든 과정이 일어났기 때문입니다.

별 데이터가 없었음에도 1.2초는 꽤나 긴 시간이며

만약 제 블로그를 방문하는 유저도 많고, 이 페이지를 그리기 위해서 필요한 데이터가 많았다면 모든 유저의 각 요청마다 위 1~3의 플로우를 거친 후 응답하기에 프론트 서버도 바쁘고 DB도 많은 요청을 받게 되는 문제점이 있을텐데요.

데이터 캐싱으로 3은 생략하여 DB부하를 줄일 수 있겠지만 결국 서버까지 들어와서 캐싱해둔 데이터로 페이지를 그리는 과정이 필요하므로 서버에 대한 부하 위험은 여전히 존재합니다.

따라서 이보다 앞선 단계에서 페이지 자체를 캐싱해두면 서버가 페이지를 그리는 과정 또한 생략될 수 있겠죠.

즉, 위 1~3의 플로우를 거치지 않고 1에서 바로 응답을 할 수 있게 됩니다.

풀라우트 캐싱은 이러한 바램의 해결책으로서 미리 빌드단계에서 페이지를 정적빌드한 후 캐시해두었다가 들어온 요청에 대해 이 페이지를 유저에게 곧바로 서빙합니다.

Cache MISS 😢
Cache MISS 😢

현재는 이 풀라우트 캐싱이 적용되어 있지 않아 Cache MISS가 뜬 것을 볼 수 있습니다.


4. 풀라우트 캐싱의 재생성 주기에 관하여 - ISR#

제가 작성한 콘텐츠가 최초 공개 상태로 계속 유지되지 않을 수 있습니다.

수정으로 인해 내용이 바뀔 수 있고, 삭제로 인해 콘텐츠 자체가 삭제될 수도 있죠.

그런 변화에도 불구하고 풀라우트 캐싱된 페이지는 이러한 사항이 자동으로 반영되지 않고 변화 전 상태로 유저에게 서빙되는 문제가 있습니다.

그러기에 페이지를 주기적으로 재생성할 수 있도록 revalidation을 사용합니다.

다만 말그대로 주기적으로 재생성하는 것이기에 수정/삭제가 이루어졌더라도 그 주기가 지나지 않으면 기존에 캐싱된 페이지를 유저에게 서빙합니다.

만약 유저가 콘텐츠를 제작가능한 UGC 서비스라면 UX를 해치지 않기 위해서 유저의 수정/삭제 사항에 대해 페이지에 곧바로 반영해야 하므로 즉각 페이지를 재생성하기 위한 ODR까지 도입해야할 수 있으나

콘텐츠의 제작 권한이 오로지 어드민인 저에게만 있는 상황에서 즉각 재생성까지는 필요없다고 생각되네요. 유저 입장에서는 콘텐츠의 변화 사실을 당장은 알 필요가 없기 때문입니다.

즉, 수정/삭제된 페이지가 곧바로 반영될 필요는 없어서 ODR을 적용하지 않고 revalidation을 적용하는 선에서 마무리합니다.

💡 ODR: On-Demand Revalidation으로 정적 페이지를 유저가 콘텐츠를 수정/삭제 하는 등 원하는 시점에 재생성할 수 있는 기법

ODR이 정식 약어은 아니나 개인적으로는 ODR이라고 부르고 있습니다.


5. 정리#

1) 결론#

결국에는 2가지 캐싱을 둘다 적용하기로 했습니다. 풀라우트 캐싱이 적용되었다면 데이터 캐싱이 크게 의미는 없을 수 있습니다만 해서 캐싱을 두 겹으로 두는 것이 나쁠건 없습니다.

Cache HIT👍
Cache HIT👍

이제는 풀라우트 캐시가 적용되어 Cache HIT가 뜬 것을 볼 수 있죠. 실제 개선된 시간을 같이 보자면

초기 HTML을 응답받는 시간이 20ms대까지 줄어든 것을 볼 수 있습니다.

최초 빌드 후 접근했을 때 걸렸던 1.2초(1200ms) -> 20ms 어마어마한 차이죠? 60배 정도의 차이가 납니다

2) 가치관#

저는 개발 가치관으로 늘 속도를 꼽는데요.

  1. 우리 프로젝트에게 느끼게 될 유저 경험도 큰 이유이지만
  2. 현업의 도메인 분야가 SEO를 중요시하였기 때문입니다.

2와 관련하여서 SEO와 속도와의 상관관계를 보자면 구글은 SERP에 노출하는 순위를 매기는 중요 지표로 코어 웹바이탈을 언급합니다.

그 코어웹바이탈 중 하나인 LCP 지표의 좋고나쁨은 페이지 응답 속도가 거의 대부분 좌우합니다. 페이지의 제공이 빨라야 그 다음 리소스들의 요청/응답도 따라오기 때문이죠.

SEO가 곧 매출인 환경에서 일해왔던지라 속도를 위해 서버에서 네트워크 요청 수를 줄여보거나, 렌더링에 필요한 응답 데이터의 개수를 줄여보거나, HTML 문서 크기를 줄여보는 등 해봤지만 속도에 직빵인 것은 캐시만한게 없었습니다.

이 것이 캐시의 힘이고, 또 캐시를 어디서 하느냐가 중요한 이유입니다.

3) 추가적인 캐싱 방향성#

위에서 언급한 캐싱들로도 충분해보이나 저는 AWS CF같은 CDN에 페이지를 캐싱해두는 것 까지 원하는데요.

풀라우트 캐싱을 걸어도 어쨌든 제 서버까지 요청을 보내야하는 것은 마찬가지인데, CDN을 붙이면 제 서버가 아닌 유저에게 가장 가까운 곳에서 리소스를 서빙받을 수 있습니다.

현업의 도메인에서 메인으로 다루는 질문/답변 페이지와 블로그 페이지는 ‘정보를 전달하는 글’이라는 특징상 노출이 중요하고, 따라서 SEO라는 공통점이 있기에 더 몰입하게 되었네요.

이렇게 풀라우트 캐시 + 페이지 revalidation (ISR)의 힘을 체감해보았고 실제 적용했던 과정은 다음 글에서 적어볼까 합니다. ...

  • 1.블로그 개발 초기에는 UI/기능 구현에 집중했으나, 첫 빌드 후 TTFB가 1.2초로 측정되며 성능 문제가 발견되었습니다. Supabase server client의 동적 함수 사용으로 모든 페이지가 동적 빌드되어 매 요청마다 서버에서 실시간 렌더링이 발생했기 때문입니다.
  • 2.블로그는 검색엔진 노출이 중요한 도메인이므로 SEO를 위해 서버측 렌더링을 필수로 선택했습니다. 성능 개선을 위해 데이터 캐싱과 풀라우트 캐싱을 검토했고, 특히 풀라우트 캐싱은 페이지 자체를 미리 빌드하여 캐싱함으로써 서버 부하와 응답 시간을 크게 줄일 수 있는 방법입니다.
  • 3.콘텐츠 수정/삭제 시 페이지 재생성을 위해 ISR(주기적 revalidation)을 적용했습니다. 콘텐츠 제작 권한이 어드민에게만 있어 즉각적인 반영이 필요 없다고 판단하여 ODR은 제외했습니다.
  • 4.두 가지 캐싱을 모두 적용한 결과, TTFB가 1200ms에서 20ms로 개선되어 약 60배의 성능 향상을 달성했습니다. 속도는 유저 경험 개선은 물론이고 SEO 순위 지표인 코어 웹바이탈의 LCP는 페이지 응답 속도에 크게 영향받으므로, 캐싱이 가장 효과적인 개선 방법입니다.