Skip to content

Commit

Permalink
Issue 233: speed up quicklove.
Browse files Browse the repository at this point in the history
Creates a planlove table, which is auto-updated on plan changes.  I'm still working on the front-end change to search.php to get it to actually use the table.

As mentioned in Issue 138, quicklove runs a slow query - the MySQL fulltext index on edit_text isn't used because MySQL can't search for square brackets in fulltext indices. By keeping a cache of who planloves who, the search page can be greatly sped up. (For a rough estimate - obtained using php's microtime - 97.5% of quicklove's run time is spent waiting for the DB query to complete)
  • Loading branch information
acohn committed Apr 4, 2012
1 parent 71ccbad commit 3c859fa
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 3 deletions.
25 changes: 25 additions & 0 deletions db/load_planlove.php
@@ -0,0 +1,25 @@
<?php
// Script to initially load the planlove table, in batches so the server doesn't run out of RAM.
// Run it like this: $ for i in {0..75} ; do echo $i; php db/load_quicklove.php $i; done

if (PHP_SAPI != 'cli') die();

require_once ('bootstrap.php');
if ($argc == 2 || $argc == 3) {
$page = $argv[1];
if ($argc = 2) {
$batchsize = 100;
} else {
$batchsize = $argv[2];
}
$plans = Doctrine_Query::create()->select('a.*, p.*')->from('Accounts a')->leftJoin('a.Plan p')
->limit($batchsize)->offset($page * $batchsize)->orderBy('a.userid asc')->execute();
foreach($plans as $plan) {
set_time_limit(20);
if ($plan->Plan) {
//A trivial change, enough to force Doctrine to update itself.
$plan->Plan->edit_text = $plan->Plan->edit_text;
$plan->Plan->save();
}//if the user has a Plan. Not all do. (!)
} //foreach plan
}
58 changes: 58 additions & 0 deletions db/migrations/6_planlove_table.php
@@ -0,0 +1,58 @@
<?php
class PlanloveTable extends Doctrine_Migration_Base {
public function migrate($direction) {
$columns = array(
'lover_id' => array(
'type' => 'integer',
'length' => 3,
'unsigned' => 1,
'notnull' => 1,
'default' => 0
),

'lovee_id' => array(
'type' => 'integer',
'length' => 3,
'unsigned' => 1,
'notnull' => 1,
'default' => 0
)
);
$options = array(
'type' => 'MyISAM',
'indexes' => array(
'unique' => array('fields' => array('lover_id', 'lovee_id'), 'type' => 'unique'),
'lover' => array('fields' => array('lover_id')),
'lovee' => array('fields' => array('lovee_id'))
)
);
$this->table($direction,'planlove',$columns, $options);

}
/*
* This function is disabled, because Doctrine provides no way to run it in batches and no way to free
* RAM associated with query sets (other than the usual end-of-script free).
* In testing this migration on a copy of the production database, the migration was repeatedly terminated
* before it could complete because my dev server ran out of RAM.
* Therefore, to load the planlove table initially, run batches in separate PHP processes.
* A script to do so is available at /trunk/db/load_planlove.php. It accepts two args: the batch size and the page to load
public function postUp() {
//Hit each plan, allowing Doctrine to update the tables for us
//Do them in batches of 100, so we don't run out of RAM.
gc_enable();
$batchsize = 100;
$plans = true;
//Yay for abusing for loops.
for ($page = 0; $plans; $page++) {
$plans = Doctrine_Query::create()->select('a.*, p.*')->from('Accounts a')->leftJoin('a.Plan p')->limit($batchsize)->offset($page * $batchsize)->orderBy('a.userid asc')->execute();
foreach($plans as $plan) {
set_time_limit(20);
if ($plan->Plan) {
$plan->Plan->edit_text = $plan->Plan->edit_text;
$plan->Plan->save();
}//if the user has a Plan. Not all do. (!)
}//for each plan in the batch
gc_collect_cycles();
}//for each batch
} */
}
20 changes: 20 additions & 0 deletions db/models/Planlove.php
@@ -0,0 +1,20 @@
<?php

/**
* Planlove
*
* This class has been auto-generated by the Doctrine ORM Framework
*
* @package ##PACKAGE##
* @subpackage ##SUBPACKAGE##
* @author ##NAME## <##EMAIL##>
* @version SVN: $Id: Builder.php 5845 2009-06-09 07:36:57Z jwage $
*/
class Planlove extends BasePlanlove
{
public function setUp() {
parent::setUp();
$this->hasOne('Accounts as Lover', array('local' => 'lover_id', 'foreign' => 'userid'));
$this->hasOne('Accounts as Lovee', array('local' => 'lovee_id', 'foreign' => 'userid'));
}
}
18 changes: 17 additions & 1 deletion db/models/Plans.php
Expand Up @@ -9,6 +9,7 @@ class Plans extends BasePlans
public function setUp()
{
$this->hasMutator('edit_text', 'processText');
$this->hasOne('Accounts as Account', array('local' => 'user_id', 'foreign' => 'userid'));
}

