Cakephp cache only caching one file per action - cakephp

I have a songs controller. Within the songs controller i have a 'view' action which get's passed an id, eg
/songs/view/1
/songs/view/5
/songs/view/500
When a user visits /songs/view/1, the file is cached correctly and saved as 'songs_view_1.php'
Now for the problem, when a user hit's a different song, eg /songs/view/2, the 'songs_view_1.php' is deleted and '/songs/view/2.php' is in it's place.
The cahced files will stay there for a day if I don't visit a different url, and visiting a different action will not affect any other action's cached file.
I've tried replacing my 'cake' folder (from 1.2 to 1.2.6), but that didn't do anything. I get no error messages at all and nothing in the logs.
Here's my code, I've tried umpteen variations all ending up with the same problem.
var $helpers = array('Cache');
var $cacheAction = array(
'view/' => '+1 day'
);
Any ideas?
EDIT:
After some more testing, this code
var $cacheAction = array(
'view/1' => "1 day",
'view/2' => "1 day"
);
will cache 'view/1' or 'view/2', but delete the previous page as before. If I visit '/view/3' it will delete the cached page from before... sigh
EDIT:
Having the same issue on another server with same code...

After working hours on this, I finally figure out the reason why the cache keep being deleted, the REASON is because you had some operations that update your 'song' record in the database after you view the 'song'. For my case, I keep a column in my database called 'Hits' to store the number of hits/reads, and it updates it everytime it read the record.
Cakephp has a feature to aumotically detect changes to your database and clear the cache for you.
Try remove any operations that update your 'song' record and the cacheaction should be working properly.
An alternative is to redefine the clearcache function in your 'song' model... it will disable the function to auto-clear off the cache.. but then remember to manually clear the cache yourself when an update is performed.
function _clearCache($type = null) {
}

After working hours on this, I finally figured out the reason why the cache keeps on being deleted. The reason is because you had some operations that update your 'song' record in the database after you view the 'song'. For my case, I keep a column in my database called 'Hits' to store the number of hits/reads, and it updates it everytime it read the record.
Cakephp has a feature to automatically detect changes to your database and clear the cache for you.
Try remove any operations that update your 'song' record and the cacheaction should work properly.
After fixing it, there will be another issue. Let's say you cache many of your records, for example song/1, song/5, song/100...etc, if there is any update for any 1 of the record... all of the caches for song/1, song/5, song/100 will be deleted. This makes cacheaction useless for frequently update website.
The solution to this is to redefine the clearcache function in your 'song' model... it will disable the function to auto-clear off the cache.. so if there is any update, none of the caches will be deleted. But then remember to manually clear the cache yourself when an update is performed.
function _clearCache($type = null) {
}
to remove cache manually, you could use
#unlink(CACHE.'views'.DS.'website_songs_view_50.php');

I think that kind of caching method is depreceted. Perhaps you should use Cache:
$song = Cache::read('songs/view/'.$id, 'cache_time');
if(empty($song)){
$song = $this->Song->findById($id);
Cache::write('songs/view/'.$id, $song, 'cache_time');
}
cache_time is a variable you define in core.php:
Cache::config('cache_time', array('engine' => 'File', 'duration' => 60*60*24));
Hope it helps.

Check some setting in the config.php file. Do you have the following setting enabled?
Configure::write('debug', 0);
//Configure::write('Cache.disable', true);
Configure::write('Cache.check', true);
Cache::config('default', array('engine' => 'File'));

Related

Saving multiple records appropriately cakePHP way

I have an event form which contains information related to event like: start_time, finish_time etc.
Every event bolongs to a user. The requirements are that the same type of event can be saved for multiple users, so from the form I receive a list of users as well.
Now that makes it impossible to use model's native save function and for that reason I have the following code:
function saveNotRepeatedEvents($event, $users){
foreach($users as $user){
$event['Event']['user_id'] = $user;
$this->create();
$this->save($event);
}
return true;
}
First of all, it does not allow for transactions, and I have a feeling that it can be done better, like "cakePHP way", but just cannot find anything during the research.
So the main issue would be how to intruduce transactions in this case. But any help or guidance on how to make it more cakePHP way would be much appreciated.
What you're looking for is Model::saveAll($yourData), which at the end will make use of a transaction (as long as your database engine supports transactions).
The main idea is that you will prepare $yourData as an array with this form
$yourData = array(
array('Event' => array('user_id' => 1)),
array('Event' => array('user_id' => 2)),
);
Of course, you will do this in your for loop, I am just giving you the main idea.

