Laravel에서의 TrueAsync: Swoole 없이 진정한 동시성을 위한 아키텍처
PHP에서 비동기를 위한 전통적인 해결책인 Swoole이나 RoadRunner 같은 도구들은 코드에 깊은 수정을 요구하고 컬러드 함수를 도입해야 합니다. TrueAsync는 근본적으로 다른 접근 방식을 제공합니다—Zend 코어를 수정하여 투명한 비동기를 구현하는 것입니다. Laravel을 이 모델에 적응시키는 방법을 분해해 설명하겠습니다. 상태 유지 서비스의 문제를 극복하고 I/O 중심 작업을 병렬 처리할 수 있게 됩니다.
TrueAsync 기본: 투명한 비동기 작동 방식
TrueAsync는 Zend Engine, I/O 라이브러리, 데이터베이스 작업을 수정하는 대체 PHP 코어입니다. 핵심 장점은 함수 색칠 문제를 없애는 것입니다. Node.js나 Python asyncio와 달리, 표준 PHP 함수(PDO::query(), file_get_contents())는 I/O 대기 중 자동으로 스케줄러에 제어를 양도합니다. 코루틴은 C 함수 수준에서 일시 중지되어 단일 스레드에서 수백 개의 요청을 처리할 수 있습니다.
중요한 점: 이는 멀티스레딩이 아닙니다. 이 모델은 협력적 멀티태스킹에 의존합니다—외부 리소스를 기다리는 동안 컨텍스트 스위칭을 합니다. CPU 중심 작업(무거운 계산)에는 비효율적이지만, 데이터베이스와 API 호출이 포함된 전형적인 웹 시나리오에 이상적입니다. 기본 사용 예시는 다음과 같습니다:
use function Async\spawn;
use function Async\await;
use function Async\delay;
$a = spawn(function() {
delay(100);
return 'result A';
});
$b = spawn(function() {
delay(100);
return 'result B';
});
// Obschee time vypolneniya ~100ms
echo await($a);
echo await($b);
Laravel에서 상태 유지 서비스의 문제
Laravel은 원래 각 요청이 격리된 프로세스에서 처리되는 FPM 모델을 위해 설계되었습니다. 상태 유지 서비스(AuthManager, Session, Router)는 객체 속성에 상태를 저장하며, 요청 처리 후 파괴됩니다. 비동기 모드에서는 프로세스가 무한정 유지되어 상태 누출이 발생합니다:
// Coroutine A: request from user_1
Auth::loginUsingId(1);
delay(200);
// Coroutine B: request from user_2
Auth::loginUsingId(2);
// Return in korutinu A
echo Auth::id(); // Vozvraschaet 2 vmesto 1
Model::$dispatcher, Facade::$resolvedInstance 같은 정적 속성과 IoC 컨테이너의 서비스들이 모든 요청에서 공유됩니다. Octane은 컨테이너를 복제하여 이를 처리하지만, 오버헤드를 발생시키고 정적 속성은 다루지 못합니다.
컨텍스트 격리: 해결 아키텍처
TrueAsync는 current_context()를 제공합니다—요청 수명 주기와 연결된 데이터 저장소입니다. 코루틴이 완료되면 컨텍스트가 자동으로 지워집니다. 아키텍처는 Laravel을 두 영역으로 나눕니다:
- 정적 코어: router, config, IoC 컨테이너. 한 번 로드되어 모든 요청에서 공유됩니다.
- 요청별 서비스: Auth, Session, Cookie. 각 코루틴의 컨텍스트에서 생성됩니다.
핵심 메커니즘은 AsyncApplication::resolve()를 통해 서비스 해석을 가로채는 것입니다:
protected function resolve($abstract, ...)
{
if ($this->asyncMode && $this->isScoped($abstract)) {
$ctx = current_context();
if ($instance = $ctx->find($abstract)) {
return $instance;
}
$instance = $this->buildFresh($abstract);
$ctx->set($abstract, $instance);
return $instance;
}
return parent::resolve($abstract, ...);
}
Enum을 통한 컨텍스트 키와 Facade 해결책
충돌을 피하고 안전한 접근을 보장하기 위해 enum 키를 사용합니다:
enum ScopedService: string
{
case REQUEST = 'request';
case SESSION = 'session';
case AUTH = 'auth';
case AUTH_DRIVER = 'auth.driver';
case COOKIE = 'cookie';
}
이것은 다음을 제공합니다:
- 접근 격리(오직 enum 소유자만 데이터 읽기/쓰기 가능)
- 충돌 방지 보장(동일한 문자열 값이라도)
- 정적 분석 지원(PHPStan/IDE가 사용 추적)
Facade는 resolvedInstance를 정적 배열에 캐싱하기 때문에 별도의 해결책이 필요했습니다. ScopedServiceProxy를 도입합니다:
class ScopedServiceProxy
{
public function __call($method, $args)
{
return ($this->resolver)()->$method(...$args);
}
}
프록시는 현재 코루틴의 컨텍스트로 호출을 전달하면서 프록시 수준에서 캐시를 유지합니다.
핵심 적응 사항과 PDO 풀
기본 격리 후, 내부적으로 요청별 상태를 저장하는 서비스들이 확인되어 특화된 비동기 버전이 필요했습니다:
- AsyncRouter (현재 라우트 격리)
- AsyncDispatcher (코루틴당 지연 이벤트)
- AsyncTranslator (번역 로케일)
- AsyncViewFactory (View::share())
- AsyncConfig (런타임 구성 변경)
- AsyncDatabaseSessionHandler (원자적 세션 쓰기)
- Async*Connection (트랜잭션 카운터)
데이터베이스 처리를 위해 특별한 주의를 기울였습니다. 연결 풀이 없으면 병렬 코루틴 쿼리가 PostgreSQL 프로토콜을 깨뜨립니다. TrueAsync는 내장 PDO 풀을 통해 이를 해결합니다:
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_POOL_ENABLED => true,
PDO::ATTR_POOL_MIN => 0,
PDO::ATTR_POOL_MAX => 10,
PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,
]);
풀은 동적으로 연결을 할당합니다: SELECT 결과 후 연결이 풀로 반환되어 풀 최대 크기보다 더 많은 코루틴을 처리합니다. 트랜잭션의 경우 commit/rollback까지 연결이 잠깁니다.
주요 요점
- TrueAsync는 PHP 코어 수정으로 함수 색칠을 없앰
- Laravel의 상태 유지 서비스는 current_context()를 통한 컨텍스트 격리가 필요
- PDO 풀은 병렬 환경에서 올바른 데이터베이스 작동에 필수
- Enum 키는 컨텍스트 데이터의 안전성과 추적성을 보장
- 이 해결책은 CPU 중심 작업에 대한 멀티스레딩을 대체하지 않음
— Editorial Team
아직 댓글이 없습니다.