osman103
By:
osman103

How to speed up PHP's write to file time?

January 29, 2017 497 views
PHP Apache DigitalOcean LAMP Stack Server Optimization Storage System Tools Linux Commands Miscellaneous Ubuntu 16.04

I've just made the switch from GoDaddy's Shared Hosting to DigitalOcean and their write to disk through PHP appears to be much faster, is there any way of speeding this up?

Code:

fwrite($retrieve, $replace);
fclose($retrieve);
header('Location: ./');

The code above will write a certain piece of data to a specified location before refreshing the page. The problem is, when the page is refreshed, the data hasn't been finished writing to so the user will see the old data on the page. He will have to refresh the page again to see the updates.

With GoDaddy's Shared Hosting (using the same piece of code above), the user would be redirected through header after the new data has been written to the file so the user will see the updated contents immediately, rather than having to refresh the page again.

Is there something I'm doing wrong? Is there a way to speed up the write to file time?

I am currently on DigitalOcean's $5/month plan and I would really appreciate any help on this, thanks!

3 Answers

@osman103

It'd be hard to diagnose without knowing more about what you're trying to write and the size of what you're trying to write.

That said, without any type of checks being in place, your code is going to run exactly as written, so if the write hasn't completed, the user will be redirected regardless of environment (or should).

