App Service on LinuxのPHPランタイムを変更した後、flushがバッファリングされるようになってしまった場合の対応

2021.10.06

いわさです。

App Service on LinuxのPHP環境で以下のスクリプトを実行していました。

<?php
ini_set('max_execution_time', 0);
for($i=0;$i<100;$i++)
{
    echo $i.",";
    ob_flush();
    flush();
    sleep(1);
}
?>

上記スクリプトページにアクセスすると、1秒毎にカウント値がクライアントブラウザへレスポンスされ、以下のように動的なレンダリングが行われます。

しかし、PHPランタイムバージョンを8に変更すると意図しない挙動となってしまいました。
本日は対応した内容を記しておきたいと思います。

PHP8へ変更するとバッファリングされてしまう

アプリケーション構成からPHPランタイムバージョンを7から8へ変更します。

アクセスしてみましょう。
リクエスト送信後、しばらくは何も応答がありません。

処理が終了したタイミング(今回だと約100秒後)でまとめてレンダリングされるようになりました。

原因と対応案のひとつ

App Service のランタイムがPHP8の場合、ApacheではなくNginxが使用されます。

そして、Nginxの場合、fastcgi_bufferingがデフォルトでonになっており、応答バッファリングが有効になっています。 同期的にクライアントブラウザにレスポンスを渡したい場合はfastcgi_bufferingoffにする方法があります。

※上記に記述のあるとおり、fastcgi_buffer_sizefastcgi_ignore_headersディレクティブも考慮すべき場合があります。

Enables or disables buffering of responses from the FastCGI server.

When buffering is enabled, nginx receives a response from the FastCGI server as soon as possible, saving it into the buffers set by the fastcgi_buffer_size and fastcgi_buffers directives. If the whole response does not fit into memory, a part of it can be saved to a temporary file on the disk. Writing to temporary files is controlled by the fastcgi_max_temp_file_size and fastcgi_temp_file_write_size directives.

When buffering is disabled, the response is passed to a client synchronously, immediately as it is received. nginx will not try to read the whole response from the FastCGI server. The maximum size of the data that nginx can receive from the server at a time is set by the fastcgi_buffer_size directive.

Buffering can also be enabled or disabled by passing “yes” or “no” in the “X-Accel-Buffering” response header field. This capability can be disabled using the fastcgi_ignore_headers directive.

今回は、上記のデフォルト設定値を変更する方法を取ってみました。
App Service on LinuxにおけるNginxの永続的な設定変更方法は以下の記事で実施しています。
同じ方法を取りたいと思います。

設定値を追加した構成ファイルを用意し、スタートアップスクリプトで配置しました。

server {
    #proxy_cache cache;
        #proxy_cache_valid 200 1s;
    listen 8080;
    listen [::]:8080;
    root /home/site/wwwroot;
    index  index.php index.html index.htm;
    server_name  example.com www.example.com;

    location / {
        index  index.php index.html index.htm hostingstart.html;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /html/;
    }

    # Add locations of phpmyadmin here.

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
        fastcgi_pass 127.0.0.1:9000;
        include fastcgi_params;
        fastcgi_param HTTP_PROXY "";
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param QUERY_STRING $query_string;
        fastcgi_intercept_errors on;
        fastcgi_connect_timeout         300;
        fastcgi_send_timeout           3600;
        fastcgi_read_timeout           3600;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_temp_file_write_size 256k;
        fastcgi_buffering off;
    }
}

上記のようにPHP8環境でも無事、レンダリングされるようになりました。

さいごに

App Service on LinuxのPHPランタイムバージョンによって、Webサーバーが変わるので、挙動の変化に困るケースが多いようです。
バージョンアップ時に動作がおかしい場合はWebサーバーの違いがあるという仕様を原因のひとつとして疑ってみると良いかもしれません。