Page MenuHomePhabricator

The user's row is being loaded three times on every page view
Closed, ResolvedPublic

Description

Follow up to the page that happened over the weekend. Just doing an audit of queries on page view showed that we are loading the user three times:

All are exact same query:

SELECT  actor_id,actor_user,actor_name  FROM `actor`    WHERE actor_name = 'Ladsgroup'  LIMIT 1;

Excimer says the latter two are coming from RL:

grafik.png (938×635 px, 121 KB)

grafik.png (944×523 px, 93 KB)

(the last one is actually global css js)

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

The callers such as RL and GlobalCssJs are doing the right thing. They have the reasonable expectation that idoms like User::newFromName do the right thing.

For the user row, User::loadFromId > User::loadFromCache includes an in-memory cache which is working fine:

User::loadFromCache
		$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
		$data = $cache->getWithSetCallback(
			$this->getCacheKey( $cache ),
			$cache::TTL_HOUR,
			function () { ... },
			[ 'pcTTL' => $cache::TTL_PROC_LONG, ... ]
		);

However, before this, User::load, when the object is created with either mFrom=name or mFrom=actor, there is an uncached actor table query to resolve the name to an ID.

User::load
	public function load( $flags = ... ) {
		...

		switch ( $this->mFrom ) {
			case 'defaults':
				...
				break;
			case 'id':
				...
				$this->loadFromId( $flags );
				break;
			case 'actor':
			case 'name':
				$dbr = ...;
				$queryBuilder = $dbr->newSelectQueryBuilder()
					->select( [ 'actor_id', 'actor_user', 'actor_name' ] )
					->from( 'actor' )
					->...->fetchRow();

				if ( ... ) {
					$this->mId = $row->actor_user;
					$this->loadFromId( $flags );
				} else {...}
				break;

The way this fell apart:

Should be easy to adopt ActorStore there to bring this back. That should reduce from 3 down to 1 actor_name queries. Or, with a bit of luck to 0 actor_name queries, because ActorStore populates its cache in both directions, and chances are that the current user was already loaded from user ID rather than user name by AuthManager.

Thank you for looking into this. I have sent you down a rabbit hole. Sorry 😅 I take a look and see what I can do to reduce the queries.

Change #1334061 had a related patch set uploaded (by Milazg; author: Milazg):

[mediawiki/core@master] User: Use ActorStore on User::load

https://gerrit.wikimedia.org/r/1334061

Change #1335290 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/core@master] phpunit: Fix inconsistent user_id in LogFormatterTestCase

https://gerrit.wikimedia.org/r/1335290

Change #1335290 merged by jenkins-bot:

[mediawiki/core@master] phpunit: Fix inconsistent user_id in LogFormatterTestCase

https://gerrit.wikimedia.org/r/1335290

Change #1336613 had a related patch set uploaded (by Krinkle; author: Milazg):

[mediawiki/core@wmf/1.47.0-wmf.18] User: Use ActorStore on User::load

https://gerrit.wikimedia.org/r/1336613

Change #1336613 merged by jenkins-bot:

[mediawiki/core@wmf/1.47.0-wmf.18] User: Use ActorStore on User::load

https://gerrit.wikimedia.org/r/1336613

Change #1334061 merged by jenkins-bot:

[mediawiki/core@master] User: Use ActorStore on User::load

https://gerrit.wikimedia.org/r/1334061

Mentioned in SAL (#wikimedia-operations) [2026-09-07T13:59:48Z] <krinkle@deploy1003> Started scap sync-world: Backport for [[gerrit:1335754|tests: Add user_id and actor in testLogDatabaseRowsForHiddenUser (T436971)]], [[gerrit:1336613|User: Use ActorStore on User::load (T428517)]], [[gerrit:rEVED13357529e57a|Fix empty bases in m(under|over) (T436876)]], [[gerrit:1336611|Use Core-compatible output for cancellation (T379359)]], [[gerrit:1336609|Page: Fix absence caching when combined with mult

Mentioned in SAL (#wikimedia-operations) [2026-09-07T14:04:06Z] <krinkle@deploy1003> krinkle: Backport for [[gerrit:1335754|tests: Add user_id and actor in testLogDatabaseRowsForHiddenUser (T436971)]], [[gerrit:1336613|User: Use ActorStore on User::load (T428517)]], [[gerrit:rEVED13357529e57a|Fix empty bases in m(under|over) (T436876)]], [[gerrit:1336611|Use Core-compatible output for cancellation (T379359)]], [[gerrit:1336609|Page: Fix absence caching when combined with multiple properties

Mentioned in SAL (#wikimedia-operations) [2026-09-07T14:11:43Z] <krinkle@deploy1003> Finished scap sync-world: Backport for [[gerrit:1335754|tests: Add user_id and actor in testLogDatabaseRowsForHiddenUser (T436971)]], [[gerrit:1336613|User: Use ActorStore on User::load (T428517)]], [[gerrit:rEVED13357529e57a|Fix empty bases in m(under|over) (T436876)]], [[gerrit:1336611|Use Core-compatible output for cancellation (T379359)]], [[gerrit:1336609|Page: Fix absence caching when combined with mul

After the fix we have ~66.6% reduction in actor table lookups, from 3 queries to 1 for each page view request.
Before the change the same query ran 3 times

MediaWiki\User\User::load [3.269ms] [1 rows] db1271: SELECT  actor_id,actor_user,actor_name  FROM `actor`    WHERE actor_name = 'Peterxy'  LIMIT 1

Screenshot 2026-09-08 at 22.25.15.png (3,444×1,550 px, 423 KB)

logstash: https://logstash.wikimedia.org/goto/32db729a946d9a18682ef37207411be4

After the deployment, we can see the lookup now happens once and goes through ActorStore

MediaWiki\User\ActorStore::getUserIdentityByName [0.876ms] [1 rows] db1195: SELECT  actor_id,actor_name,actor_user  FROM `actor`    WHERE actor_name = 'MGoncalves-WMF'  LIMIT 1

Screenshot 2026-09-08 at 21.10.40.png (3,444×1,550 px, 428 KB)

logstash: https://logstash.wikimedia.org/goto/35c2afe48559aca3a157d61fc36d6cff