워커 모드

Added in version 4.7.0.

중요

워커 모드는 현재 실험적 기능입니다. 공식적으로 지원되는 유일한 워커 구현체는 PHP Foundation이 지원하는 FrankenPHP입니다.

소개

워커 모드란 무엇인가?

워커 모드는 기존 PHP-FPM처럼 각 요청마다 새 프로세스를 시작하는 대신, 동일한 PHP 프로세스 내에서 여러 HTTP 요청을 처리할 수 있도록 하는 성능 최적화 기능입니다.

전통적인 PHP와 워커 모드 비교

전통적인 PHP (PHP-FPM)

전통적인 PHP에서는 각 HTTP 요청이 다음 주기를 거칩니다:

  1. 웹 서버가 요청을 수신하고 새 PHP 프로세스를 생성합니다

  2. PHP가 필요한 모든 파일을 로드하고 파싱합니다

  3. 프레임워크 부트스트랩: 오토로더, 설정, 서비스, 라우트

  4. 데이터베이스 연결이 수립됩니다

  5. 요청이 처리되고 응답이 전송됩니다

  6. 모든 연결이 종료됩니다

  7. PHP 프로세스가 종료되고 모든 메모리가 해제됩니다

이 “아무것도 공유하지 않는” 아키텍처는 단순하고 안전하지만, 단계 2~4가 매 요청마다 반복되기 때문에 트래픽이 많은 애플리케이션에는 비효율적입니다.

워커 모드

워커 모드에서는 생명주기가 크게 달라집니다:

  1. 워커 프로세스가 시작되어 일회성 초기화를 수행합니다:

    • 필요한 모든 파일을 로드하고 파싱합니다 (OPcache에 캐시됨)

    • 프레임워크 부트스트랩: 오토로더, 설정, 서비스, 라우트

    • 데이터베이스 및 캐시 연결을 수립합니다

  2. 각 수신 요청마다:

    • 기존 연결과 캐시된 리소스를 재사용합니다

    • 요청별 상태만 재설정합니다 (슈퍼글로벌, 요청/응답 객체)

    • 요청을 처리하고 응답을 전송합니다

    • 요청별 데이터를 정리합니다

  3. 워커는 계속 실행되며 다음 요청을 대기합니다

이 방식은 중복 초기화 작업과 연결 오버헤드를 제거하여, 일반적인 데이터베이스 기반 애플리케이션에서 2~3배의 성능 향상을 이끌어 냅니다.

CodeIgniter의 상태 관리 방법

워커 모드의 핵심 과제는 요청 간 상태 누출을 방지하는 것입니다. CodeIgniter는 여러 메커니즘을 통해 이를 처리합니다:

서비스 재설정

대부분의 서비스는 각 요청마다 소멸되고 재생성됩니다. $persistentServices에 나열된 서비스만 요청 간에 유지됩니다.

팩토리 재설정

모든 팩토리(모델, Config 인스턴스)는 요청 간에 재설정되어 오래된 데이터 없이 새 인스턴스를 보장합니다.

슈퍼글로벌 격리

요청 데이터($_GET, $_POST, $_SERVER 등)는 슈퍼글로벌 서비스를 통해 요청별로 적절히 격리됩니다.

연결 유지

데이터베이스 및 캐시 연결은 요청 시작 시 유효성을 검사하고, 정상이면 재사용합니다. 비정상 연결은 자동으로 재수립됩니다.

시작하기

설치

  1. 공식 문서를 참고하여 FrankenPHP를 설치합니다

    정적 바이너리 또는 Docker를 사용할 수 있습니다. 여기서는 직접 다운로드할 수 있는 정적 바이너리를 사용한다고 가정합니다.

  2. spark 명령어를 사용하여 워커 모드 템플릿 파일을 설치합니다:

    php spark worker:install
    

    이 명령은 두 개의 파일을 생성합니다:

    • Caddyfile - 워커 모드가 활성화된 FrankenPHP 설정 파일

    • public/frankenphp-worker.php - 요청 루프를 처리하는 워커 진입점

  3. 필요한 경우 app/Config/WorkerMode.php에서 워커 설정을 구성합니다. 기본값은 대부분의 애플리케이션에 권장됩니다.

참고

프로젝트 디렉토리를 변경할 때는 애플리케이션 디렉터리 이름 변경 또는 위치 이동 섹션을 참조하고 public/frankenphp-worker.php를 업데이트하십시오.

워커 실행

