워커 모드
Added in version 4.7.0.
중요
워커 모드는 현재 실험적 기능입니다. 공식적으로 지원되는 유일한 워커 구현체는 PHP Foundation이 지원하는 FrankenPHP입니다.
소개
워커 모드란 무엇인가?
워커 모드는 기존 PHP-FPM처럼 각 요청마다 새 프로세스를 시작하는 대신, 동일한 PHP 프로세스 내에서 여러 HTTP 요청을 처리할 수 있도록 하는 성능 최적화 기능입니다.
전통적인 PHP와 워커 모드 비교
전통적인 PHP (PHP-FPM)
전통적인 PHP에서는 각 HTTP 요청이 다음 주기를 거칩니다:
웹 서버가 요청을 수신하고 새 PHP 프로세스를 생성합니다
PHP가 필요한 모든 파일을 로드하고 파싱합니다
프레임워크 부트스트랩: 오토로더, 설정, 서비스, 라우트
데이터베이스 연결이 수립됩니다
요청이 처리되고 응답이 전송됩니다
모든 연결이 종료됩니다
PHP 프로세스가 종료되고 모든 메모리가 해제됩니다
이 “아무것도 공유하지 않는” 아키텍처는 단순하고 안전하지만, 단계 2~4가 매 요청마다 반복되기 때문에 트래픽이 많은 애플리케이션에는 비효율적입니다.
워커 모드
워커 모드에서는 생명주기가 크게 달라집니다:
워커 프로세스가 시작되어 일회성 초기화를 수행합니다:
필요한 모든 파일을 로드하고 파싱합니다 (OPcache에 캐시됨)
프레임워크 부트스트랩: 오토로더, 설정, 서비스, 라우트
데이터베이스 및 캐시 연결을 수립합니다
각 수신 요청마다:
기존 연결과 캐시된 리소스를 재사용합니다
요청별 상태만 재설정합니다 (슈퍼글로벌, 요청/응답 객체)
요청을 처리하고 응답을 전송합니다
요청별 데이터를 정리합니다
워커는 계속 실행되며 다음 요청을 대기합니다
이 방식은 중복 초기화 작업과 연결 오버헤드를 제거하여, 일반적인 데이터베이스 기반 애플리케이션에서 2~3배의 성능 향상을 이끌어 냅니다.
CodeIgniter의 상태 관리 방법
워커 모드의 핵심 과제는 요청 간 상태 누출을 방지하는 것입니다. CodeIgniter는 여러 메커니즘을 통해 이를 처리합니다:
- 서비스 재설정
대부분의 서비스는 각 요청마다 소멸되고 재생성됩니다.
$persistentServices에 나열된 서비스만 요청 간에 유지됩니다.- 팩토리 재설정
모든 팩토리(모델, Config 인스턴스)는 요청 간에 재설정되어 오래된 데이터 없이 새 인스턴스를 보장합니다.
- 슈퍼글로벌 격리
요청 데이터(
$_GET,$_POST,$_SERVER등)는 슈퍼글로벌 서비스를 통해 요청별로 적절히 격리됩니다.- 연결 유지
데이터베이스 및 캐시 연결은 요청 시작 시 유효성을 검사하고, 정상이면 재사용합니다. 비정상 연결은 자동으로 재수립됩니다.
시작하기
설치
공식 문서를 참고하여 FrankenPHP를 설치합니다
정적 바이너리 또는 Docker를 사용할 수 있습니다. 여기서는 직접 다운로드할 수 있는 정적 바이너리를 사용한다고 가정합니다.
spark 명령어를 사용하여 워커 모드 템플릿 파일을 설치합니다:
php spark worker:install이 명령은 두 개의 파일을 생성합니다:
Caddyfile - 워커 모드가 활성화된 FrankenPHP 설정 파일
public/frankenphp-worker.php - 요청 루프를 처리하는 워커 진입점
필요한 경우 app/Config/WorkerMode.php에서 워커 설정을 구성합니다. 기본값은 대부분의 애플리케이션에 권장됩니다.
참고
프로젝트 디렉토리를 변경할 때는 애플리케이션 디렉터리 이름 변경 또는 위치 이동 섹션을 참조하고 public/frankenphp-worker.php를 업데이트하십시오.
워커 실행
생성된 Caddyfile을 사용하여 FrankenPHP를 시작합니다:
frankenphp run
서버는 워커 모드가 활성화된 상태로 시작되어 워커 진입점을 통해 요청을 처리합니다.
백그라운드(데몬 모드)에서 실행하려면:
frankenphp start
제거
워커 모드 템플릿 파일을 제거하려면:
php spark worker:uninstall
이 명령은 Caddyfile 및 public/frankenphp-worker.php 파일을 제거합니다.
성능 벤치마크
워커 모드는 일반적으로 데이터베이스 기반 애플리케이션에서 2~3배의 성능 향상을 제공합니다. 실제 향상 폭은 애플리케이션의 특성에 따라 다릅니다:
시나리오 |
예상 향상 |
|---|---|
단순 엔드포인트 |
최소한의 JSON 응답을 반환하는 애플리케이션은 제거할 부트스트랩 오버헤드가 적어 소폭 향상(10~30%)을 보입니다. |
데이터베이스 쿼리 |
데이터베이스 쿼리를 수행하는 엔드포인트는 연결 재사용과 초기화 오버헤드 감소로 일반적으로 2~3배 향상을 보입니다. |
복잡한 부트스트랩 |
서비스, 라우트, 설정이 많은 애플리케이션은 이 오버헤드가 완전히 제거되어 가장 큰 향상을 보입니다. |
워커 수 고려사항
전체 처리량은 워커 수에 비례하여 확장됩니다. 일반적인 동시성 패턴과 사용 가능한 CPU 코어 수에 맞게 워커 수를 조정하십시오. 워커가 너무 적으면 동시성이 제한되고, 너무 많으면 메모리를 낭비합니다.
첫 번째 요청 지연
각 워커에 대한 첫 번째 요청은 부트스트랩 및 연결 수립으로 인해 느립니다. 이후 요청은 캐시된 리소스와 지속적인 연결의 혜택을 받습니다.
설정
모든 워커 모드 설정은 app/Config/WorkerMode.php 파일을 통해 관리됩니다.
설정 옵션
옵션 |
타입 |
설명 |
|---|---|---|
$persistentServices |
array |
요청 간에 유지되며 재설정되지 않는 서비스입니다. 이 목록에 없는 서비스는 상태 누출을 방지하기 위해 각 요청 후 소멸됩니다. 기본값: |
$resetEventListeners |
array |
요청 간에 리스너가 제거되는 이벤트 이름입니다. Config/Events.php의 최상위 레벨이 아닌 다른 이벤트 콜백 내부에 이벤트 리스너를 등록하여 요청 간에 누적될 때 사용하십시오. 기본값: |
$forceGarbageCollection |
bool |
각 요청 후 가비지 컬렉션을 강제 실행할지 여부입니다. |
지속 서비스
$persistentServices 배열은 요청 간에 유지되는 서비스를 제어합니다. 기본 설정에는 다음이 포함됩니다:
서비스 |
목적 |
|---|---|
|
PSR-4 오토로딩 설정입니다. 클래스 맵이 변경되지 않으므로 유지해도 안전합니다. |
|
프레임워크 파일을 찾는 파일 로케이터입니다. 성능을 위해 파일 경로를 캐시합니다. |
|
예외 핸들러입니다. 상태가 없으므로 재사용해도 안전합니다. |
|
CLI 명령어 레지스트리입니다. 워커 시작 시에만 사용됩니다. |
|
메인 애플리케이션 인스턴스입니다. 요청/응답 주기를 조율합니다. |
|
슈퍼글로벌 래퍼입니다. 내부적으로 요청별로 적절히 격리됩니다. |
|
라우터 설정입니다. 라우트 정의는 요청 간에 변경되지 않습니다. |
|
캐시 서비스입니다. 캐시 백엔드(Redis, Memcached)에 대한 연결을 유지합니다. |
경고
상태 관리를 이해하지 않고 서비스를 $persistentServices에 추가하면 요청 간 데이터 누출이 발생할 수 있습니다. 완전히 상태가 없거나 자체적으로 요청 격리를 관리하는 서비스만 유지하십시오.
이벤트 리스너 재설정
Added in version 4.7.1.
Config/Events.php의 최상위 레벨에 등록된 이벤트 리스너는 워커 시작 시 한 번 로드되어 요청 간에 올바르게 유지됩니다. 그러나 다른 이벤트의 콜백 내부에 리스너를 등록하면 매 요청마다 재등록되어 누적됩니다:
<?php
use CodeIgniter\Events\Events;
// This runs every request — the 'my_event' listener stacks up indefinitely
Events::on('pre_system', static function (): void {
Events::on('my_event', 'MyClass::myMethod');
});
요청 간에 이러한 리스너를 정리하려면 app/Config/WorkerMode.php의 $resetEventListeners에 이벤트 이름을 추가하십시오:
<?php
namespace Config;
class WorkerMode extends \CodeIgniter\Config\WorkerMode
{
public array $resetEventListeners = ['my_event'];
}
참고
권장 방법은 콜백 내부가 아닌 Config/Events.php의 최상위 레벨에 리스너를 등록하는 것입니다. 콜백 내부에 등록하는 것이 불가피한 경우에만 $resetEventListeners를 사용하십시오.
최적화 설정
app/Config/Optimize.php 설정 옵션(Config 캐싱 및 파일 로케이터 캐싱)은 워커 모드와 함께 사용하면 안 됩니다.
이러한 최적화는 각 요청이 새로 시작되는 전통적인 PHP를 위해 설계되었습니다. 워커 모드에서는 지속적인 프로세스가 이미 이러한 이점을 자연스럽게 제공하며, 활성화하면 오래된 데이터로 인한 문제가 발생할 수 있습니다.
경고
워커 모드를 사용할 때는 app/Config/Optimize.php에서 $configCacheEnabled 또는 $locatorCacheEnabled를 활성화하지 마십시오.
OPcache 설정
워커 모드는 OPcache로부터 상당한 혜택을 받습니다. OPcache가 활성화되어 올바르게 설정되었는지 확인하십시오:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; In production only
중요 고려사항
상태 관리
PHP 프로세스가 요청 간에 지속되므로 상태 관리에 세심한 주의가 필요합니다:
정적 속성 사용 금지 - 요청별 데이터에는 정적 속성을 사용하지 마십시오. 이러한 속성은 요청 간에 유지됩니다
싱글톤 주의 - 요청 상태를 유지할 수 있는 싱글톤에 주의하십시오
리소스 정리 - 각 요청 후 파일 핸들 및 데이터베이스 커서와 같은 리소스를 정리하십시오
사용자 데이터 미저장 - 요청 간에 유지되는 클래스 속성에 사용자 데이터를 저장하지 마십시오
등록자
Config 등록자(Config/Registrar.php 파일)는 워커 모드에서 올바르게 작동합니다. 등록자 파일은 워커 시작 시 한 번 검색되며, Config 클래스가 인스턴스화될 때마다 등록자 로직이 적용됩니다. 각 요청은 등록자가 적용된 새 Config 인스턴스를 받습니다.
메모리 관리
메모리 사용량을 모니터링하십시오:
ps aux | grep frankenphp
메모리가 지속적으로 증가하는 경우:
가비지 컬렉션이 활성화되어 있는지 확인하십시오 (
$forceGarbageCollection = true)사용 후 리소스가 올바르게 닫히는지 확인하십시오
더 이상 필요하지 않은 대형 객체를 해제하십시오
가비지 컬렉션을 방해하는 순환 참조를 확인하십시오
디버깅
워커 모드는 프로세스가 요청 간에 지속되므로 디버깅이 더 어려울 수 있습니다:
코드 변경 후 워커 재시작 - 워커는 수정된 파일을 자동으로 다시 로드하지 않습니다
로그 확인 - 연결 및 상태 관련 문제는 writable/logs/의 로그를 확인하십시오
로깅 적극 활용 - 지속적인 프로세스 전반에 걸쳐 요청 흐름을 추적하기 위해 로깅을 적극 활용하십시오
세션 및 캐시 핸들러
파일 기반 핸들러는 워커 간에 파일 잠금 경합이 발생할 수 있습니다. 프로덕션 환경에서는 다음을 고려하십시오:
세션 및 캐시에는 Redis 또는 Memcached 사용
이들은 더 나은 동시성과 자동 연결 지속성을 제공합니다
데이터베이스 연결
데이터베이스 연결은 자동으로 관리됩니다:
성능을 위해 연결이 요청 간에 유지됩니다
각 요청 시작 시 연결의 유효성을 검사합니다
실패한 연결은 자동으로 재수립됩니다
커밋되지 않은 트랜잭션은 경고 로그와 함께 자동으로 롤백됩니다