Generating thumbnails with PHP and Imagemagick under high load
Many web sites allow users to upload images and photos. This of course requires, at some point, you (the web developer) to generate thumbnails because you need images of a certain size (which is never the size of the original image the user uploaded). Image resizing in PHP can be done in several ways, most notably by using the GD library (most often by implementing the imagecopyresampled() function) or by using the Imagemagick command-line utility.
Imagemagick has some nice features such as auto-rotating images based on camera position, a feature which most cameras support and a feature users expect your software to have. It supports many file formats and is reasonably fast. If you have your own (Linux) server, you can easily install it by using yum or apt-get.
There are some caveats when resizing images on a high load server. Two will be discussed here. First, we will discuss how to use Apache to check if a cached thumbnail exists, secondly we will discuss the uses of file locking when generating thumbnails.
Use Apache to serve your cached images
Most image resizing tutorials for PHP tell you to cache your resized image so that you do not need to resize it again. This is good practice since resizing a 12 mega pixel image to a 150×100 pixel thumbnail is a fairly computational expensive operation and will bring your server down ridiculously fast if you don’t cache this operation.
However, most tutorials tell you to use PHP to check is there is a cached image and then serve the cached image. This is not necessary, Apache can do this by itself. This can easily be ten times as fast as serving the image through PHP. You can do this by using Apaches extremely versatile module mod_rewrite. Put something like this in the .htaccess file in the folder you use to store your generated thumbnails in:
RewriteEngine On
RewriteBase /thumbnails
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule .* _generator.php [L]
What do these lines do? First, you tell Apache to enable the mod_rewrite module. Then, you tell Apache where in the document root you are (this example assumes you store your generated thumbnails in the folder /thumbnails, thus: www.example.net/thumbnails). The final three lines tell Apache to call _generator.php if the requested file is not a directory (!-d) or file (!-f, thus, the requested file does not exist). If the requested thumbnail has been created earlier, the HTTP transfer of the thumbnail is taken care of by Apache no PHP file is called. Much faster and requires far less memory. If the requested file does not exist, the _generator.php file is called and you can generate the required thumbnail. After the thumbnail has been generated, Apache will take care of subsequent requests.
There is one caveat: you have to use URIs for requesting thumbnails that can be mapped to files and your thumbnail generation script has to be modified to support this. So, instead of creating a thumbnail by calling /thumbnail.php?id=22&width=150&height=100, you would have to call something like /thumbnails/22-150x100px.jpg. Notice the .jpg extension, which will tell Apache to send the file with a image/jpeg content type.Of course, your thumbnail generation script will have to support this syntax. The URL used to call your script is available in the $_SERVER['REQUEST_URI'] variable.
Use file locking when generating thumbnails
Imagine you have a album index page. Such a page often contains many (tens or even hundreds) of thumbnails. It is common practise to generate thumbnails when first requested by a visitor. However, when two users request the same thumbnail at the same time, two PHP scripts will be started and two thumbnails will be generated at the same time. The script that finishes last will overwrite the thumbnail from the earlier script.
There are several problems with this: if the file was still read from, half a file will be sent to the client. And generating two thumbnails is more computationally expensive than generating only one thumbnail. You can overcome this problem by locking a temporary file with PHPs flock() function.
How does this work? First, you create a temporary file. (Just use the file name of the thumbnail you want to generate, and append “.lock”) and then try to get an exclusive lock (LOCK_EX) on it. If this fails, you know another script is already generating the thumbnail. You can then get a shared lock (LOCK_SH) and wait until the lock is released. The thumbnail will then be completed and you can use readfile() or fpassthru() to serve the thumbnail to your client.
You can use the LOCK_NB in combination with LOCK_EX to make sure that your script does not wait for the first lock to be released. If you do not use this, you will not know if you had to wait for the lock or not. Flock() supports a third parameter that you can pass by reference and will be set to true if there was a lock on the file and if it would have blocked.
if ($fp = fopen($lockfile, 'ab')) {
$lock_status = flock($fp, LOCK_EX + LOCK_NB, $wouldblock);
if (!$wouldblock) {
// Generate thumbnail here
flock($fp, LOCK_UN); // Release lock
} else {
flock($fp, LOCK_SH); // This waits until the exclusive lock is released
}
fclose($fp);
header('Content-Type: image/jpeg');
readfile($thumbnail);
}
This way, you can be sure only one script will do the expensive operation of generating a thumbnail.
If you have any questions about using mod_rewrite in thumbnail caching or about locking for generating thumbnails, feel free to post a comment.
About this entry
You’re currently reading “Generating thumbnails with PHP and Imagemagick under high load,” an entry on Willem Stuursma
- Published:
- August 8, 2009 / 13:25
- Category:
- php
- Tags:
- linkedin, performance, php

No comments yet
Jump to comment form | comment rss [?] | trackback uri [?]