Output Buffering

Output Buffering and Streaming Responses

PHP collects echo output in buffers. Ubuntu 225 's FPM php.ini sets output_buffering = 4096, one 4 KB level that also lets you call header() after a little output, and ob_start() adds levels of your own for capturing a template into a string. A long report or CSV export wants the opposite: each part sent as soon as it is ready.

stream.php: sending rows as they are producedPHP
<?php
header('Content-Type: text/plain');
echo "output_buffering = ", ini_get('output_buffering'), ", levels = ", ob_get_level(), "\n";
while (ob_get_level() > 0) {
    ob_end_flush();                       // stop php.ini's buffer from holding output
}
for ($row = 1; $row <= 3; $row++) {
    echo "row $row ready at ", date('H:i:s'), "\n";
    flush();                              // hand the bytes to FPM and Apache now
    sleep(1);                             // stands in for a slow query per row
}

At first all four lines reached curl 3,008 together after three seconds: mod_proxy_fcgi flushes only when its buffer fills. Adding ProxySet flushpackets=on inside a <Proxy "fcgi://localhost"> block in the virtual host (a worker setting honored only by mod_proxy_fcgi and mod_proxy_ajp) fixed it:

Output of 245
$ curl -sN http://localhost/stream.php |
>   while IFS= read -r l; do echo "$(date -u +%T) got: $l"; done
09:14:45 got: output_buffering = 4096, levels = 1
09:14:45 got: row 1 ready at 09:14:45
09:14:46 got: row 2 ready at 09:14:46
09:14:47 got: row 3 ready at 09:14:47

Every layer must let go: with Accept-Encoding: gzip, mod_deflate still held the first byte for 3.0 seconds, so exclude streaming URLs from compression.