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