If you can provide a little more detail such as how the data is being fetched, from where, etc, I'll do my best to help out.

  • Hi,

    Thanks for your response as well as the tests you had carried out!

    I have a simple HTML form with an input text field and a submit button which is used to edit the title of the user's page. It's generally really short, mostly along the size of "Hello world" so just a few bytes.

    There is a text file containing the data inputted by the user. That data is fetched and displayed on the HTML form so the user can see what they've written as well as make edits to it. The text file is located in the same directory as the file containing the HTML form.

    The user types a title and hits enter, then through PHP (which is within the same file containing the HTML form), the new inputted data will overwrite what was in the text file before. I guess a header('Location: ') isn't really necessary as the page will refresh anyway since the PHP is on the same file containing the form.

    With GoDaddy's shared hosting, the write to file must have been a lot quicker as when the page is refreshed, the user will immediately be able to see their new inputted data whereas now the user will have to refresh the page (sometimes even twice) to see the new data.

    • @osman103

      It really depends on how you're loading the data. I setup a really quick and dirty test just to replicate what I thought you may be doing, which you can see below.

      <?php
      $startup    =   microtime( true );
      
      $data       =   require __DIR__ . DIRECTORY_SEPARATOR . 'data.php';
      $_POST      =   filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
      
      if ( ! empty( $_POST ) )
      {
         file_put_contents( __DIR__ . DIRECTORY_SEPARATOR . 'data.php', '<?php return ' . var_export( $_POST, true ) . ";\n" );
      
         header( 'Location: ./index.php' );
      }
      ?>
      
      <form action="./index.php" method="post">
          <input type="text" name="username" value="<?php echo isset( $data['username'] ) ? $data['username'] : ''; ?>">
          <br>
          <input type="text" name="title" value="<?php echo isset( $data['title'] ) ? $data['title'] : ''; ?>">
          <br>
          <input type="submit" value="Submit">
      </form>
      
      <?php
      echo 'Execution Time: ' . ( microtime( true ) - $startup );
      ?>
      

      The var_export call inside file_put_contents is used to create a data array from the posted data and export as a usable array to data.php, which is then loaded on each page call and tested inside each input. If there's data, we'll load it to the form, if not, it's blank.

      In this the above test, I fill in the two input fields and click submit. The data is written to the data.php file and when I refresh, I can see the updated data in the form.

      So in this case, data isn't being written too slow, it's actually written as the script tells it to be. Without using AJAX or some sort of async call to refresh data shown in the form on the first submit, it shouldn't update at all, which is normal.

      The same thing should happen with or without the header call (in my example).

      That said, it'd be a little different if you were using 2 files to handle what you're needing done. For example, we have index.php and process.php. We'll take the code I have above and break it down in to those two files.

      index.php

      <?php
      $data       =   require __DIR__ . DIRECTORY_SEPARATOR . 'data.php';
      ?>
      <form action="./process.php" method="post">
          <input type="text" name="username" value="<?php echo isset( $data['username'] ) ? $data['username'] : ''; ?>">
          <br>
          <input type="text" name="title" value="<?php echo isset( $data['title'] ) ? $data['title'] : ''; ?>">
          <br>
          <input type="submit" value="Submit">
      </form>
      

      process.php

      <?php
      if ( ! empty( $_POST ) )
      {
         file_put_contents( __DIR__ . DIRECTORY_SEPARATOR . 'data.php', '<?php return ' . var_export( $_POST, true ) . ";\n" );
      
         header( 'Refresh:0; url=./index.php' );
      }
      

      We take in data on the index file and post it to the process file which uses a slightly different header than what I used before. The new header refreshes the page and the redirects back to our index page which loads the new data and displays it accordingly.

      The 0 means 0 second delay, so the refresh is immediate as is the redirect.

      • Thank you for your response back!

        Here is the latest set of code I'm using:

        index.php

        <?php
        include("data.txt);
        ?>
        
        <form action="../rewrite" method="post">
        <input type="text" name="current" value="<?php echo $title ?>">
        <button name="book"></button>
        </form>
        

        The form data will be posted to rewrite.php which is one directory below where the user's index.php is.

        rewrite.php

        <?php
            $retrieveTitle = $_POST['current'];
            $retrieveTitle = htmlspecialchars($retrieveTitle);
            $newTitle = '<?php $title = "' . $retrieveTitle . '"; ?>';
        ?>
        
        <?php
        if ( ! empty( $_POST ) )
        {
           file_put_contents('data.txt', $newTitle);
           header( "Refresh:0; url=./");
        }
        ?>
        
        

        data.txt

        <?php $title = "whatever user inputted" ?>
        
        

        The data.txt file will store "php" code as simple plain text.

        I have just run the code on both GoDaddy's Shared Hosting and DigitalOcean ($5/month) droplet and here are the execution times:

        • GoDaddy: -----0.000188112258911
        • DigitalOcean: 0.00023388862609863

        Would I need to upgrade my droplet to a higher plan in order to get a faster execution time for file writing? If so, which size droplet would you recommend?

        Thanks again for helping me out, I really appreciate it!

        • @osman103

          Realistically, we're talking about a difference of about .0005s, which is a difference in speed that, short of displaying and making it known using microtime or another tool, wouldn't even be detectable by a viewer.

          To put that in perspective, it takes about 0.3-0.4s for someone to blink. If you want to try and blink faster, we can shave that down to about 0.2s, so attempting to really do anything about that level of speed would really be premature optimization.

          It would be more likely that there's something else somewhere in your code base that could be considered for optimization way and well before this ever becomes a real issue.

          In short, there's nothing to worry about there :-).

          As for upgrading, in terms of speeding things up, it won't really help. If the issue was, for instance, due to CPU or RAM exhaustion, upgrading would help, but you wouldn't upgrade based solely on a 0.005s difference between one server and another.

          • Thanks a lot for helping me out! I will try and find a workaround, (i.e. refresh after half or a full second rather than immediately).

            Update

            • Although the execution time is around 0.0002s, I still have to set the refresh time to about 2.5 seconds

@osman103

Running a few tests on a freshly deployed droplet with only PHP 7.1 installed, I started up an instance of PHP's built-in web server using:

php -S 0.0.0.0:80 -t.

I then created a 64MB file to read from using:

fallocate -l 64m data.txt

I then created a very basic read/write index file with the following code:

<?php
$startup    =   microtime( true );
$filler     =   file_get_contents( __DIR__ . DIRECTORY_SEPARATOR . 'data.txt' );
$file       =   fopen( __DIR__ . DIRECTORY_SEPARATOR . 'data.' . time() . '.txt', 'w' );

fwrite( $file, $filler );
fclose( $file );

echo 'Execution Time: ' . ( microtime( true ) - $startup );

Times for the 64MB file were:

Execution Time: 0.37941408157349
Execution Time: 0.19304585456848
Execution Time: 0.15773510932922
Execution Time: 0.16210699081421
Execution Time: 0.20386791229248
Execution Time: 0.71326303482056
Execution Time: 0.67525696754456
Execution Time: 0.16588401794434
Execution Time: 0.14779591560364
Execution Time: 0.15287399291992

I then wiped the slate clean and created a 128MB file using the same command as above. Times for the 128MB file were:

Execution Time: 0.30879592895508
Execution Time: 0.35546898841858
Execution Time: 0.31786108016968
Execution Time: 0.88341999053955
Execution Time: 0.32654404640198
Execution Time: 0.35381197929382
Execution Time: 0.34370303153992
Execution Time: 0.60638308525085
Execution Time: 0.37295699119568
Execution Time: 0.34345412254333

There were a few spikes reading and writing both files. That said, the above wasn't filled with random data, so I also wanted to test by pulling 128MB of random data from /dev/urandom.

head -c 128m </dev/urandom >data.txt

Running the same code on the above yielded slightly better results after running it 10x, with the lower execution times being around .22-.28 compared to the .30-.88 above.

@osman103

RE:

Although the execution time is around 0.0002s, I still have to set the refresh time to about 2.5 seconds

I setup a 512MB and 2GB Droplet (Ubuntu 16.04 64bit), then tested your code on it and the redirect is instant as is the form population, so it would seem something else is causing the delay -- perhaps in a different area of the code.

I am, however, using PHP 7.1.x, though that really shouldn't make too much of a difference on the read and write commands and their speed, or how quickly the redirect passes a visitor back over to index.

One thing that may, however, be effecting the display is cache. If OpCode caching is enabled on your installation, it may not be flushing as quickly. Otherwise, any other form of caching, be it local on your PC/Mac or on the server, can also cause delays.

I'm running just a barebones PHP 7.1 install which was installed by:

sudo add-apt-repository ppa:ondrej/php -y \
&& sudo apt-get update \
&& sudo apt-get -y install php7.1-fpm php7.1-cli php7.1-dev

and I'm not running in to any issues with either my code or yours.

  • I guess for now I will just AJAX it all as a workaround so no page refresh is involved.

    Thanks a lot for helping me out with this!

Have another answer? Share your knowledge.