고양이hyebin
Vercel 서버리스 함수가 콜드 스타트가 되다?
August 1, 2025

문제 상황: 3-4초 걸리는 느린 API

image
image

사내 사이드 프로젝트로 직장인 성격 유형 테스트를 만들었다.

클로드 바이브 코딩으로 개발하니 원하는 결과를 빠르게 만들 수 있었다.

간단한 사이드 프로젝트라 vercel로 배포후 supabase로 데이터 연동까지 완료했는데, 이상하게 next api가 너무 느렸다.

다음 버튼 누르면 처음에는 3-4초 걸리다 그 후에는 1-2초까지 걸렸다.

단순히 Supabase가 느린 걸까? 싶었는데 뭔가 이상했다. 일반적인 방법대로 했는데 왜 이렇게 느릴까?

// 클라이언트에서 API 호출
const response = await fetch("/api/save-progress", {
  method: "POST",
  body: JSON.stringify({
    user_id: userId,
    current_question: questionNumber,
    answer: answer,
   // ...
  }),
});
 
// /api/save-progress
export default async function handler(req, res) {
  const supabase = createClient(url, key);
  
  // 완료시간 계산하고
  const completionTime = calculateTime();
  
  // 첫 답변일 때만 위치정보 API도 호출하고
  if (req.body.current_question === 0) {
    deviceInfo = getDeviceInfo();
    locationInfo = await getLocationInfo(); 
  }
  
  // DB에 저장
  const result = await supabase.from('test_progress').insert(data);
  res.json(result);
}`

원인 : 콜드 스타트였다.

image
image

콜드 스타트는 Vercel 공식 문서에도 나와있는 현상이다.

💡 When a function starts for the first time, it's a 'cold start'. Subsequent requests to that function are then considered warm.

콜드 스타트는 쉽게 말하면 서버리스 함수가 잠들어 있다가 깨어나는 과정이다.

서버리스 함수는 평소에는 꺼져있고, 누군가 요청을 보내면 그때 켜진다. 마치 컴퓨터를 껐다가 다시 부팅하는 것처럼 시간이 걸린다.

콜드 스타트에서 일어나는 일들

  • 런타임 환경 초기화
  • 코드 로딩 및 의존성 로드
  • 함수 실행 준비

얼마나 걸릴까?

실제로 GitHub 이슈를 보면 "cold start times of 2 - 7 seconds" 라는 사용자 보고가 있다.

문제는 예측 불가능성이다. 첫 번째 사용자는 7초 기다리고, 바로 다음 사용자는 100ms만 기다릴 수도 있다.

왜 콜드 스타트가 발생했을까?

이는 Vercel의 배포 방식과 관련이 있다. Vercel에 Next.js 앱을 배포하면 API Routes가 자동으로 서버리스 함수로 변환된다.

일반적인 서버와 달리 서버리스 함수는:

  • 요청이 있을 때만 실행된다
  • 일정 시간 사용되지 않으면 자동으로 종료된다
  • 다시 요청이 오면 처음부터 초기화 과정을 거친다

바로 이 초기화 과정이 콜드 스타트이며, 내가 경험한 첫 번째 요청의 3-4초 지연의 원인이었다.

해결: 서버리스 함수를 거치지 말자

서버리스 함수를 거치지 않고 클라이언트에서 바로 Supabase를 호출하는 방향으로 수정 했다.

클라이언트에서 Supabase 직접 호출할 경우 브라우저에서 실행되기 때문에 Vercel의 서버리스 함수를 거치지 않는다.

기존 구조

클라이언트 (브라우저) → API Route (서버리스 함수) → Supabase

변경된 구조

클라이언트 (브라우저) → Supabase 직접 호출
const saveAnswerDirectly = async (userId, questionNumber, answer) => {
  const { saveTestAnswer } = await import("@/lib/supabase");
  
  const answerData = {
    user_id: userId,
    question_number: questionNumber, 
    answer: answer,
    answer_value: (answer === "a" ? 1 : 2),
    weighted_types: calculateWeights(answer),
  };
  
  const result = await saveTestAnswer(answerData);
  return result;
};`

결과: 엄청 빨라졌다

  • 첫 답변: 2~9배 빨라짐
  • 그 다음 답변들: 1.4~3배 빨라짐
  • 평균적으로 85% 빨라짐

체감상 완전 다르다. ㅜ 이제 버튼 누르면 바로바로 반응한다.

교훈

image
image

“정말 필요한 건가?"를 항상 생각해야겠다.

API Route 써야 하는 경우는

  • 보안상 중요한 로직
  • 복잡한 비즈니스 규칙
  • 여러 서비스 연동
  • 민감한 데이터 처리

하지만 단순한 CRUD는 그냥 직접 하는 게 낫다. 특히 사용자가 바로바로 반응을 봐야 하는 기능은 더더욱…

성능 최적화라고 해서 복잡한 기술을 쓸 필요 없다. 프로젝트 규모와 요구사항에 맞게 설계하는 것이 중요하다.

간단한 사이드 프로젝트에서는 오히려 단순함이 최고의 성능 최적화일 수 있다!