130만 MAU 서비스 Next.js Vercel Bandwidth 1,000% 개선하여 비용 절감하기
🚀

130만 MAU 서비스 Next.js Vercel Bandwidth 1,000% 개선하여 비용 절감하기

Description
Published
Published July 23, 2023

Vercel 짱짱맨

notion image
 
ZEP의 클라이언트는 웹계의 애플이라 불리는 Vercel로 서비스 되고 있습니다. Vercel은 그 어떤 환경보다도 가장 신경 쓸 필요없이 안정적으로 Next.js 앱을 호스팅 할 수 있게 도와줍니다. 또한 Next.js에서 제공하는 좋은 기술도 모두 누릴 수 있고 무엇보다도 포기하기 힘든 기능인 Preview Deployment(매 커밋마다 결과물을 서빙 해줌)는 팀의 생산성에도 도움을 많이 줍니다. Vercel은 정말 편리하고 개발자 친화적인 기능들로 현재 우리에게 없어서는 안 될 도구가 되었습니다. 약간의 설정만 하면 코드가 푸시 될 때 마다 큰 신경 쓸 필요없이 자동으로 빌드 및 배포가 되고 그 뒤로는 크게 신경 쓸 필요없이 믿고(몇번의 장애가..) 맡길 수 있습니다. 또한 여러 캐싱, Serverless Function, Edge Middleware, Function 이나 이미지 최적화 최근에는 KV 데이터베이스 까지 제공하여 정말 종합 세상이 많이 좋아졌구나를 느끼게 해주는 최고의 서비스 입니다.

Seamless Deployment!

Vercel은 간단하고 직관적인 배포 프로세스를 지원하여 대부분의 어려운 작업을 자동화합니다. 단일 커맨드나 Git 리포지토리로 푸시하기만 하면 애플리케이션이 즉시 배포되고 활성화됩니다.

Scalability!

Vercel은 어플리케이션의 요구 사항에 따라 확장되도록 설계되었으며 매우 멋지게 동작합니다. 서비스가 130만 MAU로 성장하면서 추가 서버나 로드 밸런서 프로비저닝에 대해 걱정할 필요가 없었습니다. Vercel의 서버리스 아키텍처는 자동 확장을 보장하여 배포만 되면 그 이후로는 큰 걱정 없이 서비스 할 수 있습니다.

Development and Preview Environments and DX!

Vercel의 Preview Deployments를 사용하면 쉽게 개발 환경을 구축하고, 매 커밋별 별도의 버전을 배포하여 Production 전에 모든 코드 변경 사항을 미리 테스트 할 수 있습니다. 수고스러움 없이 빠른 사이클로 거의 실시간에 가깝게 변경사항을 확인하고 버그와 문제를 조기에 파악할 수 있습니다.
또한 Vercel은 좋은 개발자 경험을 제공하는 데 힘쓰고 있습니다. 배포 관리 및 분석 보기를 위한 직관적인 대시보드를 제공합니다. 그리고 다양한 플랫폼과의 Integration을 제공합니다👍

혜자스럽지만 사악한 가격 정책

