Promise.all의 한계와 동시성 제어

문제 상황

외부 API로부터 1000개 이상의 데이터를 가져와야 하는 작업이 있었다. 각 항목마다 개별 요청이 필요했고, 처음엔 단순하게 Promise.all을 사용했다.

const results = await Promise.all(
  items.map(item => fetchData(item.id))
);

문제는 동시에 수백 개의 요청이 발생하면서 서버가 429(Too Many Requests)를 반환하기 시작했다는 것이다.

해결 방법

동시 실행되는 Promise 개수를 제한하는 함수를 작성했다.

async function promiseAllWithLimit(items, limit, fn) {
  const results = [];
  const executing = [];
  
  for (const [index, item] of items.entries()) {
    const promise = Promise.resolve().then(() => fn(item, index));
    results.push(promise);
    
    if (limit <= items.length) {
      const e = promise.then(() => 
        executing.splice(executing.indexOf(e), 1)
      );
      executing.push(e);
      
      if (executing.length >= limit) {
        await Promise.race(executing);
      }
    }
  }
  
  return Promise.all(results);
}

사용은 다음과 같이 했다.

const results = await promiseAllWithLimit(
  items,
  5, // 동시 5개까지만
  item => fetchData(item.id)
);

결과

동시 요청 수를 5개로 제한하니 서버 에러 없이 안정적으로 처리됐다. 전체 소요 시간은 늘어났지만, 실패 없이 완료되는 것이 더 중요했다.

p-limit 같은 라이브러리도 있지만, 의존성을 최소화하고 싶어 직접 구현했다. 코드는 공통 유틸로 분리해서 여러 곳에서 재사용 중이다.