Speeding Up Auto-completes
With the advent of Google search auto-complete, it seems like auto-complete should be everywhere.
Any site that has masses of information that is exposed through various search widgets has considered auto-complete in various stages of product design and development. In light of this, Drupal has allowed for quick auto-completion inclusion for over 6 years. In Drupal 7 it looks a bit like this (search for full guides for complete walkthroughs).
This example is filling in city, state combos in a search field.
/** Implementation of hook_menu **/
function example_menu() {
$items['city_autocomplete'] = array(
'page callback' => 'autocomplete_city',
'access arguments' => array('access content'),
);
return items;
}function autocomplete_city($string){
$string = array(':s' => $string . '%');
$result = db_query('SELECT DISTINCT city, province FROM {location} WHERE city LIKE :s order by city limit 20', $string);$items = array();
foreach ($result as $location) {
$items[$location->city .', '.$location->province;] = $location->city .', '.$location->province;
}
print drupal_json_encode($items);
}/** Some form alter, or form generation function **/
...$form['search_string']['#autocomplete_path'] = 'city_autocomplete';
$form['search_string']['#attributes']['class'][] = 'form-autocomplete';
...
After this, the field now will issue auto-complete suggestions as the user types.
Not Fast Enough
BUT, our business people were down on the speed that the results came back. They wanted it to go faster. So I decided strip out as much of the Drupal bootstrap as possible, and then bypass completely the index.php loading of the normal Drupal framework.
The goal was to do this while still being able to use the #autocomplete_path form enhancement, no need reinvent everything. To do this, we actually have to create a new base script and point the #autocomplete_path paths to that script. It was easiest to do this by placing the scripts in the same root directory as the regular Drupal index.php script is found.
Let call it city_autocomplete.php. Below is the adjusted module code.
Note that the hook_menu item has the '.php' in it. It still works as that is a valid Drupal menu name.
/** Implementation of hook_menu **/
function example_menu() {
$items['city_autocomplete.php'] = array(
'page callback' => 'autocomplete_city',
'access arguments' => array('access content'),
);
return items;
}//Technically not needed anymore, but leaving in for posterity.
function autocomplete_city($string){
$string = array(':s' => $string . '%');
$result = db_query('SELECT DISTINCT city, province FROM {location} WHERE city LIKE :s order by city limit 20', $string);$items = array();
foreach ($result as $location) {
$items[$location->city .', '.$location->province;] = $location->city .', '.$location->province;
}
print drupal_json_encode($items);
}/** Some form alter, or form generation function **/
...$form['search_string']['#autocomplete_path'] = 'city_autocomplete.php';
$form['search_string']['#attributes']['class'][] = 'form-autocomplete';
...
Now, inside the new city_autocomplete.php file. Much of this is taken directly from the index.php file, but we restrict the drupal bootstrap to only the database, and we include one extra file, common.inc which has the drupal_json_encode() function.
<?php
define('DRUPAL_ROOT', getcwd());
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
require_once DRUPAL_ROOT . '/includes/common.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);// arg(1) is used to gather the data sent to the auto complete script
$string = array(':s' => arg(1) . '%');// The rest is just the body of the autocomplete_city($string) function
$result = db_query('SELECT DISTINCT city, state FROM {zipcodes} WHERE city LIKE :s order by city limit 20', $string);$items = array();
foreach ($result as $location) {
//$items[$location->city] = $location->city .', '.$location->province;
$items[$location->city .', '.$location->state] = $location->city .', '.$location->state;
}
print drupal_json_encode($items);
Clear your cache to reload the menu items, and revisit whatever page had your auto-complete attached to it. That is all.
Benchmarking (all times from Firebug Console)
I wouldn't call this a strenuous set of benchmarking, but does give the ballpark opportunities for speed improvements.
Local system (vanilla MAMP setup, no code caching):
Regular Drupal auto complete: 250-350ms
Limited Bootstrap auto complete: 35-55ms
Dramatic improvement on a vanilla Apache setup, no code caching.
Remote Pantheon System (Pantheon DROPS, http://helpdesk.getpantheon.com/customer/portal/articles/361254):
Regular Drupal auto complete: 150-250ms
Limited Bootstrap auto complete: 80-150ms
First take-away, the regular auto-complete was faster under a remote Pantheon server, than my vanilla localhost. Obviously using a code cache is a great performance booster for the regular code execution.
Second take-away, this method does improve performance speeds by around 20-40%, even under great code conditions.
Bonus tip
You can decrease the delay time between the last key stroke and when the search starts. It defaults to 300ms (essentially doubling the times to display results). It is found in the /misc/autocomplete.js file
...
Drupal.ACDB = function (uri) {
this.uri = uri;
this.delay = 300;
this.cache = {};
};
...
Change that delay to something less to make all auto-completes start faster. If you don't want to do a core hack, then you need to remake all the Drupal.ACDB JS found in the /misc/autocomplete.js in your own theme or module JS, and make the adjustment there.