notion image
Vercel은 웬만한 앱을 무료 hobby plan으로 서비스할 수 있을 정도로 혜자 같은 서비스를 제공하여 많은 분들이 사이드 프로젝트나 MVP를 배포할 때 사용합니다. Pro플랜 역시 유저당 20달러라는 저렴한 가격으로 이용할 수 있습니다.
하지만 일정 수준을 벗어나면 악마로 변하기 시작합니다. 특히, Pro플랜 기준 1,000GB까지는 별도의 과금이 없지만, 1,000GB를 초과하면 얄짤없이 100GB당 40달러가 과금되는 Bandwidth 과금 악마는 서비스가 성장하면서 가장 먼저 부딪히는 문제입니다.
Bandwidth는 Vercel 서버를 통해 들어가고 나오는 모든 네트워크 딜리버리로 계산됩니다. 이 말은 즉, getServersideProps에서 패칭해와 응답하는 데이터도, 캐싱된 정적 파일도, 번들도, Next.js 서버의 API도 모두 여기에 포함됩니다. 이게 조금 너무한게 만약 외부 CDN 없이 Vercel을 통해 서빙 되고 있는 정적파일들이 많고 유저가 많다면 하루에도 수백GB를 채우는건 일도 아닐겁니다. 간단하게 예를 들면 Next.js의 Image 컴포넌트는 직접 구현하기 귀찮은 많은 최적화를 해줍니다. 근데 사용할 수가 없습니다. 그 모든 게 Bandwidth에 Image Optimization에서 계산이 되기 때문입니다. 얻는 것에 비해 비용적으로 잃는 게 많아 차라리 외부 스토리지와 CDN을 사용하는 게 좀 더 합리적인 선택이 됩니다.
또한, 엔터프라이즈가 아니라면 앱별이 아닌 계정(팀)에 속한 모든 프로젝트에 적용되는 제한사항이 있습니다. 같은 팀이 여러 가지 프로젝트를 운영하고 있다면, A 앱의 serverless function에서 부하가 발생하여 쓰로틀링이 발생하면 같은 계정 아래 있는 B 앱의 serverless도 영향을 받게 됩니다. (이는 WAF 테스트 중 serverless functions에 부하가 걸려 모든 앱에 간헐적인 장애를 일으키기도 하였습니다.)
기존에는 웹 서버에서 직접 운영하고 서빙하다가 Vercel로 마이그레이션하였더니 여러 사용자들이 폭발적으로 증가해 버렸고, 그와 동시에 비용도 폭발적으로 증가했습니다. 이에 대한 비용 절감을 위한 노력을 시작합니다.

어디서 많은 Bandwidth가 쓰이는지 분석하기

MAU가 수십만, 백만을 넘어가면서 작은 요청도 나비 효과가 되어 부담으로 돌아왔습니다. 따라서 유저 경험을 해치지 않는 선에서 최적화할 수 있는 부분들을 찾기 시작했습니다. 구석구석 최적화할 요소들은 많겠지만, 가장 빠르면서도 효과적인 개선 항목을 도출했습니다.
  • 게임 엔진, Web3 관련 거대한 패키지들이 포함되어 있는 번들 및 에셋
  • Next.js 서버를 Proxy하는 API 요청
  • 서버사이드에서의 다국어 데이터 주입
ZEP은 게임적인 요소가 굉장히 많습니다. 실제로 게임엔진이 올라가 있기도 하구요. 또한 Web3 관련된 패키지들도 어마무시한 번들 사이즈에 한몫을 했습니다.
유저에게 조금 더 실시간성을 제공하기 위해, 매우 자주 유저 정보를 패칭하고 있었습니다. 응답받은 유저 정보를 기반으로 데이터를 암호화된 쿠키에 주입하기 위해 Next.js 서버에 다시 요청을 보내는 로직이 있었습니다. 즉, 특정 컴포넌트가 마운트 되거나 focus, visibilitychange가 발생할 때마다 API 서버에 최신 유저 정보를 패칭하는데, 이 동작이 발생할 때마다 Next.js API 서버에 다시 요청을 보내고 있었습니다. 실제 이 요청과 응답은 몇 바이트 되지 않지만 이런 요청이 너무 빈번히 일어나 요청수 자체가 너무 많아졌습니다.
next-i18next 를 사용하여 서버사이드에서 동적으로 다국어 파일을 주입해주는것도 마찬가지였습니다. 수~수십 KB 지만 페이지 요청시마다 포함되었습니다.
 

개선하기

사실 Bandwidth를 개선하는 방법은 간단합니다. Next.js 서버에 요청, 응답을 줄여 들어가고 나오는 데이터의 양을 줄이면 됩니다.
  • 앞단에서 캐싱하기
  • 클라이언트로 전가하기
