Page MenuHomePhabricator

Database error when creating an abuse filter
Closed, ResolvedPublicPRODUCTION ERROR

Description

I'm trying to create a new abuse filter on ro.wp and get the following error when saving:

[51b6a458-384a-45fa-9ecc-8374f2a919b1] 2022-04-22 09:07:21: Excepție fatală de tipul „Wikimedia\Rdbms\DBQueryError”

2022-04-22 09:07:21 [51b6a458-384a-45fa-9ecc-8374f2a919b1] mw1354 rowiki 1.39.0-wmf.8 exception ERROR: [51b6a458-384a-45fa-9ecc-8374f2a919b1] /wiki/Special:Filtru_abuzuri/new   Wikimedia\Rdbms\DBQueryError: Error 1062: Duplicate entry '7' for key 'PRIMARY' (db1181)
Function: MediaWiki\Extension\AbuseFilter\FilterStore::doSaveFilter
Query: INSERT INTO `abuse_filter` (af_id,af_pattern,af_public_comments,af_comments,af_group,af_actions,af_enabled,af_deleted,af_hidden,af_global,af_user,af_user_text,af_timestamp,af_hit_count,af_throttled) VALUES (NULL,'user_editcount === null & ip_in_range(user_name, \"93.192.0.0/10\") & added_lines rlike \"Marian\"','Marian','','default','disallow',1,0,0,0,3490,'Strainu','20220422090721',0,0)
 {"exception_url":"/wiki/Special:Filtru_abuzuri/new","reqId":"51b6a458-384a-45fa-9ecc-8374f2a919b1","caught_by":"entrypoint"} 
[Exception Wikimedia\Rdbms\DBQueryError] (/srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/Database.php:1779) Error 1062: Duplicate entry '7' for key 'PRIMARY' (db1181)
Function: MediaWiki\Extension\AbuseFilter\FilterStore::doSaveFilter
Query: INSERT INTO `abuse_filter` (af_id,af_pattern,af_public_comments,af_comments,af_group,af_actions,af_enabled,af_deleted,af_hidden,af_global,af_user,af_user_text,af_timestamp,af_hit_count,af_throttled) VALUES (NULL,'user_editcount === null & ip_in_range(user_name, \"93.192.0.0/10\") & added_lines rlike \"Marian\"','Marian','','default','disallow',1,0,0,0,3490,'Strainu','20220422090721',0,0)

  #0 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/Database.php(1763): Wikimedia\Rdbms\Database->getQueryException(string, integer, string, string)
  #1 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/Database.php(1737): Wikimedia\Rdbms\Database->getQueryExceptionAndLog(string, integer, string, string)
  #2 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/Database.php(1217): Wikimedia\Rdbms\Database->reportQueryError(string, integer, string, string, boolean)
  #3 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/Database.php(2525): Wikimedia\Rdbms\Database->query(string, string, integer)
  #4 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/Database.php(3550): Wikimedia\Rdbms\Database->doInsert(string, array, string)
  #5 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/DBConnRef.php(68): Wikimedia\Rdbms\Database->replace(string, string, array, string)
  #6 /srv/mediawiki/php-1.39.0-wmf.8/includes/libs/rdbms/database/DBConnRef.php(468): Wikimedia\Rdbms\DBConnRef->__call(string, array)
  #7 /srv/mediawiki/php-1.39.0-wmf.8/extensions/AbuseFilter/includes/FilterStore.php(148): Wikimedia\Rdbms\DBConnRef->replace(string, string, array, string)
  #8 /srv/mediawiki/php-1.39.0-wmf.8/extensions/AbuseFilter/includes/FilterStore.php(108): MediaWiki\Extension\AbuseFilter\FilterStore->doSaveFilter(User, MediaWiki\Extension\AbuseFilter\Filter\MutableFilter, array, NULL, boolean)
  #9 /srv/mediawiki/php-1.39.0-wmf.8/extensions/AbuseFilter/includes/View/AbuseFilterViewEdit.php(204): MediaWiki\Extension\AbuseFilter\FilterStore->saveFilter(User, NULL, MediaWiki\Extension\AbuseFilter\Filter\MutableFilter, MediaWiki\Extension\AbuseFilter\Filter\MutableFilter)
  #10 /srv/mediawiki/php-1.39.0-wmf.8/extensions/AbuseFilter/includes/View/AbuseFilterViewEdit.php(155): MediaWiki\Extension\AbuseFilter\View\AbuseFilterViewEdit->attemptSave(NULL, NULL)
  #11 /srv/mediawiki/php-1.39.0-wmf.8/extensions/AbuseFilter/includes/Special/SpecialAbuseFilter.php(166): MediaWiki\Extension\AbuseFilter\View\AbuseFilterViewEdit->show()
  #12 /srv/mediawiki/php-1.39.0-wmf.8/includes/specialpage/SpecialPage.php(672): MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseFilter->execute(string)
  #13 /srv/mediawiki/php-1.39.0-wmf.8/includes/specialpage/SpecialPageFactory.php(1406): SpecialPage->run(string)
  #14 /srv/mediawiki/php-1.39.0-wmf.8/includes/MediaWiki.php(316): MediaWiki\SpecialPage\SpecialPageFactory->executePath(string, RequestContext)
  #15 /srv/mediawiki/php-1.39.0-wmf.8/includes/MediaWiki.php(912): MediaWiki->performRequest()
  #16 /srv/mediawiki/php-1.39.0-wmf.8/includes/MediaWiki.php(566): MediaWiki->main()
  #17 /srv/mediawiki/php-1.39.0-wmf.8/index.php(50): MediaWiki->run()
  #18 /srv/mediawiki/php-1.39.0-wmf.8/index.php(46): wfIndexMain()
  #19 /srv/mediawiki/w/index.php(3): require(string)
  #20 {main}

