Page MenuHomePhabricator

Special:SecurePoll makes primary database connections on GET requests when getting UI messages or checking for election admins
Open, Needs TriagePublic

Description

This is causing warnings about inappropriate primary DB connections on GET requests (about 1000 in the last 24 hours, at the time of writing).

UI messages

Typical stack trace:

from /srv/mediawiki/php-1.37.0-wmf.6/includes/libs/rdbms/TransactionProfiler.php(444)
#0 /srv/mediawiki/php-1.37.0-wmf.6/includes/libs/rdbms/TransactionProfiler.php(224): Wikimedia\Rdbms\TransactionProfiler->reportExpectationViolated(string, string, integer)
#1 /srv/mediawiki/php-1.37.0-wmf.6/includes/libs/rdbms/loadbalancer/LoadBalancer.php(1010): Wikimedia\Rdbms\TransactionProfiler->recordConnection(string, string, boolean)
#2 /srv/mediawiki/php-1.37.0-wmf.6/includes/libs/rdbms/loadbalancer/LoadBalancer.php(964): Wikimedia\Rdbms\LoadBalancer->getServerConnection(integer, string, integer)
#3 /srv/mediawiki/php-1.37.0-wmf.6/includes/libs/rdbms/loadbalancer/LoadBalancer.php(1130): Wikimedia\Rdbms\LoadBalancer->getConnection(integer, array, string, integer)
#4 /srv/mediawiki/php-1.37.0-wmf.6/includes/GlobalFunctions.php(2307): Wikimedia\Rdbms\LoadBalancer->getMaintenanceConnectionRef(integer, array, string)
#5 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/DBStore.php(135): wfGetDB(integer)
#6 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/DBStore.php(139): MediaWiki\Extensions\SecurePoll\DBStore->getDB()
#7 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/Entities/Election.php(404): MediaWiki\Extensions\SecurePoll\DBStore->getQuestionInfo(string)
#8 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/Entities/Election.php(166): MediaWiki\Extensions\SecurePoll\Entities\Election->getQuestions()
#9 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/Entities/Entity.php(100): MediaWiki\Extensions\SecurePoll\Entities\Election->getChildren()
#10 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/Entities/Entity.php(119): MediaWiki\Extensions\SecurePoll\Entities\Entity->getDescendants()
#11 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/Entities/Entity.php(167): MediaWiki\Extensions\SecurePoll\Entities\Entity->loadMessages(string)
#12 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/Pages/ListPage.php(43): MediaWiki\Extensions\SecurePoll\Entities\Entity->getMessage(string)
#13 /srv/mediawiki/php-1.37.0-wmf.6/extensions/SecurePoll/includes/SpecialSecurePoll.php(70): MediaWiki\Extensions\SecurePoll\Pages\ListPage->execute(array)
#14 /srv/mediawiki/php-1.37.0-wmf.6/includes/specialpage/SpecialPage.php(646): MediaWiki\Extensions\SecurePoll\SpecialSecurePoll->execute(string)
#15 /srv/mediawiki/php-1.37.0-wmf.6/includes/specialpage/SpecialPageFactory.php(1390): SpecialPage->run(string)
#16 /srv/mediawiki/php-1.37.0-wmf.6/includes/MediaWiki.php(314): MediaWiki\SpecialPage\SpecialPageFactory->executePath(string, RequestContext)
#17 /srv/mediawiki/php-1.37.0-wmf.6/includes/MediaWiki.php(917): MediaWiki->performRequest()
#18 /srv/mediawiki/php-1.37.0-wmf.6/includes/MediaWiki.php(551): MediaWiki->main()
#19 /srv/mediawiki/php-1.37.0-wmf.6/index.php(53): MediaWiki->run()
#20 /srv/mediawiki/php-1.37.0-wmf.6/index.php(46): wfIndexMain()
#21 /srv/mediawiki/w/index.php(3): require(string)
#22 {main}

Entity::getMessage calls DBStore::getQuestionInfo, which always queries the primary database:

public function getDB( $index = DB_PRIMARY ) {
	return wfGetDB( $this->forceMaster ? DB_PRIMARY : $index );
}

public function getQuestionInfo( $electionId ) {
	$db = $this->getDB();
	$res = $db->select(
		// etc.
	);
	// etc.
}

Entity::getMessage is used by most subpages of Special:SecurePoll, e.g. to display an election's title. In these cases, it should query the replicas.

