여러 개의 외부 API(예: 날씨 정보, 결제 상태, 회원 정보 등)를 한 번에 호출해야 할 때, 페이지 로딩 속도가 3초 이상 길어져 답답함을 느껴본 적이 있을까?
다만 대부분의 개발자들은 foreach 문 내부에서 curl_exec()를 순차적으로 실행하면서 네트워크 latency가 누적되어 병목이 발생한다는 사실을 모른 채 단순 타임아웃만 조절하곤 한다.
이번에는 PHP의 cURL Multi(curl_multi_exec) 함수가 정확히 뭔지, 왜 필요한지, 그리고 비동기 병렬 요청을 통해 API 응답 속도를 획기적으로 줄이는 방법을 완벽하게 정리해서 소개하겠다.

 

1. cURL Multi란 무엇이고 왜 필요한가?

기존의 일반적인 cURL(curl_exec) 방식은 하나의 API 요청을 보내고 응답이 돌아올 때까지 동기(Blocking) 방식으로 대기한다. 만약 API 3개를 호출하는데 각각 1초가 걸린다면 전체 소요 시간은 최소 3초가 된다.
반면 cURL Multi 기능은 여러 개의 cURL 핸들을 하나로 묶어 비동기(Non-blocking) 방식으로 동시에(Parallel) 요청을 전송한다. 따라서 가장 오래 걸리는 API 요청의 시간(예: 1초)만큼만 소비되며 전체 응답 속도가 획기적으로 개선된다.

구분순차 요청 (curl_exec)병렬 요청 (curl_multi_exec)
동작 방식동기 (Blocking - 하나씩 순서대로)비동기 (Non-blocking - 동시에 호출)
소요 시간각 요청 시간의 합 (A + B + C)가장 긴 요청 시간 (Max(A, B, C))
적합한 상황이전 요청의 결과가 다음 요청에 필요한 경우독립적인 여러 API를 동시 조회할 때

 

2. cURL Multi 처리 단계 및 핵심 라이프사이클

cURL Multi를 안전하게 구현하려면 다음과 같은 5단계 라이프사이클을 이해해야 한다.

  • 1단계: curl_multi_init()으로 멀티 핸들 생성
  • 2단계: 개별 curl_init() 핸들을 생성하고 옵션 설정 후 curl_multi_add_handle()로 추가
  • 3단계: curl_multi_exec() 및 curl_multi_select()를 이용해 비동기 루프 실행
  • 4단계: curl_multi_getcontent()로 각 핸들의 응답 결과 수집
  • 5단계: curl_multi_remove_handle()로 핸들 제거 및 curl_multi_close()로 메모리 해제

 

3. 실전 예제: 병렬 API 요청 서포트 함수 구현

실무에서 유용하게 쓸 수 있도록 여러 URL 배열을 받아 병렬로 데이터를 가져오는 커스텀 함수 예제다.

<?php
function fetch_urls_in_parallel(array $urls, int $timeout = 10): array {
    $mh = curl_multi_init();
    $curl_handles = [];
    $results = [];

    // 1. 개별 cURL 핸들 생성 및 멀티 핸들에 등록
    foreach ($urls as $key => $url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

        curl_multi_add_handle($mh, $ch);
        $curl_handles[$key] = $ch;
    }

    // 2. 병렬 요청 실행
    $active = null;
    do {
        $status = curl_multi_exec($mh, $active);
        if ($active > 0) {
            // CPU 점유율 과다 사용을 방지하기 위해 I/O 소켓 대기
            curl_multi_select($mh);
        }
    } while ($active && $status === CURLM_OK);

    // 3. 응답 결과 수집 및 핸들 정리
    foreach ($curl_handles as $key => $ch) {
        $results[$key] = [
            'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
            'content' => curl_multi_getcontent($ch),
            'error' => curl_error($ch)
        ];
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);
    }

    curl_multi_close($mh);
    return $results;
}

// 실무 적용 테스트
$request_urls = [
    'user' => 'https://jsonplaceholder.typicode.com/users/1',
    'posts' => 'https://jsonplaceholder.typicode.com/posts?userId=1',
    'todos' => 'https://jsonplaceholder.typicode.com/todos?userId=1'
];

$start_time = microtime(true);
$response_data = fetch_urls_in_parallel($request_urls);
$execution_time = microtime(true) - $start_time;

echo "전체 소요 시간: " . round($execution_time, 2) . "초
";
?>

 

4. 흔한 실수와 주의사항

cURL Multi 작성 시 개발자들이 가장 많이 범하는 실수는 CPU 자원 고갈(Busy Waiting) 현상이다.

 

✗ 잘못된 코드 (CPU 100% 점유 위험)

아래와 같이 curl_multi_select() 대기 함수 없이 do-while 루프만 돌리면 네트워크 응답을 기다리는 동안 CPU가 쉬지 않고 빈 루프를 실행하여 서버가 먹통이 될 수 있다.

// ✗ CPU 자원을 100% 사용하게 되는 잘못된 루프 처리
do {
    $status = curl_multi_exec($mh, $active);
    // 대기(sleep/select) 없이 무한 반복 실행됨!
} while ($active > 0);

 

✓ 올바른 코드 (curl_multi_select 활용)

curl_multi_select()를 호출하여 소켓에 이벤트가 발생할 때까지 프로세스를 대기 상태로 전환해주는 것이 올바르다.

// ✓ CPU 점유율을 안정적으로 유지하는 적절한 대기 처리
do {
    $status = curl_multi_exec($mh, $active);
    if ($active > 0) {
        // 네트워크 I/O 이벤트 발생 시까지 대기 (기본값 1초)
        if (curl_multi_select($mh) === -1) {
            usleep(100); // 시스템 호출 실패 시 미세 지연 처리
        }
    }
} while ($active && $status === CURLM_OK);

 

결과 / 출력값

순차 요청 시 약 1.5초(0.5초 x 3개) 걸리던 작업이 병렬 요청 적용 후 0.5초 안팎으로 대폭 감소한 결과를 확인할 수 있다.

전체 소요 시간: 0.52초

 

5. 정리 및 결론

PHP cURL Multi 비동기 병렬 요청은 여러 외부 API 통신이 많은 현대 웹 애플리케이션에서 페이지 응답 속도를 극대화할 수 있는 강력한 성능 최적화 기법이다. 순차적 처리로 발생하는 네트워크 딜레이 스노우볼을 방지하는 작은 코드 최적화 습관이 모여서 전체 시스템의 쾌적한 UX와 높은 응답성이라는 큰 효과를 만든다는 점을 잊지 말자. 이 글의 실전 서포트 함수 구현 부분을 참고해 기존 순차 API 호출 로직에 적용해 보면, 몰라보게 빨라진 페이지 로딩 속도라는 최종 결과를 얻을 수 있을 것이다.