title: Download script using php-fpm/Nginx Causes High Load CPU
body: <p>I have a good server for a file sharing use, and I am having trouble with the download script.
  I use PHP-FPM running on nginx.</p>
  
  <p>The server specs:</p>
  
  <pre><code>2x Intel Xeon E5
  CPU: 92GB RAM
  10x2TB (RAID6)
  And we use 1 SSD disk for CashCade
  </code></pre>
  
  <p>When I apllay this script on apache server, it works fine, but I want to run it on nginx server, because apache takes a lot of memory. (RAM)
  But when I run this script on nginx, something really wired is happening - it takes 30% from the CPU, only one download!
  Please note that after 3-4 minutes from the begining of the download, the CPU load is go back to normal (but the download continiues).</p>
  
  <p>This is the "TOP" in LINUX When im downloading...
  <img src="https://i.stack.imgur.com/G25Z9.jpg" alt="When I&#39;m Downloading"></p>
  
  <p>I don't know why, but the PHP-FPM script is taking a lot from the CPU. The script:</p>
  
  <pre><code>class ResumeDownload {
      private $file;
      private $name;
      private $boundary;
      private $delay = 0;
      private $size = 0;
  
      function __construct($file, $delay = 0) {
          if (! is_file($file)) {
              header("HTTP/1.1 400 Invalid Request");
              die("&lt;h3&gt;File Not Found&lt;/h3&gt;");
          }
  
          $this-&gt;size = filesize($file);
          $this-&gt;file = fopen($file, "r");
          $this-&gt;boundary = md5($file);
          $this-&gt;delay = $delay;
          $this-&gt;name = basename($file);
      }
  
      public function process() {
          $ranges = NULL;
          $t = 0;
          if ($_SERVER['REQUEST_METHOD'] == 'GET' &amp;&amp; isset($_SERVER['HTTP_RANGE']) &amp;&amp; $range = stristr(trim($_SERVER['HTTP_RANGE']), 'bytes=')) {
              $range = substr($range, 6);
              $ranges = explode(',', $range);
              $t = count($ranges);
          }
  
          header("Accept-Ranges: bytes");
          header("Content-Type: application/octet-stream");
          header("Content-Transfer-Encoding: binary");
          header(sprintf('Content-Disposition: attachment; filename="%s"', $this-&gt;name));
  
          if ($t &gt; 0) {
              header("HTTP/1.1 206 Partial content");
              $t === 1 ? $this-&gt;pushSingle($range) : $this-&gt;pushMulti($ranges);
          } else {
              header("Content-Length: " . $this-&gt;size);
              $this-&gt;readFile();
          }
  
          flush();
      }
  
      private function pushSingle($range) {
          $start = $end = 0;
          $this-&gt;getRange($range, $start, $end);
          header("Content-Length: " . ($end - $start + 1));
          header(sprintf("Content-Range: bytes %d-%d/%d", $start, $end, $this-&gt;size));
          fseek($this-&gt;file, $start);
          $this-&gt;readBuffer($end - $start + 1);
          $this-&gt;readFile();
      }
  
      private function pushMulti($ranges) {
          $length = $start = $end = 0;
          $output = "";
  
          $tl = "Content-type: application/octet-stream\r\n";
          $formatRange = "Content-range: bytes %d-%d/%d\r\n\r\n";
  
          foreach ( $ranges as $range ) {
              $this-&gt;getRange($range, $start, $end);
              $length += strlen("\r\n--$this-&gt;boundary\r\n");
              $length += strlen($tl);
              $length += strlen(sprintf($formatRange, $start, $end, $this-&gt;size));
              $length += $end - $start + 1;
          }
          $length += strlen("\r\n--$this-&gt;boundary--\r\n");
          header("Content-Length: $length");
          header("Content-Type: multipart/x-byteranges; boundary=$this-&gt;boundary");
          foreach ( $ranges as $range ) {
              $this-&gt;getRange($range, $start, $end);
              echo "\r\n--$this-&gt;boundary\r\n";
              echo $tl;
              echo sprintf($formatRange, $start, $end, $this-&gt;size);
              fseek($this-&gt;file, $start);
              $this-&gt;readBuffer($end - $start + 1);
          }
          echo "\r\n--$this-&gt;boundary--\r\n";
      }
  
      private function getRange($range, &amp;$start, &amp;$end) {
          list($start, $end) = explode('-', $range);
  
          $fileSize = $this-&gt;size;
          if ($start == '') {
              $tmp = $end;
              $end = $fileSize - 1;
              $start = $fileSize - $tmp;
              if ($start &lt; 0)
                  $start = 0;
          } else {
              if ($end == '' || $end &gt; $fileSize - 1)
                  $end = $fileSize - 1;
          }
  
          if ($start &gt; $end) {
              header("Status: 416 Requested range not satisfiable");
              header("Content-Range: */" . $fileSize);
              exit();
          }
  
          return array(
                  $start,
                  $end
          );
      }
  
      private function readFile() {
          while ( ! feof($this-&gt;file) ) {
              echo fgets($this-&gt;file);
              flush();
              usleep($this-&gt;delay);
          }
      }
  
      private function readBuffer($bytes, $size = 1024) {
          $bytesLeft = $bytes;
          while ( $bytesLeft &gt; 0 &amp;&amp; ! feof($this-&gt;file) ) {
              $bytesLeft &gt; $size ? $bytesRead = $size : $bytesRead = $bytesLeft;
              $bytesLeft -= $bytesRead;
              echo fread($this-&gt;file, $bytesRead);
              flush();
              usleep($this-&gt;delay);
          }
      }
  }
  </code></pre>
  
  <p>And the <code>download.php</code> (where I run the script)</p>
  
  <pre><code>// ... some code that get the file details from extrenal Database...
  
  $fileArr = $query-&gt;fetch_assoc();
  $file = 'uploads/' . $download['fileid'] . '/' . $fileArr['name'];
  
  if(file_exists($file)) {
      $mysqli-&gt;close();
      require 'class.download.php';
      set_time_limit(0);
      $download = new ResumeDownload($file, 0); //delay about in microsecs 
      $download-&gt;process();
  }
  </code></pre>