고성능 PHP application server - Road Runner 와 라라벨 연동하기
Road Runner 는 Spiral scout 라는 Agency 에서 만드는 high performance PHP Application Server 입니다.
(외국에서 웹 에이전시를 하려면 이 정도 수준의 기술력을 갖춰야 경쟁력이 있는건지 잠시 좌절감을 느끼게 되네요.)
golang 으로 작성했고 go routine 으로 PHP worker 와 동작하고 gRPC 로 통신을 합니다.
PHP 의 장점이자 단점은 Request 가 끝나면 worker 가 초기화되어 버리는 것입니다.
이는 memory 나 resource 관리에 개념이 없는 초보 개발자가 PHP 를 사용해서 잘못 코딩해도 leak 이 발생하지 않으므로 안정적인 서비스가 가능했지만 이로 인해 framework 의 booting time 등이 매번 발생하므로 대규모 처리가 어려운 문제가 있습니다.
특히 laravel 같이 덩치가 큰 framework 일 경우 그 무거운 초기화를 매 요청시마다 해야 하지만 road runner 를 사용하면 worker 를 memory 에 유지해서 빠른 처리가 가능해진다고 합니다.
사전 준비
https://github.com/spiral/roadrunner-binary/releases 에 연결한 후에 road runner 바이너리를 laravel 프로젝트 루트에 다운받아 놓습니다.
다음은 2.0.1 Linux 버전을 다운받는 예제입니다.
$ curl -L -O https://github.com/spiral/roadrunner-binary/releases/download/v2.0.1/roadrunner-2.0.1-linux-amd64.tar.gz
OSX 용 바이너리는 다음 명령어로 받으면 됩니다.
$ curl -L -O https://github.com/spiral/roadrunner-binary/releases/download/v2.0.1/roadrunner-2.0.1-darwin-amd64.zip
다운받은 바이너리의 압축을 풀어서 road runner 실행 파일인 rr 을 라라벨 프로젝트 루트에 복사해 둡니다.
$ tar -zxvf roadrunner-2.0.1-linux-amd64.tar.gz $ mv roadrunner-2.0.1-linux-amd64/rr .
road runner 바이너리가 동작하는지 실행해 봅니다.
$ ./rr Usage: rr [command] Available Commands: help Help about any command reset Reset workers of all or specific RoadRunner service serve Start RoadRunner server workers Show information about active roadrunner workers ...
설치
컴포저로 road runner 라라벨 bridge 프로젝트를 설치합니다.
$ composer require spiral/roadrunner-laravel "^4.0"
vendor 설정 파일을 퍼블리싱합니다.
$ php ./artisan vendor:publish --provider='Spiral\RoadRunnerLaravel\ServiceProvider' --tag=config
라라벨 프로젝트 root 폴더에 road runner 설정 파일인 .rr.yaml 을 만들고 다음 내용을 추가해 줍니다.
server: command: "php ./vendor/bin/rr-worker start --relay-dsn unix:///var/run/rr/rr-rpc.sock" relay: "unix:///var/run/rr/rr-rpc.sock" http: address: 0.0.0.0:8080 middleware: ["headers", "static", "gzip"] pool: max_jobs: 64 # feel free to change this supervisor: exec_ttl: 60s headers: response: X-Powered-By: "RoadRunner" static: dir: "public" forbid: [".php"]
일반 사용자로 Road Runner 를 띄울 경우 /var/run 폴더에 Unix Domain socket 을 만들 권한이 없어서 에러가 나므로 /var/run 밑에 폴더(Ex: rr)를 만들고 Road Runner 를 뛰울 사용자 소유로 변경해 줍니다.
$ sudo mkdir /var/run/rr/ $ sudo chown lesstif /var/run/rr/
이제 다음 명령어로 Road Runner 를 구동하면 됩니다.
$ ./rr serve
nginx 연결
nginx 를 웹 서버로 사용한다면 가상 호스트 설정에 proxy_pass 를 추가해 줍니다.
server { listen 80; listen 443 ssl; server_name rr.local; root "/var/www/lesstif/road-runner/public"; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } ssl_certificate /etc/nginx/ssl/rr.local.crt; ssl_certificate_key /etc/nginx/ssl/rr.local.key; }
wrk 로 테스트
제 desktop 인 fedora 33 에 순정 설치 laravel 의 landing 페이지를 부하 측정 도구인 wrk 에 다음 옵션을 줘서 테스트를 해보았습니다.
$ wrk -t 4 -c 50 http://rr.local
nginx + php-fpm
먼저 nginx에서 fastcgi 로 연결한 php-fpm 을 테스트해 보았습니다. nginx 설정은 다음과 같습니다.
location ~ \.php$ { # Mitigate https://httpoxy.org/ vulnerabilities fastcgi_param HTTP_PROXY ""; fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_intercept_errors off; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; }
$ wrk -t 4 -c 50 http://rr.local Running 10s test @ http://rr.local 4 threads and 50 connections Thread Stats Avg Stdev Max +/- Stdev Latency 155.95ms 282.79ms 1.84s 88.59% Req/Sec 174.36 55.54 330.00 70.50% 6953 requests in 10.01s, 123.36MB read Socket errors: connect 0, read 0, write 0, timeout 2 Requests/sec: 694.44 Transfer/sec: 12.32MB
초당 약 7백여개의 요청을 처리했습니다.
nginx + road runner
이제 road runner 와 nginx 을 reverse proxy 로 연결하고 wrk 로 동일한 조건으로 요청을 전송했습니다.
$ wrk -t 4 -c 50 http://rr.local Running 10s test @ http://rr.local 4 threads and 50 connections Thread Stats Avg Stdev Max +/- Stdev Latency 166.61ms 286.86ms 1.99s 91.16% Req/Sec 126.10 95.12 720.00 79.38% 4968 requests in 10.01s, 88.37MB read Socket errors: connect 0, read 0, write 0, timeout 9 Requests/sec: 496.12 Transfer/sec: 8.83MB
초당 496 정도 나오는데 생각보다 너무 성능이 안 나와서 살펴보다가 soft max open file 이 1024인 것을 발견하고 max open file 을 Hard, Soft 둘 다 524,288 로 수정했습니다.
$ ulimit -Hn 524288 $ ulimit -Sn 524288
그리고 다시 wrk 로 테스트를 해보았는데 크게 성능 차이가 없었습니다.
$ wrk -t 4 -c 50 http://rr.local Running 10s test @ http://rr.local 4 threads and 50 connections Thread Stats Avg Stdev Max +/- Stdev Latency 179.50ms 296.11ms 1.72s 90.19% Req/Sec 128.81 108.37 707.00 81.00% 5002 requests in 10.01s, 88.98MB read Socket errors: connect 0, read 0, write 0, timeout 1 Requests/sec: 499.61 Transfer/sec: 8.89MB
그래서 road runner 를 구동시 console 에서 구동해서 로그를 화면에 뿌리느라 성능에 영향을 줄수 있을 것 같아서 중지하고 출력을 화면에 뿌리도록 수정했습니다.
$ ./rr serve &> rr.log
그리고 다시 wrk 를 실행해 보아도 큰 차이가 없었습니다.
road runner 에 직접 연결
nginx 를 사용하지 않고 road runner 에 직접 연결해 보았습니다.
$ wrk -t 4 -c 50 http://rr.local:8080 Running 10s test @ http://rr.local:8080 4 threads and 50 connections Thread Stats Avg Stdev Max +/- Stdev Latency 182.63ms 281.43ms 1.77s 90.31% Req/Sec 113.31 77.94 540.00 75.77% 4470 requests in 10.01s, 79.23MB read Requests/sec: 446.52 Transfer/sec: 7.91MB
역시 크게 차이를 못 느끼겠네요.
PHP-FPM이 더 빠른 걸로 봐서는 아마 제가 설정을 잘못한 게 아닐까 싶네요.
PHP.ini 에서 성능에 영향을 줄 설정은 다음과 같습니다.
- memory_limit => 512M => 512M
- opcache.enable => On => On
- opcache.enable_cli => On => On