Checking for election admins

Typical stack trace:

from /srv/mediawiki/php-1.37.0-wmf.5/includes/libs/rdbms/TransactionProfiler.php(432)
#0 /srv/mediawiki/php-1.37.0-wmf.5/includes/libs/rdbms/TransactionProfiler.php(212): Wikimedia\Rdbms\TransactionProfiler->reportExpectationViolated(string, string, integer)
#1 /srv/mediawiki/php-1.37.0-wmf.5/includes/libs/rdbms/loadbalancer/LoadBalancer.php(1010): Wikimedia\Rdbms\TransactionProfiler->recordConnection(string, string, boolean)
#2 /srv/mediawiki/php-1.37.0-wmf.5/includes/libs/rdbms/loadbalancer/LoadBalancer.php(964): Wikimedia\Rdbms\LoadBalancer->getServerConnection(integer, string, integer)
#3 /srv/mediawiki/php-1.37.0-wmf.5/includes/libs/rdbms/loadbalancer/LoadBalancer.php(1130): Wikimedia\Rdbms\LoadBalancer->getConnection(integer, array, string, integer)
#4 /srv/mediawiki/php-1.37.0-wmf.5/includes/GlobalFunctions.php(2307): Wikimedia\Rdbms\LoadBalancer->getMaintenanceConnectionRef(integer, array, string)
#5 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/DBStore.php(135): wfGetDB(integer)
#6 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/DBStore.php(60): MediaWiki\Extensions\SecurePoll\DBStore->getDB()
#7 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/Entities/Entity.php(132): MediaWiki\Extensions\SecurePoll\DBStore->getProperties(array)
#8 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/Entities/Entity.php(238): MediaWiki\Extensions\SecurePoll\Entities\Entity->loadProperties()
#9 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/Entities/Election.php(361): MediaWiki\Extensions\SecurePoll\Entities\Entity->getProperty(string)
#10 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/Pages/ElectionPager.php(127): MediaWiki\Extensions\SecurePoll\Entities\Election->isAdmin(User)
#11 /srv/mediawiki/php-1.37.0-wmf.5/includes/pager/IndexPager.php(611): MediaWiki\Extensions\SecurePoll\Pages\ElectionPager->formatRow(stdClass)
#12 /srv/mediawiki/php-1.37.0-wmf.5/includes/pager/TablePager.php(94): IndexPager->getBody()
#13 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/Pages/EntryPage.php(38): TablePager->getBodyOutput()
#14 /srv/mediawiki/php-1.37.0-wmf.5/extensions/SecurePoll/includes/SpecialSecurePoll.php(70): MediaWiki\Extensions\SecurePoll\Pages\EntryPage->execute(array)
#15 /srv/mediawiki/php-1.37.0-wmf.5/includes/specialpage/SpecialPage.php(646): MediaWiki\Extensions\SecurePoll\SpecialSecurePoll->execute(string)
#16 /srv/mediawiki/php-1.37.0-wmf.5/includes/specialpage/SpecialPageFactory.php(1396): SpecialPage->run(NULL)
#17 /srv/mediawiki/php-1.37.0-wmf.5/includes/MediaWiki.php(313): MediaWiki\SpecialPage\SpecialPageFactory->executePath(string, RequestContext)
#18 /srv/mediawiki/php-1.37.0-wmf.5/includes/MediaWiki.php(916): MediaWiki->performRequest()
#19 /srv/mediawiki/php-1.37.0-wmf.5/includes/MediaWiki.php(550): MediaWiki->main()
#20 /srv/mediawiki/php-1.37.0-wmf.5/index.php(53): MediaWiki->run()
#21 /srv/mediawiki/php-1.37.0-wmf.5/index.php(46): wfIndexMain()
#22 /srv/mediawiki/w/index.php(3): require(string)
#23 {main}

Election::isAdmin calls DBStore::getProperties, which always queries the primary database:

public function getProperties( $ids ) {
	// See DBStore::getDB above
	$db = $this->getDB();
	$res = $db->select(
		// etc.
	);
	//etc.
}

Election::isAdmin is used for permissions checks when displaying forms and links. In these cases, it should query the replicas.

Possible solutions

We might need to allow passing which database to query when calling getQuestionInfo and getProperties.

