[horde] php and postgres, Re: Horde 3.1, memcache sessionhandler, sidebar, and flock()
Chris Stromsoe
cbs at cts.ucla.edu
Sun Dec 31 07:00:10 PST 2006
In case anybody else is having the same problem -- I am working around
this problem by using the post authentication hook to check for the
existing of the default share for kronolith and nag and am creating them
if they don't. I am not using the shares API, since that's what I'm
having problems with.
I'd prefer to fix the problem described below, but I don't have much more
time to invest in tracking this down. In the mean time, his is my hook.
If anybody can think of a cleaner way to do this, I'm all ears.
-Chris
function _horde_hook_postauthenticate($userID, $credential, $realm)
{
global $conf;
require_once 'DB.php';
$db =& DB::connect($conf['sql'], true);
if (PEAR::isError($db)) {
Horde::logMessage("db connection: " . $db->getMessage(), __FILE__, __LINE__, PHP_LOG_ERR);
return false;
}
$shares = array('kronolith' => 'Default Calendar',
'mnemo' => 'Default Notepad',
'nag' => 'Default Task List');
foreach ($shares as $group => $name) {
$group = 'horde.shares.' . $group;
$query = sprintf("SELECT datatree_id FROM horde_datatree " .
"WHERE user_uid = %s AND group_uid = '%s';",
$db->quote($userID), $group);
$rows = $db->getAll($query);
if (count($rows) == 0) {
$db->autoCommit(false);
$query = "SELECT NEXTVAL('horde_datatree_seq');";
$sequence = $db->getOne($query);
if (count($sequence) != 1) {
$db->rollback();
$db->disconnect();
return false;
}
$query = "INSERT INTO horde_datatree_attributes " .
"(datatree_id,attribute_name,attribute_key,attribute_value) ".
"VALUES (?, ?, ?, ?);";
$sth = $db->prepare($query);
if (PEAR::isError($db->execute($sth, array($sequence, "owner", "", $userID))) ||
PEAR::isError($db->execute($sth, array($sequence, "name", "", $name))) ||
PEAR::isError($db->execute($sth, array($sequence, "perm_type", "", "matrix"))) ||
PEAR::isError($db->execute($sth, array($sequence, "perm_users", $userID, "30")))
) {
Horde::logMessage("db connection: " . $db->getMessage(), __FILE__, __LINE__, PHP_LOG_ERR);
$db->rollback();
$db->disconnect();
return false;
}
$query = "INSERT INTO horde_datatree " .
"(datatree_id,group_uid,user_uid,datatree_name,datatree_parents,datatree_serialized) " .
"VALUES (?, ?, ?, ?, ?, ?);";
$sth = $db->prepare($query);
$rc = $db->execute($sth, array($sequence, $group, $userID, $userID, '', 0));
if (PEAR::isError($rc)) {
Horde::logMessage("db connection: " . $db->getMessage(), __FILE__, __LINE__, PHP_LOG_ERR);
$db->rollback();
$db->disconnect();
return false;
}
$db->commit();
$db->autoCommit(true);
}
}
$db->disconnect();
return true;
}
On Sat, 30 Dec 2006, Chris Stromsoe wrote:
> The more I look at this, the more it looks like here is either something
> broken in the way that PHP is interacting with postgres, or there is
> something broken in the way that horde is using DB to interact with
> postgres.
>
> The first call to ->exists() for any share returns quickly. Subsequent
> calls to ->exists() take an enormous amount of time.
>
> I've inserted microtime() calls into the PEAR DB code in fetchInto(),
> around pg_fetch_row().
>
> During the first exists() call, all of the calls to pg_fetch_row()
> return almost right away (multiple calls return with the same time
> value, measured to one microsecond).
>
> For calls to exists() afterward, the first 12 to 16 calls to
> pg_fetch_row() return in the same amount of time as the first call to
> exists(). All calls after that point average > 100ms to return.
>
> The first calendar access calls exists() to check for a calendar. If
> one does not exists, it creates a share. addShare() calls exists(),
> running 175s (~13k items at 120+ms each). I'm adding nag events to
> calendars. Since people who don't have a calendar likely also don't have
> a tasklist, addShares() gets called again, looping over 24500 nag
> shares.
>
>
> In any event, I'm stumped about where to look. Is anybody else using
> postgres and shares/datatree seeing the same issues (extremely long lag
> times to create the first instance of a share) ?
>
> This is happening for me with both kronolith and nag. I have not
> checked other applications at this point.
>
>
>
> -Chris
>
>
> On Sat, 30 Dec 2006, Chris Stromsoe wrote:
>> On Sat, 30 Dec 2006, Chris Stromsoe wrote:
>>> On Sat, 30 Dec 2006, Chris Stromsoe wrote:
>>>
>>>> The call to "$GLOBALS['kronolith_shares']->addShare($share);" returns the
>>>> results of "$this->_datatree->add($share);"
>>>>
>>>> That calls exists() from DataTree.php, which calls _load() from
>>>> DataTree/sql.php. _load() runs the query
>>>>
>>>> SELECT c.datatree_id, c.datatree_name, c.datatree_parents,
>>>> c.datatree_order
>>>> FROM horde_datatree c
>>>> WHERE c.group_uid = 'horde.shares.kronolith'
>>>>
>>>> and then calls DB::getAll(), which loads the results by looping over all
>>>> 13,000+ of the entries. I added time() calls into DB/common.php around
>>>> the block that loops loading the rows, and I'm timing it at 175s to
>>>> complete.
>>>
>>>
>>> Now I'm really confused. The code block in base.php that I'm looking at
>>> looks like this:
>>>
>>> /* If the user's personal calendar doesn't exist, then create it.
>>> */
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> if (!$GLOBALS['kronolith_shares']->exists(Auth::getAuth())) {
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> require_once 'Horde/Identity.php';
>>> $identity = &Identity::singleton();
>>> $name = $identity->getValue('fullname');
>>> if (trim($name) == '') {
>>> $name = Auth::removeHook(Auth::getAuth());
>>> }
>>>
>>> $share =
>>> &$GLOBALS['kronolith_shares']->newShare(Auth::getAuth());
>>> $share->set('name', sprintf(_("%s's Calendar"), $name));
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> $GLOBALS['kronolith_shares']->addShare($share);
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> $GLOBALS['all_calendars'][Auth::getAuth()] = &$share;
>>>
>>>
>>> The call to ->exists goes through the above mentioned code path. It runs
>>> the same query and loads the same 13k+ entries. It completes in 2s
>>> according to the elapsed time() calls.
>>>
>>> The call to ->addShare() ends up calling ->exists(), which runs the above
>>> query. Only it takes 175s to complete. I added additional time() logging
>>> into the getAll() from PEAR DB to verify.
>>>
>>>
>>> If I add more calls to ->exists, for example, changing the code to read:
>>>
>>> /* If the user's personal calendar doesn't exist, then create it.
>>> */
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> if (!$GLOBALS['kronolith_shares']->exists(Auth::getAuth())) {
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> $GLOBALS['kronolith_shares']->exists(Auth::getAuth());
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>>
>>> require_once 'Horde/Identity.php';
>>> $identity = &Identity::singleton();
>>> $name = $identity->getValue('fullname');
>>> if (trim($name) == '') {
>>> $name = Auth::removeHook(Auth::getAuth());
>>> }
>>>
>>> $share =
>>> &$GLOBALS['kronolith_shares']->newShare(Auth::getAuth());
>>> $share->set('name', sprintf(_("%s's Calendar"), $name));
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> $GLOBALS['kronolith_shares']->addShare($share);
>>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>>> $GLOBALS['all_calendars'][Auth::getAuth()] = &$share;
>>>
>>
>>
>> One final oddity:
>>
>> If I remove the first explicit call to ->exists() and change the code so
>> that it reads:
>>
>>
>> /* If the user's personal calendar doesn't exist, then create it. */
>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>> if (1) {
>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>> require_once 'Horde/Identity.php';
>> $identity = &Identity::singleton();
>> $name = $identity->getValue('fullname');
>> if (trim($name) == '') {
>> $name = Auth::removeHook(Auth::getAuth());
>> }
>>
>> $share =
>> &$GLOBALS['kronolith_shares']->newShare(Auth::getAuth());
>> $share->set('name', sprintf(_("%s's Calendar"), $name));
>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>> $GLOBALS['kronolith_shares']->addShare($share);
>> Horde::logMessage("time " . time(), __FILE__, __LINE__, PEAR_LOG_ERR);
>> $GLOBALS['all_calendars'][Auth::getAuth()] = &$share;
>>
>>
>> to unconditionally run the block, ->addShares() returns in 37s instead of
>> 175s.
>>
>>
>>
>>
>>
>>
>> -Chris
>>
>>
>>
>>>
>>>
>>> Inserting the additional call to ->exists() adds another 175s to the call
>>> time.
>>>
>>>
>>> I've built a stand-alone file using DB to execute the same query, but
>>> can't duplicate the behavior.
>>>
>>> For reference, this is horde stable, kronolith stable, postgres 8.2, php
>>> 5.2.0, and DB 1.7.6. I am not familiar enough with the PEAR DB code, let
>>> alone how horde is using it to know where to look next. Any ideas?
>>>
>>>
>>>
>>> -Chris
>>>
>>
>
More information about the horde
mailing list