블로그 만들기의 첫 걸음
1. 일단 만들자#
처음은 MVP 차원에서 성능은 고려하지 않고 UI/기능 구현에 집중하였습니다.
냅다 운영용으로 빌드를 해보았을 때



이렇게 첫 빌드 후 페이지에 접근을 해보니 역시 느리네요..! 피드, 커리어 페이지를 각각 들어가보니 1.2초 정도로 데이터가 전무한 페이지임에도 TTFB에 꽤나 큰 시간을 잡아 먹고 있었습니다.
TTFB : Time To First Byte의 약자로 네트워크 요청의 응답의 첫 번째 바이트가 도착하기까지 걸린 시간이다.
프론트에서는 HTML을 받고 그 문서 안에 담긴 리소스들을 요청하기에 보통 HTML 문서의 첫 바이트를 받기까지 걸린 시간을 뜻한다.
2. 반드시 서버측 렌더링을 이용한다#
문제의 원인을 짚기 전에,
내가 하고 있는 블로그 개발이라는 작업이 무엇을 위한 것인가를 고민해보면
블로그 콘텐츠는 사람들에게 공유되어야 하므로 무료 마케팅이라고 볼 수 있는 검색엔진에 노출되는 것이 가장 중요한 도메인 분야라고 생각합니다.
버전1 기준으로 현재 이 프로젝트에 존재하는
- 피드 페이지
- 커리어 페이지
- 포스트 상세 페이지
모두 다 SEO가 중요한 페이지이고,
따라서 검색엔진의 크롤러들이 내 콘텐츠를 더 잘 읽어갈 수 있도록 서버측에서 페이지의 정보를 담아둬야 하기 때문에 CSR은 배제하였습니다.
3. 문제 규정과 해결책 고민#
문제의 원인으로는 페이지가 동적 빌드되어 SSR로서 매 요청마다 서버에서 실시간으로 페이지를 그려야하기 때문이라고 판단했습니다.
따라서 정적 빌드와 Next가 제공하는 캐시 기법에 대해 고민했는데 이를 중심으로 해결 수단을 내보자면
1) 데이터 캐싱 🤔#
데이터 캐싱은 한번 요청해서 받아온 응답 데이터에 대해 Next서버가 특정 시간동안 캐싱을 해두는 기법인데요. 즉 아래처럼 요청마다 DB에 액세스하게 되는 문제를 방지합니다.

RTT를 줄일 수 있기에 너무나 확실한 개선 수단이라 후보 1로 두었습니다.
RTT : Round Trip Time의 약자로 요청이 시작점에서 목적지로 갔다가 다시 시작점으로 돌아오는 데 걸리는 시간을 뜻합니다.
데이터 캐싱으로 API 요청을 스킵할 수 있어 RTT를 줄일 수 있습니다.
2) 풀라우트 캐싱 ✅#
1)에서 말한 매번 데이터를 요청하지 않고 캐싱해두는 것도 좋지만, 유저입장에서 더 가까운 단계에서 캐싱을 해두면 어떨까요?
일반적으로 유저가 SSR 페이지를 받기 위해서
- 유저가 프론트 서버에 요청
- 프론트는 요청을 받아 페이지를 그리기 시작함
- 페이지를 그리기 위해 데이터가 필요하다면 API 요청/응답까지 진행
하는 절차를 거칩니다.
위에서 TTFB가 1.2초가 걸렸던 이유는 이 모든 과정이 일어났기 때문입니다.
별 데이터가 없었음에도 1.2초는 꽤나 긴 시간이며
만약 제 블로그를 방문하는 유저도 많고, 이 페이지를 그리기 위해서 필요한 데이터가 많았다면 모든 유저의 각 요청마다 위 1~3의 플로우를 거친 후 응답하기에 프론트 서버도 바쁘고 DB도 많은 요청을 받게 되는 문제점이 있을텐데요.
데이터 캐싱으로 3은 생략하여 DB부하를 줄일 수 있겠지만 결국 서버까지 들어와서 캐싱해둔 데이터로 페이지를 그리는 과정이 필요하므로 서버에 대한 부하 위험은 여전히 존재합니다.
따라서 이보다 앞선 단계에서 페이지 자체를 캐싱해두면 서버가 페이지를 그리는 과정 또한 생략될 수 있겠죠.
즉, 위 1~3의 플로우를 거치지 않고 1에서 바로 응답을 할 수 있게 됩니다.
풀라우트 캐싱은 이러한 바램의 해결책으로서 미리 빌드단계에서 페이지를 정적빌드한 후 캐시해두었다가 들어온 요청에 대해 이 페이지를 유저에게 곧바로 서빙합니다.

