Friday, December 20, 2013

Random Characters in NodeJS

Quick example on how to generate random characters in NodeJS
var crypto = require('crypto');
var randomChars = crypto.randomBytes(50).toString('base64');
That makes a synchronous call to randomBytes, so keep that in mind. If you want asynchronous then pass a second param to randomBytes:
var crypto = require('crypto');
crypto.randomBytes(70, function(err, buf){
 console.log(buf.toString('base64'));
});
more info can be found here

Friday, September 13, 2013

Recently (version 3.6) we moved all JS files to the end of <body>, this improves performance but there are some cases where you as a developer need to put JS files in <head>, in 3.7 we are introducing a way to do this, from a controller you can now do this:

<?php
$this->template()->setHeader('head', array(
    'somefile.js' => 'static_script',
    'otherfile.js' => 'style_script'
));
And it will be loaded in the head. Notice that the difference is the first param when calling ->setHeader.

Monday, March 4, 2013

Optimizing Phpfox - Tip #2

The feed seems to be the most resource hungry feature so far. If Timeline is enabled , when going to a profile the script needs to find which years have content so it can display the Timeline years block; this can span up to 20 queries to the database in some cases and while it makes proper use of indexes and conditions this is a load we can save in 2 ways:

1) Disable Timeline
2) Disable the Time block, this is perhaps the most feasible option, if you have timeline enabled it must be for a reason. To disable this specific block go to AdminCP -> CMS -> Block Manager, then click on profile.index and disable "Feed Timeline":



 you will keep the timeline look but the year selector wont be there


With this small change your site will be using a lot less resources and it will contribute to keeping it more stable and efficient.

Note: In case you are wondering, the queries that come from this block do in fact get cached, one cache file per user, but the cache file (specific to a user) is deleted after that user posts something that creates a feed.

Friday, March 1, 2013

Optimizing Phpfox - Tip #1

Lately I've been running into little "tricks" that can help a phpfox site perform better, I will try to add them here as I find them.
Today I have perhaps the most important one that I have seen so far, it came after debugging this database query:

explain extended SELECT feed.*, f.friend_id AS is_friend, 
apps.app_title,  u.user_id, u.profile_page_id, 
u.server_id AS user_server_id, u.user_name, u.full_name, 
u.gender, u.user_image, u.is_invisible, u.user_group_id, 
u.language_id

FROM phpfox_feed AS feed
JOIN phpfox_user AS u
        ON(u.user_id = feed.user_id)
LEFT JOIN phpfox_friend AS f
        ON(f.user_id = feed.user_id 
AND f.friend_user_id = 1)
LEFT JOIN phpfox_app AS apps

        ON(apps.app_id = feed.app_id)


WHERE feed.time_stamp > '0' AND feed.feed_reference = 0
GROUP BY feed.feed_id
ORDER BY feed.time_update DESC
LIMIT 10
Why is it such a naughty query? well the feed table is the main table here, but the only two conditions to filter it are the time_stamp and the feed_reference. Most of the time feed_reference will be 0 so this isn't a great filter, and time_stamp > 0 is trivial, in fact ignored by MySQL. So, many times this will run through the entire feed table, which is very likely to host hundreds of thousands of records.

Luckily we have a setting in the AdminCP that can help with this, "Feed Limit (Days)", this tiny little setting will help you greatly to improve performance. What it does is to limit the feeds to a number of days in the past. One way to find a good value for this is to do this check, add a photo to the feed or something that you can easily identify, come tomorrow and look for that feed, if you cannot find it in the first page (before the ajax load ) then you think that 1 day is enough, but for safety check the next page of feeds, if you cannot find it in the 3d page then my advise is to set this to 3 so it looks a maximum of 3 days in the past.

Hope it helps

Friday, February 8, 2013

Optimization of Phpfox: MySQL

Today  Yesterday I landed in a bug report that mentioned full table scans, in my younger years (ok, 5 years ago) I was very interested in databases and even took a workshop in Mexico by MySQL to become a DBA so I dutifully began testing.

The first thing I did was to set up a local copy of Phpfox 5.3.0 RC1 (not yet released) with content (users, blogs, friends,...), then enabled the slow_query_log and set the long_query_time to 2, because this is a local server with no traffic I assumed a safe bet that no query would take longer than 2 seconds. Then I enabled log-queries-not-using-indexes to capture those in the slow query log. Then using the script I was able to find queries that could be optimized (theoretically at least).

There were indeed some queries using union and joins that could be optimized (large sites will likely see a performance increase in 5.3.0 RC1).

I do have to point out a couple of things:
First, some queries are being logged but they are meant to scan the full table, for example when getting all the user groups we want all the records in that table, using indexes would not improve performance because we want everything in that table, intentionally this table is typically very small, it defaults to 5 records and in normal circumstances shouldn't grow beyond 10 rows.
Second, In some cases, MySQL seemed to not want to use a specific index, for example with this query:

SELECT COUNT(*)
FROM phpfox_user AS u
JOIN phpfox_user_field AS ufield
ON (ufield.user_id = u.user_id)
WHERE u.status_id = 0 AND u.view_id = 0
If you run that query with explain you will see that MySQL had many indexes to use but chose none, and in my test it did run a full table scan. So to help in this situation we implemented the function forceIndex() in the DBA library, this allowed us to tell MySQL which index to use and after rewriting the query we saved on fetching rows, (in the one that I have just rewritten in the feed there is a filtering improvement of 45.31%)  which translates in a performance improvement.

Third, we made sure that whenever the developer queries for getSlaveField or getField the database library adds a LIMIT 1 if it was not added by the developer, we saw a tiny tiny performance after this small change.

Fourth, I also found some strange behavior in mysql where it would log a query from a 'derived' table and not use any indexes for that sub-query, but taking the sub-query out and testing it by itself did use the index, in this case force index did not help, for what is worth, this only happened in the main Blog section when logged in as an administrator.

Fifth, in one occasion after optimizing the query (meaning it did not get logged in the slow query log) it took longer for mysql to fetch the results, we opted for 'un-optimizing' the query since the tangible benefits outweighed the theoretical ones.

The improvements added affect sections frequently reached like the Browse Members and Home (after logging in, where the news feed is).

For developers: here is sample code using the forceIndex function:

$iCnt = $this->database()
        ->from($this->_sTable, 'u')
        ->forceIndex('status_id')
        ->join(Phpfox::getT('user_field'), 'ufield', 'ufield.user_id = u.user_id')
        ->where($this->_aConditions)
        ->execute('getSlaveField');
There surely are queries still to fix, if you find them please let us know, we will continue looking for them but must also attend to other bug reports.

Monday, January 21, 2013

Tip: Time in JS

I recently forgot one of the (implicit) primers in JS, once you instantiate a Date() object you will continue getting the time from when you instantiated it, for example:
// We instantiate the Date object
// at this time it has a date that will never change
var oDate = new Date();
console.log( oDate.getTime() );
setTimeout( function(){
    console.log( oDate.getTime() );
}, 5000);
It will output the same value in line 4 and 6. I just mention this in case I myself run into this problem again. one easy easy fix is to instantiate it on the fly:
var oDate = new Date();
console.log( oDate.getTime() );
setTimeout( function(){
    console.log( new Date().getTime() );
}, 5000);