• May 29, 2015

Optimizing Slim Routing

Optimizing Slim Routing

150 150 RealSelf Team

PHP image courtesy of flickr user stethoscopeis a general purpose programming language–but it acts a little differently than most (Ruby, Java, etc.) when used as a plugin to your web server. A Ruby on Rails app starts up and loads a bunch of things into memory and continues to run, but an Apache+PHP application is constantly dumping your PHP code and re-starting everything for every web request. Thus, while you can have a lengthy startup time in Rails and it’s totally cool, you must optimize your PHP code to do as little as possible all the time to keep it fast. This means keeping careful track of how your code loads up all of its dependencies–and not all PHP web frameworks do this elegantly.

Here at RealSelf, we do continuous integration and rapid deployment, and we iterate on things quickly. Sometimes code that starts out as an experiment turns into a key component of our infrastructure–even if its architecture wasn’t initially optimized for performance! So when we chose Slim to iterate quickly on a new API service, we got something up really quickly, and then slowly watched it grow in importance and response times. Recently, this situation became untenable as server response times grew to >300ms per request. We are getting between 1-3000 requests per minute for this particular service and end-users were starting to see the effects. Luckily, there was a simple solution to get around Slim’s deficiencies.

We discovered that each single route that we registered added a significant overhead to each request. As an experiment, we doubled the number of routes registered, and saw that we roughly doubled the response time of the server. Ouch! This service has a rather large API, so the number of registrations is significant here. Since this is a PHP app served by Apache, we quickly realized that we really don’t need 99% of these routes registered on a given request: Apache already knows which route was requested when it starts executing the PHP code, and PHP exposes this information in the $_SERVER['REQUEST_URI'] bucket. So, we really only need to register ONE route with the Slim framework. Nice.

We parsed out the REQUEST_URI and made a little registration routine that selects only the specific section of code concerned with registering that route, and the results were dramatic:

We shaved ~250 milliseconds off of our request time, bringing most requests under the ~100ms line. Notably, PHP is _still_ taking about 85% of the total request time, so clearly there are more optimizations to do. We should be able to get our response below 50ms. Really, all PHP needs to do is load some extremely targeted business logic (usually not even any logic, just a database query) and then turn that into a JSON payload to hand back to Apache. If we’re doing anything else, it’d better be for a really good reason!

It’s worth noting, however, that in other languages this wouldn’t be an issue at all. A Java web server would load all of this overhead up on app startup and code would be loaded, unloaded, and garbage-collected whenever the JVM sees the need. PHP is a very different beast, and needs to be treated very differently.