일단 가장 중요한것은 캐싱이 가능한 요청은 vercel 앞단에서 캐싱하여 vercel 까지 흘러들어오지 못하게 하여야 합니다. 캐싱이 가능하다는것은 똑같은 요청에 대해 같은 응답을 주는것을 말합니다. 우리는 라우팅 및 CDN의 목적으로 CloudFront를 앞단에 사용하고 있습니다. (Vercel은 사실 앞단에 다른 CDN을 두는걸 권장하지 않습니다. 왤까..??) CloudFront에서 캐싱이 가능한 모든 패턴에 대해 캐싱 처리를 하였습니다. Next.js 앱의 경우 /_next/static 하위의 chunk 들과 css 들과 static 다국어 파일들을 캐싱 처리 하였습니다.
또한 불필요한 요청을 줄였습니다. 위에서 말한 예시처럼 유저정보를 패칭해 올때마다 파생된 요청을 보내는것이 아니라 유저정보를 패칭했을 때 이전 유저정보와 비교해 변경사항이 발생했을 때만 파생요청을 보내게 수정하였습니다.
ZEP은 여러가지의 클라이언트 앱들이 있는데 이번에 개선한 play-app(ZEP의 메인이 되는 메타버스 공간)은 더더욱 서버사이드에서 다국어 파일을 함께 주입해 줄 필요가 없었습니다. 어짜피 문서의 성격도 가지고 있지 않고 다른 번들과 에셋들을 로드하고 부트스트랩 하는데 시간이 걸리기 때문에 그사이에 클라이언트 사이드에서 다국어 파일을 로드 해오면 되기 때문입니다. 따라서 play-app의 모든 다국어 파일의 로드는 클라이언트 사이드로 위임 하였습니다.
 

결과

notion image
 
결과적으로 Bandwidth를 10분의 1 정도로 줄이는 약 1,000%의 개선이 이루어졌습니다. 사실 이것은 위에 예시로든 작업 중 다국어 파일 로드를 클라이언트 사이드로 온전히 전가한 작업 하나로 개선된 내용입니다. 그 이전에 다른 작업들로 200%, 30% 정도의 개선이 있었습니다. 즉 처음보다 약 1/20의 Bandwidth만 차지하고 있다는게 되겠네요.

너는 다 생각이 있었구나

점점 Edge로, 클라이언트 입장에서는 서버 사이드로 많은 역할과 책임을 넘기려고 하고 있습니다. 서버 컴포넌트, 이미지 최적화, 엣지 미들웨어 등에서 제품을 더 편리하고 빠르게 개선할 수 있는 기능과 기술이 있지만, 비용 때문에 제대로 활용하지 못하는 아이러니한 상황에서의 타협점이나 합리화 정도 일까요? 가끔은 “이렇게 계속 쥐어 짜는 게 진짜 그렇게 큰 의미가 있을까? 내가 여러 바이탈들을 0.1초 줄이고 많은 유저들의 시간을 수백 수천 시간을 아꼈다고 자랑스러워 하는 게 정말 유저들에게도 정말 큰 의미가 있는 걸까? 결국엔 PaaS, SaaS만 배부른 거 아닌가?” 하는 바보 같고 이기적인 생각도 합니다.
직접 운영하면 손이 많이 가고, 더 저렴한 대체제를 사용하면 지원하지 않는 기능들이 아쉽고, 엔터프라이즈로 마음놓고 사용하면 비용이 부담됩니다. 😂(웹 클라이언트에서는 단순히 스태틱 서빙만 생각하는 문화도...)
여러 환경에서 가치와 근거를 찾고 적절한 트레이드오프를 찾는 것은 쉽지 않지만, 우리가 계속 나아가야 할 길인 것 같습니다. 결론은 Vercel은 여전히 짱짱맨이고 당분간은 더 사용할것 같습니다.