Node.js fetch의 숨겨진 5분 타임아웃 함정

Node.js 18+의 내장 fetch는 기본적으로 5분(300초) 헤더 타임아웃이 설정되어 있습니다. 이는 문서화되지 않은 undici의 기본값이며, 장시간 실행되는 API 호출 시 예상치 못한 타임아웃을 발생시킵니다.

문제 상황

딥 리서치 API를 개발하던 중, 정확히 5분 후에 다음과 같은 에러가 발생했습니다:

[Error [HeadersTimeoutError]: Headers Timeout Error] {
  code: 'UND_ERR_HEADERS_TIMEOUT'
}

처음에는 다양한 원인을 의심했습니다:

  • Cloudflare Worker의 CPU 시간 제한 (30초)
  • ALB 타임아웃 설정
  • NAT Gateway 제한
  • 각종 네트워크 장비 설정

하지만 모든 설정을 확인해봐도 5분 제한은 없었습니다.

진짜 원인 발견

문제는 Node.js 18+에서 도입된 내장 fetch가 내부적으로 사용하는 undici의 기본 설정이었습니다.

// 이 코드가 문제였습니다
const response = await fetch('https://deep-research.creatrip.team/deep-research', {
  method: 'POST',
  // ... 다른 옵션들
});

undici는 기본적으로 headersTimeout: 300000 (5분)이 설정되어 있습니다. 이는 서버가 응답 헤더를 5분 내에 보내지 않으면 연결을 끊는다는 의미입니다.

해결 방법들

  1. AbortController를 사용한 커스텀 타임아웃
const fetchWithTimeout = (url, init = {}, timeoutMs = 600000) => {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  return fetch(url, {
    ...init,
    signal: init.signal || controller.signal
  }).finally(() => clearTimeout(timeoutId));
};

// 사용법
const response = await fetchWithTimeout(
  'https://api.example.com/long-running-task',
  { method: 'POST' },
  600000 // 10분
);
  1. node-fetch 사용
import fetch from 'node-fetch';

const response = await fetch('https://api.example.com/long-running-task', {
  method: 'POST',
  timeout: 600000 // 10분
});
  1. undici 직접 사용
import { fetch } from 'undici';
const response = await fetch('https://api.example.com/long-running-task', {
  method: 'POST',
  headersTimeout: 600000, // 10분
  bodyTimeout: 600000     // 10분
})

왜 이런 일이?

Node.js 팀의 공식 답변:

"This is not a bug, it's working as intended."

undici의 기본 타임아웃 설정은 대부분의 웹 요청에는 합리적이지만, 장시간 실행되는 작업(AI 처리, 대용량 데이터 분석 등)에는 문제가 됩니다.

교훈

  1. 새로운 기술 도입 시 기본값 확인: Node.js 18+로 업그레이드하면서 fetch가 내장되었지만, 기존 node-fetch와는 다른 기본을 가집니다.
  2. 에러 메시지 주의 깊게 읽기: UND_ERR_HEADERS_TIMEOUT는 undici 특유의 에러 코드입니다.
  3. 문서화의 중요성: 이런 기본값들이 명확히 문서화되어 있지 않아 디버깅에 시간이 오래 걸렸습니다.

마무리

5분이라는 숫자가 너무 정확해서 어딘가 설정된 값이라고 확신했는데, 설마 fetch 자체에 하드코딩되어 있을 줄은...
다른 개발자분들도 비슷한 상황을 겪지 않기를 바라며 이 글을 공유합니다. 혹시 장시간 실행되는 API를 다루신다면 타임아웃 설정을 꼭 확인해보세요!

참고 링크: