Lazy Loaded Models for CakePHP

I have been doing some recent profiling on CakePHP, the results look good despite the large amount of bootstrap code that it has to run with each request. However, I have observed one thing about the framework that could pose problems on servers with heavy traffic: the bootstrap process loads model class files even if the models are not used in the duration of the request.

Why would this be a problem? Depending on the PHP setup, either CGI binary or Apache module, the server could end up exhausting file handles under heavy traffic. This issue is even worse on a virtual hosting setup. Virtual hosting allows one to run a website for cheap by sharing a server with several other websites. Because of this shared environments, there is the increased risk of file handle exhaustion leading to failed requests not only for the CakePHP-based website, but for the other hosted websites as well.

CakePHP, by default, loads everything it finds in the app/models directory into memory: even if it’s not used for the duration of the request. The only reason I see why this is done is to ensure that associations work automagically. However, simply loading everything into memory can be wasteful. It wastes memory and processing time because the PHP engine has to load and parse all those files, even if most of it is “dead code” anyway. While this is not a problem in most setups, I have seen hosting services where they do “CPU utilization capping”.

As part of my ongoing project to improve on CakePHP to allow for porting of my old framework, I have decided to implement a modification that allows for lazy-loaded models in CakePHP. The modification has two goals in mind:

  1. Allow client code that depends on the mainstream CakePHP to work without problems;
  2. Consequently, this allows the programmer to decide what gets to be loaded on each and every request and what gets to be lazy-loaded.

To allow for goal number 1, I had to leave the bootstrap code alone. For number 2, I had to modify the way CakePHP models are instantiated from both the controller and the models themselves. The controller instantiates the appropriate model at runtime. A model may instantiate one or more models from its associations model. But how do you do lazy-loading and still allow for the old model loading behavior?

My solution is to create a new directory under the app/models directory called lazy_load. All models to be lazy_loaded will be placed here. Everything else can stays in app/models.

A new function had to be created to perform the task of lazy loading. The lazyLoadModel() function in the basics.php file takes care of the task. It behaves a fashion similar to loadModel() by loading models from the lazy_loaded directory found in the modelPaths.

Show Me The Code

The changeset for this modification can be found here. This should apply cleanly to CakePHP version 1.1.8.3544. It should also apply cleanly to version 1.1.10.3825. If you are still using 1.1.8.3544, then you should upgrade to 1.1.10.3825 before applying this patch. Otherwise, you will end up having to manually resolve conflicts. Of course, if you apply this modification, you will end up having to re-apply it with each new version of CakePHP.

Does it work?

I have done a bit of profiling on an example app based on the Blog tutorial from the CakePHP Manual as well as tests using Siege. For this test, I created 10 dummy model class files and placed them under the app/models directory as usual and then I ran the profiler from my demo version of Zend IDE. As expected, CakePHP loads everything it can find under the app/models directory and chews up processing time, memory and file handles. The Zend IDE profiler reports that it had to open at least 50 files for the blog app home page.

With Siege, with 100 concurrent requests and with 10000 reps per connection (resulting in 1,000,000 hits total), the server began to cave after the first 4,000 hits and continued to slowdown to at most 40 to 70 seconds on a lot of requests. I was not sure if there were any file handle exhaustion errors since Siege is not able to display the actual page that it requested.

For the second test, I moved all the dummy classes from app/models to app/models/lazy_load I also did the same for the classes that are actually used by the app (post.php and tag.php). First the Zend IDE profiler reports only 40 files are being opened for each request. With Siege, under the same settings as before, the server was able to make it through the ordeal slowing down to 10 to 20 seconds on some requests after about 700,000 hits.

For me, it’s a significant improvement. But you don’t have to take my word for it so go do your own tests and let me know about your results.

Caveats

This is a modification on the mainstream CakePHP code. Which means if anything changes on the mainstream, this modification may also be affected. If you apply the patch to future versions of CakePHP and it results in failed hunks, then you should check what went wrong and take the necessary steps to correct the problem.

Theoretically, the lazy loader should work for plugins too. But I have not tried it yet.

Conclusion

If you like this modification and use it in your production site, please leave a comment here and let me know what you think.

This and other modifications I am making are being made independently from the CakePHP project. As such, you should never report problems you encounter with my modifications to either the CakePHP mailing list or IRC channel. Please report them directly to me.

If you want to send bug fixes or suggestions for improvement, please send email to:

nimrod.abing+blog@gmail.com

Please use the subject line:

PATCH: CakePHP Model Lazy Loader

5 Responses to “Lazy Loaded Models for CakePHP”

  1. bobalien Says:

    i like it… it’s always irked me that every model class gets loaded on each request, and I’m glad there’s someone out there smarter than me working on the problem ;)

  2. nimrod.abing Says:

    I’m glad someone has shown interest in this. You might want to look at some other improvements that I have made to the basic CakePHP, such as Delegating Actions to Separate Classes. The purpose of which is to improve maintainability of larger applications. Although it may be a bit of overkill for small applications.

    Look in the CakePHP Category for all things CakePHP.

    For the diff patches, you can find them in the CakePHP-Patches Area. Or if you have Darcs, you can use it to grab the latest copy from my repository, simply do:

    darcs get http://abing.gotdns.com/darcs/repos/cake
    

    Then do a darcs pull from time to time to keep your local copy up to date.

  3. bobalien Says:

    looks like someone was listening: https://trac.cakephp.org/wiki/notes/1.1.x.x

    “Models - all models are now lazy loaded so that only the ones Cake needs are used. If you plan to create your own instance of a model you must use loadModel(‘ModelName’);”

  4. Jeena Says:

    Unbelievable. You are a genius. Lazy loading on Cake’s ORM is what I was dreaming about for months. Thank you, thank you. I hope your code will be added in the original code. Did you submit it to bakery?

  5. nimrod.abing Says:

    @Jeena

    As bobalien above says, they implemented some form of lazy loading. The patch above will only work for a specific development version of CakePHP, and that version was a long time ago. I have not taken a look at CakePHP for a while. They started making some design decisions that struck me as odd a while back and I lost interest after there was a bit of in-fighting between devs.

Leave a Reply

Comments are moderated by the administrator. If this is your first time posting a comment, your comment will go to a moderation queue and it may take a while for your comment to appear. Or it may get deleted.