public function save(Doctrine_Connection $conn = null)
Expand All @@ -31,8 +32,23 @@ public function save(Doctrine_Connection $conn = null)
public function processText($text) {
$text = $this->processDates($text);
$this->_set('edit_text', $text);
$html_text = cleanText($text);
$planloves = array();
$html_text = cleanText($text, $planloves);
$this->_set('plan', $html_text);
Doctrine_Query::create()->delete('Planlove p')->where('p.lover_id = ?', $this->user_id)->execute();
// cleanText will return multiple copies of a username if different capitalizations are used
// on a plan. Make sure we don't try to add a row twice.
$done = array();
foreach ($planloves as $planlove) {
$lovee = Doctrine_Query::create()->select('a.userid')->from('Accounts a')->where('a.username = ?', $planlove)->fetchOne();
if (!in_array($lovee->userid,$done)) {
$love = new Planlove();
$love->Lover = $this->Account;
$love->Lovee = $lovee;
$love->save();
$done[] = $lovee->userid;
}
}
}

protected function processDates($text) {
Expand Down
37 changes: 37 additions & 0 deletions db/models/generated/BasePlanlove.php
@@ -0,0 +1,37 @@
<?php

/**
* BasePlanlove
*
* This class has been auto-generated by the Doctrine ORM Framework
*
* @property integer $lover
* @property integer $lovee
*
* @package ##PACKAGE##
* @subpackage ##SUBPACKAGE##
* @author ##NAME## <##EMAIL##>
* @version SVN: $Id: Builder.php 5845 2009-06-09 07:36:57Z jwage $
*/
abstract class BasePlanlove extends Doctrine_Record
{
public function setTableDefinition()
{
$this->setTableName('planlove');
$this->hasColumn('lover_id', 'integer', 3, array(
'type' => 'integer',
'length' => 3,
'unsigned' => 1,
'primary' => true,
'autoincrement' => false,
));
$this->hasColumn('lovee_id', 'integer', 3, array(
'type' => 'integer',
'length' => 3,
'unsigned' => 1,
'primary' => true,
'autoincrement' => false,
));
}

}
27 changes: 26 additions & 1 deletion documents/db-schema
Expand Up @@ -344,7 +344,7 @@ CREATE TABLE `migration_version` (

LOCK TABLES `migration_version` WRITE;
/*!40000 ALTER TABLE `migration_version` DISABLE KEYS */;
INSERT INTO `migration_version` VALUES (5);
INSERT INTO `migration_version` VALUES (6);
/*!40000 ALTER TABLE `migration_version` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down Expand Up @@ -423,6 +423,31 @@ INSERT INTO `plans` VALUES (7820,708,'<p class=\"sub\">Contact us at <a href=\"m
/*!40000 ALTER TABLE `plans` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `planlove`
--

DROP TABLE IF EXISTS `planlove`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `planlove` (
`lover_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`lovee_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
UNIQUE KEY `unique_idx` (`lover_id`,`lovee_id`),
KEY `lover_idx` (`lover_id`),
KEY `lovee_idx` (`lovee_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `planlove`
--

LOCK TABLES `planlove` WRITE;
/*!40000 ALTER TABLE `planlove` DISABLE KEYS */;
/*!40000 ALTER TABLE `planlove` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `poll_choices`
--
Expand Down
3 changes: 2 additions & 1 deletion functions-edit.php
Expand Up @@ -25,7 +25,7 @@ function get_letters($dbh, $first_letter, $second_letter, $idcookie) {
/*
*Handles the cleaning up of a plan, such as allowing only certain html links in
*/
function cleanText($plan) {
function cleanText($plan, &$planlove = array()) {
$plan = htmlspecialchars($plan); //take out html
//fix the dollar sign error- by josh
//$plan = preg_replace("(\|(\w\s)*)\$
Expand Down Expand Up @@ -54,6 +54,7 @@ function cleanText($plan) {
$dbh = db_connect();
if ($item = get_item($dbh, "username", "accounts", "username", $mycheck)) //see if is a valid user, if so also gets username
{
$planlove[] = $mycheck;
$plan = preg_replace("/\[".preg_quote($mycheck,'/')."\]/s", "[<a href=\"read.php?searchname=$item\" class=\"planlove\">$mycheck</a>]", $plan); //change all occurences of person on plan

} else {
Expand Down

0 comments on commit 3c859fa

Please sign in to comment.