배포와 인프라5 분 읽기

제 프록시는 대부분 아무도 열지 않은 페이지를 위해 돌고 있었습니다

블로그 하나가 팀 전체에서 Fluid CPU를 가장 많이 쓰고 있었습니다. 프록시 호출 49번 중 실제 페이지 조회는 17번뿐이었고, 나머지는 링크 프리페치였습니다. 라우팅 단계에서 프리페치를 걸러 함수가 아예 깨어나지 않게 바꾼 기록입니다.

#nextjs#vercel#proxy#prefetch#serverless-cost
프록시 호출 49건 중 페이지 조회 17건, 나머지 32건이 링크 프리페치였음을 보여주는 비교 이미지
프록시 호출 49번 중 사람이 실제로 연 페이지는 17번이었습니다.

블로그가 CPU 1위였습니다

10월 12일 사용량을 보다가 이상한 걸 발견했습니다. 이 개발 블로그가 팀에서 Fluid CPU를 가장 많이 쓰는 프로젝트였습니다. 글 몇 편을 올려둔 정적에 가까운 사이트가 그 자리에 있을 이유가 떠오르지 않았습니다.

원인은 프록시(proxy.ts)였습니다. 프록시는 요청마다 함수로 실행되고, CDN 캐시 HIT인 요청에서도 똑같이 실행됩니다. 페이지가 캐시에서 나가더라도 그 앞의 프록시는 매번 깨어나 CPU를 씁니다.

그래서 프록시 호출을 하나씩 나눠 봤습니다. 49번 중 실제 페이지 조회는 17번이었습니다. 나머지 32번은 Next.js의 <Link>가 보내는 프리페치였고, 요청 헤더가 Sec-Fetch-Dest: empty였습니다. 사람이 클릭하기도 전에, 화면에 링크가 보인다는 이유만으로 프록시가 돌고 있었습니다.

여러분의 미들웨어나 프록시는 호출 중 몇 퍼센트가 실제로 사람이 연 페이지인지 알고 계신가요?

왜 놓쳤나

코드 안에는 이미 페이지 조회를 판별하는 isPageView가 있었습니다. Sec-Fetch-Dest가 document이거나 헤더가 아예 없으면 페이지 조회로 봅니다. 문제는 이 판별이 함수 안에서 일어났다는 점입니다. 프리페치를 알아보고 아무 일도 하지 않더라도, 그걸 알아보려면 함수가 이미 실행된 뒤였습니다. 걸러내는 로직은 맞았지만 위치가 틀렸습니다.

당신이라면? 함수 안의 조기 return을 더 가볍게 다듬으시겠습니까, 아니면 함수가 호출되지 않게 하시겠습니까?

수정: 걸러내기를 라우팅 단계로 옮겼습니다

프록시의 matcher가 isPageView와 같은 조건을 따르도록 바꿨습니다. Sec-Fetch-Dest가 document이거나 없는 요청만 프록시로 들어오고, 프리페치는 라우팅 단계에서 빠집니다. 그래서 프리페치에는 함수가 아예 깨어나지 않습니다. 변경은 proxy.ts 한 파일, 8줄 추가와 1줄 삭제였습니다.

여기서 얻은 교훈은 단순합니다. 함수 안의 조기 return은 비용을 줄여주지 않습니다. 호출 자체가 비용입니다. 걸러낼 수 있는 요청이라면 함수에 도달하기 전에 걸러야 합니다.

자가진단 체크리스트

  • 미들웨어/프록시가 캐시 HIT 요청에서도 실행되는지 확인하셨나요?
  • 호출을 Sec-Fetch-Dest 같은 헤더로 나눠서 실제 페이지 조회 비율을 세어 보셨나요?
  • 함수 안에서 걸러내는 조건 중 matcher(라우팅 단계)로 옮길 수 있는 것이 있나요?

솔직한 부분

근거는 10월 12일 하루, 49번의 호출뿐입니다. 이 비율이 평소에도 같은지는 모르고, 수정 후 CPU 사용량이 실제로 얼마나 줄었는지도 아직 숫자로 확인하지 못했습니다. 프리페치를 matcher에서 빼면 프리페치 응답에는 프록시가 하던 처리가 적용되지 않는데, 그게 어떤 경우에도 문제없는지도 아직 충분히 지켜보지 못했습니다.

관련 글