Event Timeline

Reedy changed the subtype of this task from "Bug Report" to "Production Error".Apr 22 2022, 9:23 AM
Reedy updated the task description. (Show Details)

Same issue as T300924... As I said back then, this code hasn't changed in years, and there's nothing that stands out as obviously wrong with it. CC'ing @Ladsgroup in case he may have some ideas, but I really don't know why this is happening.

This time I spent some time looking at the code and while I can't exactly say what is the reason behind this but I'm fairly certain it has to do with the way the code inserts new filters. It currently sets af_id in INSERT. INSERT INTO abuse_filter` (af_id,af_pattern,af_public_comments`. This is a bad practice and can lead to lots of issues in HA setups and you are basically at the mercy of your database being 100% consistent all the time. You should avoid making any change to af_id of any rows (the code does that: $dbw->replace( 'abuse_filter', 'af_id', $newRow, __METHOD__ );) and so on. The insert should avoid setting af_id and simply should read the value later and leave it to the db to set it properly.

We wanted to do this kind of gymnastics in url shorterner to get "custom short urls" but DBAs told us it can break in so many different ways.

General rule: Don't change or set columns that their values is auto_increment. Only read them.

Do we have any workaround until the code is fixed? On the other bug it fixed itself, but rowiki has almost 100 filters.

This time I spent some time looking at the code and while I can't exactly say what is the reason behind this but I'm fairly certain it has to do with the way the code inserts new filters. It currently sets af_id in INSERT. INSERT INTO abuse_filter` (af_id,af_pattern,af_public_comments`. This is a bad practice and can lead to lots of issues in HA setups and you are basically at the mercy of your database being 100% consistent all the time. You should avoid making any change to af_id of any rows (the code does that: $dbw->replace( 'abuse_filter', 'af_id', $newRow, __METHOD__ );) and so on. The insert should avoid setting af_id and simply should read the value later and leave it to the db to set it properly.