CakePHP: Why is my cached file causing huge spikes when it expires?

I'm using cake 2.1.3 and currently have a page that is getting hundreds of views per second and so I have utelized caching in order to handle the load better. The problem is, that once the cache expires, I get a spike in my server resources as well as hundreds of mysql connections.
I'm wondering if I'm going about this the wrong way and if I should be running a cron to cache the page instead of how I'm currently doing it or if there's another technique I'm not thinking of.
here's what my function looks like in my controller:
public function index() {
$this->layout = 'ajax';
if (isset($this->params['url']['callback'])) {
$callback = $this->params['url']['callback'];
}else{
$callback = 'callback';
}
$this->set('callback',$callback);
$today = date("Y-m-d");
$end_date = strtotime ('+1 day' , strtotime($today)) ;
$end_date = date ( 'Y-m-d' , $end_date);
$start_date = strtotime ('-1 day' , strtotime($today)) ;
$start_date = date ( 'Y-m-d' , $start_date);
$total = Cache::read('popular_stories', 'short');
if (!$total) {
$total = $this->TrackStoryView->find('all', array(
'fields' => array('COUNT(story_id) AS theCount', 'headline', 'url'),
'conditions' => array('date BETWEEN ? AND ?' => array($start_date,$end_date)),
'group' => 'story_id',
'order' => array('theCount DESC'),
'limit' => 20,
));
Cache::write('popular_stories', $total, 'short');
}
$this->set('story', $total);
}
Here's what my Cache config looks like in my bootstrap.php file:
Cache::config('short', array(
'engine' => 'File',
'duration' => '+60 minutes',
'path' => CACHE,
'prefix' => 'cake_short_'
));
This is what's in my view file:
<?php
echo $callback . '('.json_encode($story).')';
?>
I was hoping that once the cached file expires, as soon as the first person accessed it, it would craete a new cached file and serve that up for everyone, however because hundreds of people are hitting it per second, it seems like this method isn't working for me and that maybe I should be caching the view view a cron somehow instead or maybe there's a different way to cache that I'm not utelizing.
It sounds like you have the answer more or less figured out (create the cache automatically, not triggered by a user request).
To do this, look into cake's AppShell class, book talks about it here. You can then link this to a cron job. If you create the file thru Cache::write, cake should be aware that it is a new cache file and read it transparently. You might want to leave the "if cache not found" block in there just in case your cronjob fails.
Shells & Tasks in cake are fun and allow you to free your application from using the request/response model exclusively.
TLDR: It's not ideal to force a user to break the cache for you. Use a chron job or a trigger on data change.
Explanation:
"hundreds of views per second" is the problem. When it expires, there are "hundreds of views" during the time it's trying to create the cache file.
The first person hits it, it starts creating the cache, and in the meantime, another hundred+ people hit it, and it looks, and can't yet find a cache file...etc.
If you can manage, try to manually create the cache when an item(s) is updated, or run a chron job that creates a new cache every X minutes as opposed to having it create for a user.
Cake has lots of cool triggers like afterSave() that you can use to trigger this kind of thing. If that doesn't make sense in your case though, a chron job should be fine for you.
I think the answer lies by working out how long this query takes:
$total = $this->TrackStoryView->find('all', array(
'fields' => array('COUNT(story_id) AS theCount', 'headline', 'url'),
'conditions' => array('date BETWEEN ? AND ?' => array($start_date,$end_date)),
'group' => 'story_id',
'order' => array('theCount DESC'),
'limit' => 20,
));
Lets say it takes 500ms.
You are getting 100 hits a second, so when the cache clears the first request makes the find call and then 50 other people also make the find call before the first request completes.
One alternative solution:
Make the cached content never expire. Set up a cron task that overwrites the cache by calling a different action which runs:
Cache::write('popular_stories', $total, 'short');
To overwrite the cached content.
This way, the 100s of users per second will ALWAYS read from cache

How use caching to improve the performance of a classified website?

