This blog post is about how eZ Publish serves content images -- also called image aliases -- in a DFS cluster setup. The eZ DFS setup is the recommended and most common solution for high traffic eZ Publish sites that are run off multiple servers. However, it still has some room for improvement when it comes to serving images. In this post, we describe how eZ Publish currently serves image aliases, along with its downsides and some alternative approaches. At the end, we describe a possible implementation with some example performance gains.
A request for an image alias is first routed by an Apache rewrite rule:
RewriteRule ^/var/([^/]+/)?storage/images/.* /index_cluster.php [L]
index_cluster.php (and some other PHP scripts) first checks if the requested URI exists in the cluster database and if the image is still available (not removed or expired). (For more information about the eZ DFS cluster database, please see the documentation.)
We've implemented the solution described below for one of our clients, following solution #2. For this particular client, we are already using a reverse proxy (solution #1) and we are not interested in an implementation for solution 3: the client is not worried about the visibility and permission checks. They are very much interested in improving the system performance.
First, an Apache rewrite rule would check for the existence of the local image file. (Note that we also list a second rewrite rule for serving images that do exist, since the very last eZ Publish rewrite rule sends all remaining requests to "index.php".)
# Check for a local image file
# If it doesn't exist, use a PHP script that serves the image from the shared file system and also places a copy locally.
RewriteCond %{REQUEST_URI} ^/var/([^/]+/)?storage/images/.*
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/var/([^/]+/)?storage/images/.* /index_cluster.php [L]
# Serve local image file if it exists
RewriteRule ^/var/([^/]+/)?storage/images(-versioned)?/.* - [L]
In "index_cluster.php" we configure a new "storage backend":
define( 'STORAGE_BACKEND', 'localcache' );
This allows us to put our code in a dedicated file "index_image_localcache.php". Here is the code showing the general idea. It does not contain the code for some helper functions.
<?php $source = MOUNT_POINT_PATH . $_SERVER[ 'REQUEST_URI' ]; if( file_exists( $source ) ) { $target = dirname(__FILE__) . $_SERVER[ 'REQUEST_URI' ]; $target_temp = $target . '.' . getmypid() . '_' . rand(); create_local_directories( $target ); if( copy( $source, $target_temp ) ) { if( ! file_exists( $target) ) { rename( $target_temp, $target ); } //another process was faster else { unlink( $target_temp ); } } else { serve_500( 'Cannot create local copy. Check permissions.' ); } serve_image( $target ); } else { serve_404( $source ); } ?>
Serving image aliases with the original code of the DFS handler (index_image_dfsmysqli.php) is already quite fast. Here is the ApacheBench test result from our example client:
ab -n 10000 -c 150 http://192.168.100.181/var/ezwebin_site/storage/images/media/images/0927-image-1/10760921-1-eng-US/0927-image-1_thumbnail_65_cropped.jpg Failed requests: 0 Requests per second: 5316.28 [#/sec] (mean)
Now, look at the same test with our handler (index_image_localcache.php):
ab -n 10000 -c 150 http://192.168.100.181/var/ezwebin_site/storage/images/media/images/0927-image-1/10760921-1-eng-US/0927-image-1_thumbnail_65_cropped.jpg Failed requests: 0 Requests per second: 14182.20 [#/sec] (mean)
Performance is not the only reason to use our handler. Each image alias request opens a TCP port to the database and another to the NFS server. The ApacheBench tests show that you can easily run out of available TCP connections. Those tests show "Failed requests" with the existing handler only:
ab -n 30000 -c 10 http://192.168.100.181/var/ezwebin_site/storage/images/media/images/0927-image-1/10760921-1-eng-US/0927-image-1_thumbnail_65_cropped.jpg Failed requests: <strong>2135</strong> Requests per second: 4294.20 [#/sec] (mean)
It is not very realistic to run out of TCP connections under normal/real traffic situations. (As a side note, you can check the current TCP connections with "netstat -n".) Still, avoiding extra MySQL and NFS connections will speed up the overall performance of an eZ Publish setup, as our JMeter tests proved. The test that we used in JMeter simulates visitors browsing landing pages and article pages with many image thumbnails on each page. The overall performance gain (using index_image_localcache.php) was about 10%. In real traffic situations (without a reverse proxy), that performance gain is probably higher because the MySQL and NFS caching is not that effective (compared to the repetitive traffic pattern from JMeter). When you are behind a reverse proxy and you have long caching times configured for image aliases, the overall performance gain is smaller but still tangible.
We're a group of web experts who solve complex web problems.
Learn more about us »Many years of experience with complex websites allows us to offer total solutions.
Learn more about what we can do »We've solved problems across North America and around the world.
Learn more about what we've done »
Comments
blog comments powered by Disqus