Good point. That code is responsible for both creating new filters and updating existing ones, hence the use of IDatabase::replace() with rows containing af_id (which is NULL for new filters). I wonder if IDatabase::upsert would be better suited for this use case (in fact, I don't quite get how to choose between the two).

This time I spent some time looking at the code and while I can't exactly say what is the reason behind this but I'm fairly certain it has to do with the way the code inserts new filters. It currently sets af_id in INSERT. INSERT INTO abuse_filter` (af_id,af_pattern,af_public_comments`. This is a bad practice and can lead to lots of issues in HA setups and you are basically at the mercy of your database being 100% consistent all the time. You should avoid making any change to af_id of any rows (the code does that: $dbw->replace( 'abuse_filter', 'af_id', $newRow, __METHOD__ );) and so on. The insert should avoid setting af_id and simply should read the value later and leave it to the db to set it properly.

Good point. That code is responsible for both creating new filters and updating existing ones, hence the use of IDatabase::replace() with rows containing af_id (which is NULL for new filters). I wonder if IDatabase::upsert would be better suited for this use case (in fact, I don't quite get how to choose between the two).

replace doesn't insert when it's a new row (AFAIK and simply don't pass it). Upsert should insert but I suggest avoiding that as MySQL due to MVCC wastes an auto_increment value on update. I think mw core tires to avoid that but still, first do a select if it exists do an update, if not, do an upsert (to avoid issues in race conditions).

Do we have any workaround until the code is fixed? On the other bug it fixed itself, but rowiki has almost 100 filters.

I can try to look at fixing the auto increment value for now. I was busy with another db issue.

This time I spent some time looking at the code and while I can't exactly say what is the reason behind this but I'm fairly certain it has to do with the way the code inserts new filters. It currently sets af_id in INSERT. INSERT INTO abuse_filter` (af_id,af_pattern,af_public_comments`. This is a bad practice and can lead to lots of issues in HA setups and you are basically at the mercy of your database being 100% consistent all the time. You should avoid making any change to af_id of any rows (the code does that: $dbw->replace( 'abuse_filter', 'af_id', $newRow, __METHOD__ );) and so on. The insert should avoid setting af_id and simply should read the value later and leave it to the db to set it properly.

Good point. That code is responsible for both creating new filters and updating existing ones, hence the use of IDatabase::replace() with rows containing af_id (which is NULL for new filters). I wonder if IDatabase::upsert would be better suited for this use case (in fact, I don't quite get how to choose between the two).

replace doesn't insert when it's a new row

I think I didn't get this. AIUI, replace does a delete first, and then an insert. If the filter is new (af_id = NULL), no row is deleted and it just inserts a new row, letting the auto increment choose the ID. I thought it was good for this use case.

More generally, I'm a bit puzzled: we have two methods that seem to be fine for the use case where you want to either update an existing row or insert a new one (with auto increment). It's a bit weird that neither of them can be used instead. I wonder if this should be fixed in MW core then.

From looking at the docs, replace doesn't accept af_id with NULL. It even says Columns belonging to a key in $uniqueKeys must be defined here and non-null.

General note: Half of rdbms is overly complex with speculative benefits and needs to be removed. Probably this function would be one of them but doing it will take time. I'm working on it.

I reset the auto increment value to 98 so it should be working now for rowiki but this code path needs fixing.

Confirmed working for rowiki, but I do suggest leaving the bug open until it's properly fixed.

Yeah. I can spend some time on it next week.

Change 785971 had a related patch set uploaded (by Ladsgroup; author: Amir Sarabadani):

[mediawiki/extensions/AbuseFilter@master] FilterStore: Use upsert instead of replace

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

Change 785971 merged by jenkins-bot:

[mediawiki/extensions/AbuseFilter@master] FilterStore: Use upsert instead of replace

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

Ladsgroup claimed this task.
Ladsgroup edited projects, added DBA; removed Patch-For-Review.
Ladsgroup moved this task from Triage to Done on the DBA board.