I have built classified website using Yii php framework. Now it is getting a lot of traffic. So I want to using caching to optimize the performance of the website.
There are two controllers I want to optimize.
One is the thread list controller: (example) http://www.shichengbbs.com/category/view/id/15
The other one the the thread controller: (example) http://www.shichengbbs.com/info/view/id/67900
What I have done:
the thread list is cached for 3mins.(The other option is update the thread list only when new thread comes)
set the last-modified time HTTP header for the thread view. (expire time is not set, as some user complain that the page appears unchanged after editing)
Partial caching the categories navigation fragment.(It appears on the left side of every page)
Use htaccess to set expire header for img/html/css/js.
Considered database sql caching for the thread list, but not done. As I thought it is the same as 1.
What else can I do to improve the website performance?
I assume you have done the Performance Tuning guide point 1 and 3. It's really helpful.
For number 2 you can use the CHttpCacheFilter
class CategoryController extends Controller {
private $_categoryLastUpdate;
public function filters(){
return array(
array(
'CHttpCacheFilter + view',
'cacheControl' => " max-age=604800, must-revalidate",
'etagSeedExpression' => function() {
return $this->getCategoryLastUpdate();
}
'lastModifiedExpression' => function() {
return $this->getCategoryLastUpdate();
}
)
)
}
public function actionView($id){
$object = Category::model()->findByPk($_GET['id']);
$this->render('view', array('object' => $object));
}
public function getCategoryLastUpdate(){
if (!isset($this->_categoryLastUpdate)){
$obj = Category::model()->findByPk($_GET['id'], array('select' => 'lastUpdate'));
$this->_categoryLastUpdate
}
return $this->_categoryLastUpdate;
}
}
It basically will calculate the ETag and LastUpdate by the category. And to save the query, it will first only calculate the lastUpdate of the Category object.
And for number one, you can always use the CCacheDependency. Just make a field in the thread list object, e.g. lastUpdate. And when a new thread submitted, just update the field and use it for the CCacheDependency.
Since I see you are using a very large pagination, I think you want to read about Four Ways to Optimize Paginated Displays (if you use MySQL for your database and thread search/list).
Try using a Cache Manager with Memcache or APC. For example, http://code.google.com/p/memcache-flag/ . When you edit the list, then you can invalidate the cache item or tag. I suppose it could also just be done with regular APC / Memcache functions if you design is simple (set a key and delete it when it is no longer valid).
Use this to store serialized (or automatically serialized) data instead of retrieving it from mysql.

CakePHP user session not updating but database yes

