비동기 메시징이 필요한 이유
큐가 생산자와 소비자를 분리하는 방식을 알아봅니다.
비동기 메시징이 필요한 이유은(는) CoddyKit의 무료 PHP Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PHP Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
비동기 메시징을 사용하는 이유
동기 요청에서는 PHP 프로세스가 이메일 게이트웨이, 결제 처리 시스템 또는 하위 서비스와 통신하는 동안 차단됩니다. 부하가 걸리면 호출하는 모든 의존성의 지연 시간과 가용성에 요청이 묶입니다. 비동기 메시징은 이 연결을 끊습니다. 생산자는 큐에 메시지를 넣고 즉시 반환하며, 소비자는 나중에 자신의 속도로 메시지를 처리합니다.
이 단원에서는 RabbitMQ나 Kafka를 다루기 전에 고민해야 하는 이유인 결합 해제, 버퍼링, 전달 보장에 따른 절충점을 설명합니다.
시간적·공간적 분리
큐는 두 가지 축에서 생산자와 소비자를 분리합니다:
- 공간적 분리 — 어느 쪽도 다른 쪽의 주소를 알 필요 없이 브로커만 알면 됩니다.
- 시간적 분리 — 생산자가 메시지를 발행할 때 소비자가 중단되어 있어도 메시지는 대기합니다.
아래의 동기 방식은 웹 요청을 메일 서버의 가동 상태와 처리 속도에 직접 묶습니다.
<?php
// Synchronous: the HTTP request blocks on SMTP
function registerUser(string $email): void {
saveUser($email);
// If SMTP is slow or down, the user waits or the request fails
sendWelcomeEmail($email); // 800ms blocking call
echo "Registered\n";
}
function saveUser(string $e): void { /* ... */ }
function sendWelcomeEmail(string $e): void { usleep(1); }
registerUser('dev@example.com');발행하고 계속 진행
비동기 방식은 사용자를 저장하고 UserRegistered 메시지를 발행한 다음 반환합니다. 별도의 작업자가 이메일을 보냅니다. 이제 HTTP 지연 시간은 SMTP 왕복 시간에 좌우되지 않고, 빠른 로컬 브로커 발행에만 좌우됩니다.
<?php
function registerUser(string $email): void {
saveUser($email);
// Publish a lightweight event; worker handles email later
publish('user.registered', json_encode(['email' => $email]));
echo "Registered (email queued)\n";
}
function saveUser(string $e): void { /* ... */ }
function publish(string $routingKey, string $payload): void {
echo "-> queued $routingKey: $payload\n";
}
registerUser('dev@example.com');부하 평준화(버퍼링)
트래픽 급증은 고르지 않지만 처리 용량은 일정합니다. 큐는 버퍼로 작동하여 10,000개의 메시지가 한꺼번에 몰려와도 흡수하고, 작업자 풀이 감당 가능한 속도로 메시지를 처리하도록 합니다. 큐가 없다면 급증한 트래픽이 데이터베이스나 하위 API를 직접 압도할 것입니다.
이를 부하 평준화라고 합니다. 지연 시간(메시지가 잠시 대기할 수 있음)을 안정성(어떤 구성 요소도 무너지지 않음)과 맞바꾸는 것입니다.
전달 보장
모든 메시징 시스템은 전달을 보장하는 방식을 제공합니다. 자신이 사용하는 방식이 무엇인지 알아야 합니다:
- 최대 한 번 전달 — 보내고 잊기 방식이며, 메시지가 손실될 수 있지만 중복되지는 않습니다.
- 최소 한 번 전달 — 확인 응답을 받을 때까지 재전달되므로 중복될 수 있습니다. 일반적인 기본값입니다.
- 정확히 한 번 전달 — 손실도 중복도 없지만 비용이 많이 들고, 애플리케이션 계층에서는 부분적인 착시에 불과한 경우가 많습니다.
대부분의 실제 시스템이 최소 한 번 전달을 제공하므로 소비자는 같은 메시지를 두 번 받아도 처리할 수 있어야 합니다.
멱등 소비자
최소 한 번 전달에서 발생하는 중복을 해결하는 방법은 멱등성입니다. 메시지를 두 번 처리해도 한 번 처리한 것과 같은 결과가 나와야 합니다. 일반적인 방법은 메시지 ID인 중복 제거 키를 고유 인덱스에 저장하는 것입니다.
<?php
function handle(array $msg, PDO $pdo): void {
$pdo->beginTransaction();
try {
// Unique constraint on message_id makes the insert the dedup gate
$stmt = $pdo->prepare(
'INSERT INTO processed_messages (id) VALUES (?)'
);
$stmt->execute([$msg['id']]);
} catch (PDOException $e) {
$pdo->rollBack();
echo "Duplicate {$msg['id']} skipped\n";
return; // already handled
}
chargeCustomer($msg['amount']);
$pdo->commit();
}
function chargeCustomer(int $a): void {}확인 응답과 재전달
소비자는 확인 응답으로 성공을 알립니다. 확인 응답을 보내기 전에 소비자가 중단되면 브로커는 다른 소비자에게 메시지를 재전달합니다. 이것이 최소 한 번 전달을 가능하게 하지만, ack은 부수 효과가 커밋된 후에 와야 하며 절대 그 전에 와서는 안 됩니다.
너무 일찍 확인 응답을 보내면 중단 시 메시지가 손실됩니다. 너무 늦게 확인 응답을 보내고 그 전에 중단되면 중복이 발생하지만, 멱등성 계층이 이를 흡수합니다.
<?php
// Pseudocode of the consumer contract
function consumeLoop($channel): void {
while ($msg = $channel->get()) {
try {
processSideEffect($msg); // commit DB write first
$channel->ack($msg); // only then ack
} catch (\Throwable $e) {
$channel->nack($msg, requeue: true); // let it redeliver
}
}
}
function processSideEffect($m): void {}순서 보장에는 비용이 듭니다
소비자를 확장하면 큐는 전체 순서를 보장하지 않습니다. 같은 큐에서 두 작업자가 메시지를 가져와 동시에 처리하므로 메시지 B가 메시지 A보다 먼저 완료될 수 있습니다.
순서가 중요하다면(예: 계정 잔액의 증감) 키별로 분할하여 관련된 모든 메시지가 순서대로 하나의 소비자에게 전달되도록 해야 합니다. Kafka는 파티션을 통해 이를 기본적으로 지원하고, RabbitMQ에서는 일관된 해시를 사용해 키별 큐로 라우팅합니다.
DLQ(배달 못한 메시지 큐)
형식이 잘못된 페이로드나 삭제된 행을 참조하는 메시지처럼 결코 성공할 수 없는 메시지도 있습니다. 이런 메시지를 영원히 재시도하면 큐가 막히는 처리 불가 메시지가 됩니다. 이때 사용하는 패턴이 DLQ(배달 못한 메시지 큐)입니다. N번 실패한 후에는 메시지를 다시 전달하는 대신 검사를 위해 별도의 큐로 보냅니다.
<?php
function consume(array $msg, $channel): void {
$attempts = ($msg['headers']['x-attempt'] ?? 0) + 1;
try {
process($msg);
$channel->ack($msg);
} catch (\Throwable $e) {
if ($attempts >= 5) {
$channel->deadLetter($msg); // park in DLQ
} else {
$channel->republish($msg, ['x-attempt' => $attempts]);
}
}
}
function process(array $m): void {}NOT: 큐를 사용하지 말아야 할 때
비동기 메시징에는 실제 운영 비용이 따릅니다. 브로커를 운영해야 하고, 제품 팀에 최종적 일관성을 설명해야 하며, 프로세스 경계를 넘나드는 디버깅도 더 어려워집니다.
작업이 느리거나, 급증하거나, 재시도할 수 있거나, 보내고 잊어도 되는 경우 큐를 사용하십시오. 호출자가 결과를 지금 실제로 필요로 한다면 동기 방식으로 유지하십시오(예: 사용자에게 반드시 보여야 하는 권위 있는 가격). 결과가 필요한 호출을 큐로 감싸면 지연 시간과 복잡성만 늘어납니다.
큐와 로그 비교
이러한 패턴을 뒷받침하는 대표적인 브로커 형태는 두 가지이며, 이 과정의 나머지 부분에서는 둘 다 사용합니다:
- 작업 큐(RabbitMQ)는 확인 응답을 받으면 메시지를 삭제합니다. 메시지별 라우팅과 만료 시간을 지원하면서 경쟁하는 소비자 풀에 작업을 분배하는 데 적합합니다.
- 커밋 로그(Kafka)는 보존 기간 동안 메시지를 유지합니다. 각 소비자는 자신의 오프셋을 추적하여 기록을 다시 재생할 수 있고, 여러 독립적인 소비자 그룹이 같은 스트림을 읽을 수 있습니다.
작업 분배에는 큐를, 고처리량 이벤트 스트림과 재생에는 로그를 선택하십시오.
<?php
$useCase = 'replay events for a new analytics service';
$broker = str_contains($useCase, 'replay') || str_contains($useCase, 'stream')
? 'Kafka (commit log)'
: 'RabbitMQ (task queue)';
echo $broker . "\n";빠른 확인
전달 보장에 대해 추론하기
복습
이제 비동기 메시징의 기본 작동 방식을 이해했습니다:
- 큐는 공간적·시간적 분리를 제공하고 부하 평준화를 위한 버퍼로 작동합니다.
- 대부분의 시스템은 최소 한 번 전달을 사용하므로 소비자는 멱등적이어야 합니다.
- 부수 효과가 커밋된 후에 확인 응답을 보내고, 실패하면 메시지가 재전달되도록 하십시오.
- 순서 보장에는 키별 분할이 필요하고, 처리 불가 메시지에는 DLQ가 필요합니다.
- 호출자가 동기적으로 답을 받아야 하는 작업은 큐에 넣지 마십시오.
다음에는 PHP에서 RabbitMQ를 사용해 이 내용을 직접 적용해 보겠습니다.
자주 묻는 질문
“비동기 메시징이 필요한 이유” 강의는 무료인가요?
네 — “비동기 메시징이 필요한 이유” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PHP Academy 강의 전체를 잠금 해제할 수 있습니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“비동기 메시징이 필요한 이유”에서 뭘 배우나요?
큐가 생산자와 소비자를 분리하는 방식을 알아봅니다. 브라우저에서 직접 실행하는 실습 코드로 PHP Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
PHP Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 PHP Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“비동기 메시징이 필요한 이유” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 PHP Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 PHP Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 비동기 메시징이 필요한 이유
- PHP에서 RabbitMQ 다루기
- PHP용 Apache Kafka
- 이벤트 기반 워크플로 구축