86호. PHP 7.4 Preloading free

2019-12-03

오늘은 PHP 7.4에 새로 추가된 기능 중 프리로딩(Preloading)에 대해 알아보겠습니다. 7버전으로 올라오면서 속도가 많이 향상되었는데 또또 더 빨라진다니 기대가 되네요.


프리로딩이란


PHP 7.3.x에서 7.4.x로 마이그레이션하기새로운 기능 문서를 보면 프리로딩과 관련해서 OPcache 항목에 아래와 같이 짧은 한 줄로만 설명이 나옵니다.



Support for preloading code has been added.



뭐 크게 중요하진 않을 수 있지만 OPcache와 프리로딩간의 관계는 프리로딩이 OPcache를 기반으로 한다고 알고 계시면 될 것 같습니다.


프리로딩은 OPcache가 가진 두 가지 한계를 극복하기 위해 고안되었다고 합니다. 첫 번째는 리퀘스트마다 파일이 변했는지 검사한다는 것이고, 두 번째는 모든 파일이 독립적으로 캐싱 되다 보니 서로 다른 파일에 저장된 클래스 간의 의존성을 해소할 수 없어서 런타임에 이들을 연결해줬어야 한다는 점입니다.


프리로딩은 소스 코드를 opcode로 컴파일할 뿐만 아니라 연관된 클래스, 트레이트, 인터페이스도 함께 연결합니다. 이렇게 연결 정보를 포함해서 컴파일되어 PHP 인터프리터가 사용할 수 있는 코드로 메모리에 저장됩니다. 매번 파일의 변화를 감지하지 않고, 파일을 연결하지도 않아 기존에 OPcache를 쓸 때보다 속도가 빨라집니다. 대신 파일이 변하면 직접 서버를 재시작해 줘야 합니다.


프리로딩 사용 방법


파일을 프리로드하기 위해서는 PHP 스크립트가 하나 실행되어야 합니다. 이 스크립트는 php.iniopcache.preload 파일에 지정합니다. 이 프리로드 스크립트에서 프리로드할 파일을 opcache_compile_file()에 인자로 넘겨주거나, require_once()로 불러들이면 됩니다.


php.ini에 다음과 같이 설정하게 됩니다.


opcache.preload=/path/to/project/preload.php

preload.php는 아래와 같이 구현합니다.


$files = /* 프리로드 하고자 하는 파일의 배열 */;

foreach ($files as $file) {
opcache_compile_file($file);
}

예제


RFC에는 젠드 프레임워크를 통째로 프리로드 하는 헬퍼 펑션 예시가 나옵니다.


<?php
function _preload($preload, string $pattern = "/\.php$/", array $ignore = []) {
if (is_array($preload)) {
foreach ($preload as $path) {
_preload($path, $pattern, $ignore);
}
} else if (is_string($preload)) {
$path = $preload;
if (!in_array($path, $ignore)) {
if (is_dir($path)) {
if ($dh = opendir($path)) {
while (($file = readdir($dh)) !== false) {
if ($file !== "." && $file !== "..") {
_preload($path . "/" . $file, $pattern, $ignore);
}
}
closedir($dh);
}
} else if (is_file($path) && preg_match($pattern, $path)) {
if (!opcache_compile_file($path)) {
trigger_error("Preloading Failed", E_USER_ERROR);
}
}
}
}
}

set_include_path(get_include_path() . PATH_SEPARATOR . realpath("/var/www/ZendFramework/library"));

_preload(["/var/www/ZendFramework/library"]);

앤드류 데이비스는 이 예제를 가져다가 슬림 3에 적용했습니다.


스파티의 브랜트는 라라벨 애플리케이션을 손쉽게 프리로드 할 수 있게 Preloader 클래스를 만들어 공개했습니다.


Preloader 클래스를 사용하면 아래와 같은 모습이 됩니다.


(new Preloader())
->paths(__DIR__ . '/vendor/laravel')
->ignore(
\Illuminate\Filesystem\Cache::class,
\Illuminate\Log\LogManager::class,
\Illuminate\Http\Testing\File::class,
\Illuminate\Http\UploadedFile::class,
\Illuminate\Support\Carbon::class,
)
->load();

기타


부모 클래스, 인터페이스, 트레이트, 상수값 등이 모두 해결되지 않는 클래스는 프리로드 할 수 없습니다. 그리고 if() 같이 제어 구문에 들어가 있지 않은 탑-레벨 엔티티만이 프리로드 됩니다.


php.ini에 설정하고 웹서버를 재시작 해야 하기 때문에 이를 직접 제어할 수 없는 호스팅 서비스에서는 프리로딩을 사용할 수 없습니다.


브랜트나 앤드류 데이비스의 글을 보면 컴포저가 프리로드를 지원했으면 하고 바라는 내용이 있고, 실제로 논의도 활발히 진행됐는데, 아쉽게도 당분간은 지원할 계획이 없다고 합니다.



처음 문서를 읽을 때는 특정 조건을 만족시켜야 프리로드가 된다고 하기에 겁을 먹었었는데요. 프리로드 하려다가 실패해도 기존과 같이 OPcache로 동작해서 서버는 정상적으로 작동한다고 하니 큰 위험 부담 없이 활용해 볼 수 있을 것 같습니다.


1일 1식 라라벨 86호

2019년 12월 3일


이현석

바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 2020년에 출간될 Laravel Up & Running 2nd Edition을 번역하고 있습니다.