생성된 Caddyfile을 사용하여 FrankenPHP를 시작합니다:

frankenphp run

서버는 워커 모드가 활성화된 상태로 시작되어 워커 진입점을 통해 요청을 처리합니다.

백그라운드(데몬 모드)에서 실행하려면:

frankenphp start

제거

워커 모드 템플릿 파일을 제거하려면:

php spark worker:uninstall

이 명령은 Caddyfilepublic/frankenphp-worker.php 파일을 제거합니다.

성능 벤치마크

워커 모드는 일반적으로 데이터베이스 기반 애플리케이션에서 2~3배의 성능 향상을 제공합니다. 실제 향상 폭은 애플리케이션의 특성에 따라 다릅니다:

시나리오

예상 향상

단순 엔드포인트

최소한의 JSON 응답을 반환하는 애플리케이션은 제거할 부트스트랩 오버헤드가 적어 소폭 향상(10~30%)을 보입니다.

데이터베이스 쿼리

데이터베이스 쿼리를 수행하는 엔드포인트는 연결 재사용과 초기화 오버헤드 감소로 일반적으로 2~3배 향상을 보입니다.

복잡한 부트스트랩

서비스, 라우트, 설정이 많은 애플리케이션은 이 오버헤드가 완전히 제거되어 가장 큰 향상을 보입니다.

워커 수 고려사항

전체 처리량은 워커 수에 비례하여 확장됩니다. 일반적인 동시성 패턴과 사용 가능한 CPU 코어 수에 맞게 워커 수를 조정하십시오. 워커가 너무 적으면 동시성이 제한되고, 너무 많으면 메모리를 낭비합니다.

첫 번째 요청 지연

각 워커에 대한 첫 번째 요청은 부트스트랩 및 연결 수립으로 인해 느립니다. 이후 요청은 캐시된 리소스와 지속적인 연결의 혜택을 받습니다.

설정

모든 워커 모드 설정은 app/Config/WorkerMode.php 파일을 통해 관리됩니다.

설정 옵션

옵션

타입

설명

$persistentServices

array

요청 간에 유지되며 재설정되지 않는 서비스입니다. 이 목록에 없는 서비스는 상태 누출을 방지하기 위해 각 요청 후 소멸됩니다. 기본값: ['autoloader', 'locator', 'exceptions', 'commands', 'codeigniter', 'superglobals', 'routes', 'cache']

$resetEventListeners

array

요청 간에 리스너가 제거되는 이벤트 이름입니다. Config/Events.php의 최상위 레벨이 아닌 다른 이벤트 콜백 내부에 이벤트 리스너를 등록하여 요청 간에 누적될 때 사용하십시오. 기본값: []

$forceGarbageCollection

bool

각 요청 후 가비지 컬렉션을 강제 실행할지 여부입니다. true (기본값, 권장): 메모리 누수를 방지합니다. false: PHP의 자동 가비지 컬렉션에 의존합니다.

지속 서비스

$persistentServices 배열은 요청 간에 유지되는 서비스를 제어합니다. 기본 설정에는 다음이 포함됩니다:

서비스

목적

autoloader

PSR-4 오토로딩 설정입니다. 클래스 맵이 변경되지 않으므로 유지해도 안전합니다.

locator

프레임워크 파일을 찾는 파일 로케이터입니다. 성능을 위해 파일 경로를 캐시합니다.

exceptions

예외 핸들러입니다. 상태가 없으므로 재사용해도 안전합니다.

commands

CLI 명령어 레지스트리입니다. 워커 시작 시에만 사용됩니다.

codeigniter

메인 애플리케이션 인스턴스입니다. 요청/응답 주기를 조율합니다.

superglobals

슈퍼글로벌 래퍼입니다. 내부적으로 요청별로 적절히 격리됩니다.

routes

라우터 설정입니다. 라우트 정의는 요청 간에 변경되지 않습니다.

cache

캐시 서비스입니다. 캐시 백엔드(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 사용

  • 이들은 더 나은 동시성과 자동 연결 지속성을 제공합니다

데이터베이스 연결

데이터베이스 연결은 자동으로 관리됩니다:

  • 성능을 위해 연결이 요청 간에 유지됩니다

  • 각 요청 시작 시 연결의 유효성을 검사합니다

  • 실패한 연결은 자동으로 재수립됩니다

  • 커밋되지 않은 트랜잭션은 경고 로그와 함께 자동으로 롤백됩니다