Node.js 22에서 달라진 fetch API 기본 동작

문제 상황

사내 API 게이트웨이를 Node.js 20에서 22로 업그레이드했다. 배포 후 외부 API 호출이 간헐적으로 hang 상태로 빠지는 이슈가 발생했다.

기존 코드는 단순했다.

const response = await fetch('https://external-api.com/data');
const data = await response.json();

원인 분석

Node.js 22부터 undici 기반 fetch 구현이 기본으로 들어가면서, 타임아웃 처리 방식이 달라졌다. 이전 버전에서는 암묵적으로 소켓 타임아웃이 있었지만, 22부터는 명시적으로 설정하지 않으면 무한 대기할 수 있다.

해결 방법

AbortController를 사용해 타임아웃을 명시적으로 구현했다.

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://external-api.com/data', {
    signal: controller.signal
  });
  const data = await response.json();
  return data;
} catch (error) {
  if (error.name === 'AbortError') {
    throw new Error('Request timeout');
  }
  throw error;
} finally {
  clearTimeout(timeoutId);
}

유틸 함수로 분리해서 재사용했다.

export async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    return await fetch(url, {
      ...options,
      signal: controller.signal
    });
  } finally {
    clearTimeout(timeoutId);
  }
}

교훈

Node.js 메이저 버전 업그레이드 시 변경사항을 꼼꼼히 체크해야 한다. 특히 네트워크 레이어는 프로덕션 환경에서 예상치 못한 동작을 보일 수 있으므로, 스테이징에서 충분한 부하 테스트가 필요하다.