현재는 이 풀라우트 캐싱이 적용되어 있지 않아 Cache MISS가 뜬 것을 볼 수 있습니다.
4. 풀라우트 캐싱의 재생성 주기에 관하여 - ISR#
제가 작성한 콘텐츠가 최초 공개 상태로 계속 유지되지 않을 수 있습니다.
수정으로 인해 내용이 바뀔 수 있고, 삭제로 인해 콘텐츠 자체가 삭제될 수도 있죠.
그런 변화에도 불구하고 풀라우트 캐싱된 페이지는 이러한 사항이 자동으로 반영되지 않고 변화 전 상태로 유저에게 서빙되는 문제가 있습니다.
그러기에 페이지를 주기적으로 재생성할 수 있도록 revalidation을 사용합니다.
다만 말그대로 주기적으로 재생성하는 것이기에 수정/삭제가 이루어졌더라도 그 주기가 지나지 않으면 기존에 캐싱된 페이지를 유저에게 서빙합니다.
만약 유저가 콘텐츠를 제작가능한 UGC 서비스라면 UX를 해치지 않기 위해서 유저의 수정/삭제 사항에 대해 페이지에 곧바로 반영해야 하므로 즉각 페이지를 재생성하기 위한 ODR까지 도입해야할 수 있으나
콘텐츠의 제작 권한이 오로지 어드민인 저에게만 있는 상황에서 즉각 재생성까지는 필요없다고 생각되네요. 유저 입장에서는 콘텐츠의 변화 사실을 당장은 알 필요가 없기 때문입니다.
즉, 수정/삭제된 페이지가 곧바로 반영될 필요는 없어서 ODR을 적용하지 않고 revalidation을 적용하는 선에서 마무리합니다.
💡 ODR: On-Demand Revalidation으로 정적 페이지를 유저가 콘텐츠를 수정/삭제 하는 등 원하는 시점에 재생성할 수 있는 기법
ODR이 정식 약어은 아니나 개인적으로는 ODR이라고 부르고 있습니다.
5. 정리#
1) 결론#
결국에는 2가지 캐싱을 둘다 적용하기로 했습니다. 풀라우트 캐싱이 적용되었다면 데이터 캐싱이 크게 의미는 없을 수 있습니다만 해서 캐싱을 두 겹으로 두는 것이 나쁠건 없습니다.

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


초기 HTML을 응답받는 시간이 20ms대까지 줄어든 것을 볼 수 있습니다.
최초 빌드 후 접근했을 때 걸렸던 1.2초(1200ms) -> 20ms 어마어마한 차이죠? 60배 정도의 차이가 납니다
2) 가치관#
저는 개발 가치관으로 늘 속도를 꼽는데요.
- 우리 프로젝트에게 느끼게 될 유저 경험도 큰 이유이지만
- 현업의 도메인 분야가 SEO를 중요시하였기 때문입니다.
2와 관련하여서 SEO와 속도와의 상관관계를 보자면 구글은 SERP에 노출하는 순위를 매기는 중요 지표로 코어 웹바이탈을 언급합니다.
그 코어웹바이탈 중 하나인 LCP 지표의 좋고나쁨은 페이지 응답 속도가 거의 대부분 좌우합니다. 페이지의 제공이 빨라야 그 다음 리소스들의 요청/응답도 따라오기 때문이죠.
SEO가 곧 매출인 환경에서 일해왔던지라 속도를 위해 서버에서 네트워크 요청 수를 줄여보거나, 렌더링에 필요한 응답 데이터의 개수를 줄여보거나, HTML 문서 크기를 줄여보는 등 해봤지만 속도에 직빵인 것은 캐시만한게 없었습니다.
이 것이 캐시의 힘이고, 또 캐시를 어디서 하느냐가 중요한 이유입니다.
3) 추가적인 캐싱 방향성#
위에서 언급한 캐싱들로도 충분해보이나 저는 AWS CF같은 CDN에 페이지를 캐싱해두는 것 까지 원하는데요.
풀라우트 캐싱을 걸어도 어쨌든 제 서버까지 요청을 보내야하는 것은 마찬가지인데, CDN을 붙이면 제 서버가 아닌 유저에게 가장 가까운 곳에서 리소스를 서빙받을 수 있습니다.
현업의 도메인에서 메인으로 다루는 질문/답변 페이지와 블로그 페이지는 ‘정보를 전달하는 글’이라는 특징상 노출이 중요하고, 따라서 SEO라는 공통점이 있기에 더 몰입하게 되었네요.
이렇게 풀라우트 캐시 + 페이지 revalidation (ISR)의 힘을 체감해보았고 실제 적용했던 과정은 다음 글에서 적어볼까 합니다. ...