Tag: cakephp cache

Million dollar site in CakePHP …

Is that possible? Yes, and it has been done.

But…

— CakePHP is slow. I’ve seen bench-marks on “Hello world” and cake falls behind other frameworks.
— What’s your caching strategy?
— [crickets] …

  1. Cache content
  2. CakePHP comes with a built-in view caching mechanism. Granted there is always a question of real-time data vs. performance, but I’ve yet to come across a project where at least some content could not be cached. Even a five or ten minute cache can be quite helpful if you’ve got millions of hits a day on your site. I’ve described some strategies in a post a while back, so give it a read, if you are after the details of implementation.
    If you need something faster, consider adding a front-end cache solution, like varnish or reverse-proxy nginx. (If you have a choice consider replacing apache, which requires a lot of tweaking with nginx as your web server, which is quite fast out of the box).

  3. Cache DB queries
  4. Optimizing queries is important. You should always use containable and limit the fields being returned by each query. This optimization will only take you so far, however… and I would argue that before you go head-first into nitty-gritty details of each query, you should invest into setting up memcached (nicely supported by cake) to alleviate some load on your DB.

  5. Index your DB fields
  6. Do you have some JOIN’s? Do you perform some searches on certain fields? (i.e. “username”). Then you’d better remember to index any fields in your DB that are used in the JOIN or being searched on. There are a few ways to properly use indexes, but one thing is for sure — without them, your DB and your app performance is going to suffer greatly.

  7. De-normalize your DB structure, better yet use an appropriate DB system
  8. Speaking of JOIN’s, no matter how you slice it… they are costly for the performance. Sometimes it helps to de-normalize your DB structure in order to avoid such expensive operations. An example would be a users table and user_profiles table. I love to keep minimal information in the users table, but if I find that I keep JOIN’ing user_profiles to get additional information, it is probably time to consider de-normalizing data and move whatever piece of info I need into the users table to avoid an extra JOIN. (Counter cache is another prime example of this).
    That being said, if you find yourself de-normalizing your DB quite heavily, perhaps it is time to consider an alternative to RDBMS. Of course, I am going to suggest MongoDB. Where applicable, it is quite alright to use a mixture of DB systems… always use the right tool for the right job (simple, but powerful statement).

  9. Create read/write DB replicas
  10. Do evaluate your read and write queries. Do you have a ton of admin features, which require complex find() operations? Offload them to a read replica, so that your users (or front-end) write queries do not get in the way. Increasing performance through replication is a nice trick, but you should be cautious not to offload mission-critical data to a replica, because the data might be a little behind as compared to your master server.

  11. Offload heavy tasks to a background process
  12. I do hear this pretty often… “I need to export data to an Excel, and it is taking forever”. This is a perfect example of a job that can be offloaded to a background operation. There is simply no need for a user to sit in-front of the screen and wait for X minutes for the Excel file to build and download. When a user requests an Excel report, add this task to your job queue manger and notify the user when the job is complete. (Have you looked at gearman?).
    — Well, I need to have this real-time.
    — Sorry, but this is not going to happen. Having a user sit and wait is already not “real-time”, IMO. Not to mention the unnecessary stress this kind of operation will put on your DB. (This kind of requirement is a perfect time to consider replication as well).

  13. Use AJAX, when needed
  14. The whole point of AJAX was to minimize the number of heavy requests to the server. Imagine you have an e-commerce site, where you have a page of most popular t-shirts (purchased by the users in the last week) cached… for a week. Makes sense, you only rebuild this list once a week, based on the updated data in the DB. For a week, this popular page on your site is served statically, just like good ol’ HTML.
    But there is a gotcha… you have shopping cart info also as part of this page. Well, AJAX to the rescue. While the rest of the page is cached, one little div, which has the shopping cart summary is easily updated by AJAX.
    Granted this is a very simple example, but think about how well this applies to other situations where you need to mix and match static and dynamic content.

A few other things worth looking into: APC, hiphop-php, Yahoo! performance rules.

Overall, I suggest not to get too hung up on trying to squeeze milliseconds out of your PHP code, unless you’ve exhausted all other resources. Spend your time on proper architecture and caching strategies. Avoid premature optimization and do not trust “hello world” benchmarks when measuring something so complex as an entire framework.
Oh, and before you invest hours into setting up various caching and optimization tools, do not forget to run a load and stress tests to help you identify early bottle-necks upfront.

Would love to hear your experience with optimization, caching and improving performance.

Dynamic menus without requestAction() in CakePHP 1.2

You’ve probably heard time and time again (especially in a few recent and popular blog posts) that using requestAction() is, generally speaking, considered to be a “bad practice”, “last resort”, “hackish” way of doing things in cake.

