AI Store에서 AI코딩으로 만들어진 앱을 만나보세요!
지금 바로 방문하기

비동기 작업 최적화: Promise.all이 성능을 해치는 경우와 루프 사용 시기

카테고리

프로그래밍/소프트웨어 개발

서브카테고리

웹 개발

대상자

  • *대상자**: 중급 이상의 JavaScript/Node.js 개발자
  • *난이도**: 실무에서 비동기 작업 성능 최적화에 관심 있는 개발자

핵심 요약

  • Promise.all의 과도한 동시성은 데이터베이스 오버로드와 메모리 부족을 유발함 (50,000개 동시 요청 시 500개 동시 연결로 DB 충돌)
  • p-limit를 사용한 제어된 동시성(10개 병렬 처리)으로 메모리 사용량 1.2GB → 200MB로 줄임
  • 메모리 제약 또는 순서 의존성이 있는 작업에는 루프가 더 안정적 (for-await-of, 배치 처리)
  • Promise.allSettled로 실패 처리를 개선한 예제 포함

섹션별 세부 요약

1. Promise.all의 성능 문제

  • 50,000개 레코드 처리 시 500개 동시 연결로 DB 충돌 발생
  • await Promise.all(records.map(processRecord))는 메모리와 동시성 제어 없음
  • 과도한 동시성으로 인한 DB 오버로드와 메모리 부족 문제 강조

2. p-limit로 동시성 제어

  • pLimit(10)으로 최대 10개 병렬 작업 제한
  • await Promise.all(hugeArray.map(item => limit(() => db.insert(item))))로 메모리 사용량 200MB로 감소
  • 동시성 제어가 성능과 안정성 균형을 유지

3. Promise.allSettled로 실패 처리 개선

  • Promise.all은 하나의 실패로 전체 작업 실패
  • Promise.allSettled + results.filter(r => r.status === 'fulfilled')로 실패 별도 처리 가능

4. 스트리밍 처리로 메모리 최적화

  • for await (const record of getRecordsStream())으로 레코드 하나씩 처리
  • 500MB 배열 메모리 대신 스트리밍으로 메모리 사용량 50MB로 감소

5. 시나리오 비교: Promise.all vs 루프

  • 순서 의존성: Promise.all ❌, 루프 ✅
  • 메모리 제약: Promise.all ❌, 루프 ✅
  • 백프레셔: Promise.all ❌, 루프 ✅

6. 하이브리드 접근 방식

  • for (let i = 0; i < items.length; i += batchSize)로 배치 루프
  • await Promise.all(batch.map(process))로 제어된 동시성과 순서 보장
  • 시간/메모리: Promise.all 2분 1.2GB → 배치 루프 6분 50MB

결론

  • "빠르다"는 항상 최선이 아님: 동시성 제어(p-limit)와 메모리 최적화(for-await-of)가 핵심
  • Hybrid Approach 권장: 루프 내부에서 Promise.all 배치 처리로 성능과 안정성 균형 달성
  • 실무 팁: Promise.all은 작은 독립 작업에, 루프는 순서/메모리 제약 작업에 사용하며, 배치 처리로 동시성 제어하세요