Interestingly, a similar method getElectionInfo was changed to always query the replicas in https://gerrit.wikimedia.org/r/c/mediawiki/extensions/SecurePoll/+/332952. (...Though it seems unlikely we could do this with getProperties, since admin checks may be performed when admins are actually trying to perform an action.)

Event Timeline

Change 709533 had a related patch set uploaded (by Zabe; author: Zabe):

[mediawiki/extensions/SecurePoll@master] Use a replica connection in DBStore::getQuestionInfo()

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

Change 709533 merged by jenkins-bot:

[mediawiki/extensions/SecurePoll@master] Use a replica connection in DBStore::getQuestionInfo()

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

@Zabe Further to your comment https://gerrit.wikimedia.org/r/c/mediawiki/extensions/SecurePoll/+/709533/1#message-dd40e9955064e53f6d6eaaea03533aaebed5d6e3, will this patch actually change anything? Are you planning a follow up patch? Thanks.

I am not very familiar with the extension and it was set to force a primary connection in T209804, so we need to make sure it doesn't break again. Maybe if I find some time I can take another look.

@Zabe Further to your comment https://gerrit.wikimedia.org/r/c/mediawiki/extensions/SecurePoll/+/709533/1#message-dd40e9955064e53f6d6eaaea03533aaebed5d6e3, will this patch actually change anything? Are you planning a follow up patch? Thanks.

I am not very familiar with the extension and it was set to force a primary connection in T209804, so we need to make sure it doesn't break again. Maybe if I find some time I can take another look.

DBStore is actually intantiated elsewhere in a slightly abstracted way. So when called that way, (and hasn't been through recordElectionToNamespace before in the same session), that will result in it being implicitly false (from Context.php):

	/**
	 * Get the Store instance
	 * @return Store
	 */
	public function getStore() {
		if ( !isset( $this->store ) ) {
			$this->store = new $this->storeClass;
		}

		return $this->store;
	}

So, in theory, you patch should have actually helped in at least some situations. Would need the logs to be checked since .17 to see if/how much it has helped.

@Zabe Further to your comment https://gerrit.wikimedia.org/r/c/mediawiki/extensions/SecurePoll/+/709533/1#message-dd40e9955064e53f6d6eaaea03533aaebed5d6e3, will this patch actually change anything? Are you planning a follow up patch? Thanks.

I am not very familiar with the extension and it was set to force a primary connection in T209804, so we need to make sure it doesn't break again. Maybe if I find some time I can take another look.

DBStore is actually intantiated elsewhere in a slightly abstracted way. So when called that way, (and hasn't been through recordElectionToNamespace before in the same session), that will result in it being implicitly false (from Context.php):

	/**
	 * Get the Store instance
	 * @return Store
	 */
	public function getStore() {
		if ( !isset( $this->store ) ) {
			$this->store = new $this->storeClass;
		}

		return $this->store;
	}

So, in theory, you patch should have actually helped in at least some situations. Would need the logs to be checked since .17 to see if/how much it has helped.

Thanks, I hadn't spotted that instantiation.

Change 712605 had a related patch set uploaded (by Reedy; author: Reedy):

[mediawiki/extensions/SecurePoll@master] DBStore: Send reads to DB_REPLICA by default

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

Change 712605 merged by jenkins-bot:

[mediawiki/extensions/SecurePoll@master] DBStore: Send reads to DB_REPLICA by default

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

^ That sends all the reads done inside DBStore to DB_REPLICA unless the force primary override is in place.

https://logstash.wikimedia.org/goto/de2b2fa834a039b1ad27833c89262fe2

Screenshot 2021-08-12 at 22.11.30.png (908×1 px, 184 KB)

^ Using that link, there's definitely more entries, but that's expected as it's being used more.

Most of the log entries now seem to be T288784: SecurePoll doing DB writes to securepoll_voters on HTTP GET [Timebox: 8h]

Screenshot 2021-08-24 at 15.16.06.png (854×1 px, 166 KB)

So... I think the two patches here have definitely helped. And the two forked tasks (T288783: SecurePoll doing DB writes to securepoll_log on HTTP GET [M] being the other) cover the remaining related cases

In the past, AHT stepped in to support Trust & Safety with the time-sensitive matter of the Board Elections by providing help with SecurePoll. Unfortunately, at this time we can no longer support nor maintain SecurePoll. Per the Foundation leadership’s instructions, AHT is dedicating all of our time and energy to other critical efforts.