Let’s consider one common use to see if we can achieve our goals without using requestAction()…

We have a Company model and our goal is to build a navigational menu consisting of all the companies in our table. We would create an element called ‘company_nav.ctp’ and somehow load the relevant data from our Company model.
The first, obvious, approach is to use requsestAction() in an element, to get the required data. This, however, is something we would like to avoid.

The approach I offer below relies on cache, or cached model’s data to be precise…

We can agree that our menu or navigation would only change if a company has been added, modified or deleted. Therefore we use our Company model’s callback methods such as afterSave() and afterDelete() to properly cache the required data for the menu.

So, assuming we have a Company model, we can add the following to handle the data caching:

function afterSave() {
   $this->_cacheNav();
}

function afterDelete() {
   $this->_cacheNav();
}

function _cacheNav() {
$companies = $this->find('list');

Cache::config(null, array('engine'=>'File', 'path'=>CACHE));
Cache::write('companies', $companies, array('duration'=>7200, 'config'=>null));
}

The code is pretty simple, but let me explain it just a little…

We rely on afterSave() and afterDelete() to call our custom method _cacheNav(), which handles the actual caching of the Company data.

In this case we only need Company.name and Company.id for our menu, so using find(‘list’) works perfectly well for that.
We then call Cache::config() to ensure that we write to the correct location (the constant CACHE is defined for us by cake core) and that we use the correct storage engine (I use File, but you can use any supported engine). I found that specifying the ‘path’ is a good idea, since Cache can write to different locations depending on the context, therefore it’s best to be explicit about it.

Alright, now that our data is cached, we can easily build our element (company_nav.ctp):

<?php

Cache::config(null, array('engine'=>'File', 'path'=>CACHE));

$companyData = Cache::read('companies');

if(!empty($companyData)) {
   foreach($companyData as $key => $value) {
      echo '<div>'.$html->link($value, array('action'=>'view', $key)).'</div>';
   }
}

?>

I think the above should be pretty much self-explanatory, we are simply reading the data we’ve cached earlier in our model and building a menu of links.
Again, I prefer to specify the ‘path’ to ensure we attempting to read from the correct cache path.

Before we finish up, I wanted to point out a few important issues:

1. The duration of the cache is limited
Of course you can increase it to some unrealistically large value to cache forever, or you could come up with some fall-back mechanism (i.e. the good ol’ requestAction()) if cache data is not available.
At any rate, this is just something to be aware of and it can be easily fine-tuned to fit your specific needs.

2. Breaking of MVC (?)
In theory the view (element) should not access the data directly, and I’m not 100% sure if using Model’s cached data is somehow an exception to this rule. Looking at the benefits, however, I feel that this approach is justified. Cache is accessible to all of our application objects, therefore we could at least say that we are not breaking the rules, but rather bending them slightly.

15 Essential CakePHP Tips

1. Save() does not work!
Sometimes it happens that save() fails without any obvious reason. Your data array looks fine and you’ve build the form correctly, etc., etc., but no query was executed. It is very possible that save had failed due to validation errors. Maybe you are updating some model and while the current fields in the form pass the validation, there is a chance that some “other ones” are causing the validation rules to fail. An easy (and helpful) way to see what’s going on with validation is to do pr($this->validationErrors); in your view. By employing this method you’ll see exactly what’s happening with your model’s validation. The other option is to pass false as a second parameter to save(); in order to disable the validation. However, the latter method does not give much of hint and should not be used to fix a failing problem, but rather to purposely avoid validation.

2. Save() still does not work!
Do you have beforeSave(); in your model or app model? Always double-check for this method, and even more importantly, ensure that it returns true.

3. Validating on create or update
CakePHP has an ‘on’ key to be used in your $validate array. It allows you to specify whether the rule should be enforced during a new record creation or during an update of an existing record. For example, if you only want to check for a unique email address when you are creating a new User account, you’d add ‘on’ => ‘create’ to your $validate array. Therefore, this rule will be ignored when you are updating/editing some user.

4. Mind your cache
You’ve made some changes to your tables, but you app “ignores” them… You’ve moved some things around, bur your app doesn’t seem to “notice” that…
Clear the cache. Delete the files from your app/tmp/cache. Be sure to only delete the files and not the directory structure.
Basically if you notice some strange behavior in your app, just keep in mind that it could be caused by the cache.

5. I’m losing the extra URL parameters when paginating
You need to retain some extra URL parameters during pagination. For example the URL is something like /products/view/45. Yet, when you build the pagination the ID (45) is lost… Well, all you need to do is add this line of code to your view: $paginator->options(array(‘url’ => $this->passedArgs));