I'm developing with cakePhP and I have the following problem:
When a user logs in with his name and password to the account system that I've created, he can save items (images) as favorites. This is saved in a text field into the database. What is saved is the image ID.
The saving process works perfectly, the user clicks on the images and they're added to that field (it actually saves all the IDs as a text array that I process later).
The problem comes when removing images. When the user does it (I'll post the code below), the images is removed correctly from the database (I go to PHP MyAdmin and I see it). This means that the array that holds the favorite images IDs is updated instantly. However, when I reload that array from the website, it hasn't been updated. It's like it's stored in the caché or something. Then, if the user logs out and logs in again, then he can see the correct one. The thing is that I have other things in my website that work in a similar way and they all get updated instantly, so I can't see why this doesn't.
This is the code that I use to remove the ID from the database:
function remove_favorite($pictureID) {
$this->User->id = $this->Auth->User('id'); //We get the ID of the current user
$favoritesArray = $this->User->deleteFavoritePicture($this->User->id, $pictureID); //This function retrieves the array (string) of pictures from the user's table, and deletes all the images with the ID passed as parameter, returning the updated array (string)
$fields = array('images_favorites' => $favoritesArray, 'modified' => true); //We indicate the field that we're going to update in the users table
//We save the new string that doesn't contain the deleted image anymore
if($this->User->save($fields, false, array('images_favorites'))) {
$this->Session->setFlash(__('The image has been removed from your favorites', true));
} else {
$this->Session->setFlash(__('Error removing image from favorites, please try again', true));
}
$this->redirect(array('action' => 'manage_favorites',$this->User->id));
}
This is how the deleteFavoritePicture function looks like:
function deleteFavoritePicture($userID, $pictureID) {
$userInfo = $this->find("id = $userID");
$favoritePicturesString = $userInfo['User']['images_favorites'];
$favoritePicturesArray = explode(",", $favoritePicturesString); //Array
$i = 0;
while ($i < count($favoritePicturesArray)) {
//We remove from the array the images which ID is the one we receive to delete
if ($favoritePicturesArray[$i] == $pictureID) unset($favoritePicturesArray[$i]);
$i++;
}
$favoritePicturesString = implode(",", $favoritePicturesArray); //String
return ($favoritePicturesString);
}
That's it. Does anyone now what can be going on? Thanks so much in advance for any clue!
EDIT
Ok, I think I found something that may give a clue of what's going on here:
This is the code for the manage_favorites action:
function manage_favorites($id) {
//$user = $this->User->find("id = $id");
$user = $this->Auth->user();
$this->set('user', $user);
}
That is the action that is called for the page when a user wants to modify his favorites. The same action is called once the user removes a favorite. Here's the thing:
If I use the $id parameter in the manage_favorites function and the $user = $this->User->find("id = $id"); line (the one quoted now), then the problem does not exist! This is how I used to have it. HOWEVER, I had to change it because it was a big security flaw, since the user id ($id) was a visible parameter who anyone could change, and then access other users accounts. What I did was changing the way I obtain the user array of favorite images, using the following line: $user = $this->Auth->user();. This is how I have it now (well, and also without the $id parameter in the function header), so the user information (including the favorites array) comes from the Auth component, instead directly from the database.
So, the problem is clear: when the user deletes a favorite, it's doing it on the array in the database. WHen I show the result of that operation, the array I'm retrieving is not the one in the DB, it's the one in the session. That's why it's not showing the changes.
How can I avoid this without using a non-secure method like the one I had before?
When you save, the array passed to the save method of the model should look like this:
[User] => array(
[field] => value,
[field2] => value2,
...
)
In your example, you clearly haven't added the [User] key.
Also, is your modified field actually the default Cake modified field? That is, the DATETIME field which changes to the current time when the row is updated?
Lastly, maybe you have debugging set to 2 in config.php. try changing this to 0 (as in production) and see if caching persists.
Hope some of the points I have mentioned above will solve your problem. Please let me know!
There could be two things wrong with this.
What does your deleteFavoritePicture method look like? There could be something being done wrong there.
You're passing false as the second parameter to the User::save method, which means that you don't want to validate. Unless there is a SQL error, then this will return true even if it doesn't validate properly, I believe. Try changing this false to true and see if your results differ.

Full view caching for dynamic routes in cakephp

I am having some issues with full view caching in cakephp.
The url that I would like to cache is /posts/badge/23/size:180x150 I have been able to cache the view successfully by adding $cacheAction = "1 hour"; to the controller.
Since I do not want to cache all methods in the controller (just specific methods) I tried to use the array syntax to match the url /posts/badge/23/size:180x150
where 23 is the post ID and size is a named parameter (there are 2
variations of size).
So my question is what is the proper (if any) match to place in
cacheAction to cache all posts/badges/* Here are some examples that I
have tried:
var $cacheAction = array(
'badge/*' => '+1 hour',
'posts/badge/23/size:180x150' => '1 hour',
'badge/23/size:180x150' => '1 hour',
'posts/badge/:id/:size' => '1 hour',
'badge/:id/:size' => '1 hour',
);
None of these seem to match (or at least do not cache for some
reason). I do need to cache posts/badge//size: and trap each of the
parameters being passed.
Again, if I set $cacheAction = "1 hour"; then the cache file is
created (the file tmp/cache/views/posts_badge_23_size_180x150.php is
created)
Has anyone been able to create a cache for a dynamic url?
CakePHP's CacheHelper currently isn't capable of Routes. CacheHelper::cache(), which is responsible for parsing and checking the $cacheAction settings does not use any Router functionality, it just does some string checking.
You can use your own CacheHelper, just modify the cache() method appropriately and place the file cache.php in app/views/helpers.
This ticket is similiar to your problem, the solution posted there may help you: trac.cakephp.org/ticket/6192
On this page: Caching in the Controller it says:
Remember to use your routes in the $cacheAction if you have any.
So try creating some routes that match your URL structure and using them for cacheAction keys. Please report back if it works.

Resources