Update: this is likely fixed in the recent builds of CakePHP, but still good to be aware of.

6. Using afterFind()
Model::afterFind() allows you to perform some data manipulation after the Model’s find method. Here’s a sample usage in the model:

function afterFind($results, $primary=false) {
  if($primary == true) {
  // do stuff to the $results
  }

  return $results;
}

$primary will be set to true after your find() method is executed and some results are returned. After you modify the $results you will return them back to your app (controller).

7. I need to know the basic information about my table
Try this: pr($this->ModelName->schema())

(Thanks, gwoo, for the hint)

8. How do I check for a non-empty field in CakePHP 1.2?
Update: cake core now has a ‘notEmpty’ rule built-in, so definitely use it instead.

For historic purposes only:
The old VALID_NOT_EMPTY constant is now deprecated and there does not seem to be a rule to replace it… Well, it’s easy enough by using: ‘rule’ => array(‘minLength’, ’1′)

Martin Bavio pointed out that having space characters (only) as your field data will make the validation rule pass and this is probably not a desirable effect. Using this simple regex, instead, will catch an empty string with space characters: ‘rule’ => array(‘custom’, ‘/\S+/’)

9. Avoid using the $uses array
You’ve got two completely unrelated models, but you need info from one in the controller of another. The first idea is to add them to the $uses array. Hey, it’s easy and gets the job done. Well, to make the long story short, it’s bad practice. Think about your model bindings and ensure that models are really not related to one another. Sometimes your User model is definitely not related to CommentRating, but you absolutely need it in your users controller. Well, just by chance it appears that User->Post->Comment->CommentRating. It’s a deep binding that may not be obvious at first, but by employing such a chain of models you can easily avoid using the $uses array, when it’s really not necessary.

Update: if you truly need to load some random (and definitely unrelated) model in your controller, do it like so in the given action:

$this->load(‘MyModel’);
As soon as this line executed you’ll have an instance of MyModel available in $this->MyModel (pr($this->MyModel); to see your freshly loaded object.

Another option is to use this approach:
$MyModel = ClassRegistry::init(‘MyModel’);

Which is nice, because it allows you to do things like this:

$someData = ClassRegistry::init(‘MyModel’)->find(‘all’, array(‘conditions’ => array(‘MyModel.name LIKE’ => ‘% ‘. $name . ‘%’ ))); (the latter, chaining, example is PHP5 only)

10. Clean-up ugly HTML

It’s no secret that CakePHP often outputs some very ugly and hard to read HTML. This little trick will make it a lot cleaner.
Create a file named app_helper.php in your app’s root directory.

Next add this function to it:


function output($string) {
return parent::output($string . “\n”);
}

Take a look at your HTML now… much better.

(Thanks, TommyO, for the hint)

11. How can I access session data in the view?
Very easy. You have the $session helper available.
Just try it: pr($session)
Update: if you have already written some variables to the session, then try pr($session->read());

12. I need to save data from multiple models
Don’t even think about using loops and other trickery. There is a very nice (but not well documented) method saveAll() available. It will let you save data from multiple models at once. Granted, it takes a little time to figure it out, but once you get it working it is really a time saver. Try using it with some dummy data, and make sure that your data array is properly formatted and associations are setup correctly.

13. Static pages and routes
I really don’t like having /pages/ as part of the URL for my static pages. Well, they are static… so let’s make them .html instead (at the same time we sprinkle just a little of security by obscurity mantra).
If you had links pointing to www.example.com/pages/myPage/ they should now point to www.example.com/myPage.html and add this to your routes:

Router::connect(‘/(.*).html’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’));

Sweet.

14. The fastest way to build a form in CakePHP


echo $form->create();
echo $form->inputs();
echo $form->end();

It is just a step beyond scaffolding, but really this is how easy it is to build a form in CakePHP. Just give it a go.

15. Get to know CakePHP by using Bug tracker
Update: recently CakePHP has switched to another system from Trac.

CakePHP is constantly evolving and if you are serious about developing on top of this framework it is very important to keep up with the latest and greatest and to get familiar with some of the new and upcoming features. code.cakephp.org is a great place for this:

a. You can download nightly builds from here.
b. Keep an eye on the Wiki to see current status of CakePHP project and helpful hints.
c. Use Timeline to see the latest code updates. Pick “commits” to filter the updates.
d. Take a look at the code of test files to see how a certain feature should be used
e. Submit bugs and requests for enhancements, but read this first: http://book.cakephp.org/view/759/Bugreport