On systems with an auto_increment value greater than 1, http://api.drupal.org/api/function/dblog_cron/7 will delete more records than intended. Say your database is setup with auto_increment_increment = 5, 1000 log messages will lead to a max wid of 5001, and the SQL query will be DELETE FROM watchdog WHERE wid <= 4001. Although you technically have only 1000 records in the watchdog table which is within the default limit, 800 of them will be deleted (when none should have been deleted).

This issue was found while working on #549662: Core unit tests assume auto_increment values.

Comments

scor’s picture

Category: task » bug

this is a bug report

hbergin’s picture

To get around the issue of auto_increment value greater than 1 , the queries could be changed to do the following to leave the most recent 1000 records:

Instead of selecting the max(wid) and subtracting 1000
1) select the min(wid) from the most recent 1000 records
2) delete everything less than the resulting min(wid).

SELECT min( wid )
FROM (

SELECT wid
FROM `watchdog`
ORDER BY wid DESC
LIMIT 1000
) AS t1

If this solution addresses your issue correctly, I would be happy to have the bug assigned to me to fix.

bleen’s picture

Issue tags: +backport

once this lands for D7 ... this will need to be ported to D6 (where I can confirm the problem)

scor’s picture

@hbergin please assign the bug to yourself and roll a patch! Your proposal sounds good.

hbergin’s picture

Assigned: Unassigned » hbergin

Great, I'll start working on a patch.

I'll likely end up using db_query_range() or other parts of the database abstraction layer, because not all databases agree on the format of the LIMIT syntax.

scor’s picture

@hbergin make sure to use the new D7 database API and in particular the dynamic queries to work around the vendor specific limit issue.

hbergin’s picture

Yep, looking at it now.

hbergin’s picture

Here's a solution using dynamic queries from the D7 database APIs, which should avoid vendor specific issues. The code looks good so far and I'll roll a patch after further testing.

function dblog_cron() {
  // Cleanup the watchdog table

  // Get the row limit set for the watchdog table (default 1000)
  $rowlimit = variable_get('dblog_row_limit', 1000);
 
  // If row limit is n, get the wid of the nth row if it exists, ordering by the newest entry first.
  // Counting the most recent n rows avoids possible issues with wid number sequences,
  // e.g. auto_increment value > 1 or rows have been deleted directly from the table. 
  if ($rowlimit > 0) {
    $result = db_select('watchdog', 'w')
      ->fields('w', array('wid'))
      ->orderBy('wid', 'DESC')
      ->range($rowlimit-1, 1)
      ->execute(); 

    //Delete all table entries older than the nth row.  Only executes if nth row was found. 
    foreach($result as $min_row) {
      $num_deleted = db_delete('watchdog')
        ->condition('wid', $min_row->wid, '<')
        ->execute();
    }
  }
}
hbergin’s picture

StatusFileSize
new1.64 KB

Here's the patch, with some additional edits made to the code in my last post regarding coding guidelines and a check for a successful query execution.

I would be happy to help with the D6 port as needed, and have drafted code for that.

hbergin’s picture

Status: Active » Needs review
scor’s picture

Status: Needs review » Needs work
+++ modules/dblog/dblog.module	22 Sep 2010 14:57:28 -0000
@@ -101,12 +101,28 @@ function dblog_init() {
+      foreach ($result as $min_row) {

no need for foreach here, the delete query should only be run once. use a fetchField() or fecthCol() right after the execute() above to get the value directly.

+++ modules/dblog/dblog.module	22 Sep 2010 14:57:28 -0000
@@ -101,12 +101,28 @@ function dblog_init() {
+        $num_deleted = db_delete('watchdog')

seems we've not using the $num_deleted variable, is it necessary to use this variable then?

Powered by Dreditor.

hbergin’s picture

Status: Needs work » Needs review
StatusFileSize
new1.61 KB

Made updates as recommended in this new patch revision, now using fetchField(), and not using $num_deleted variable.

scor’s picture

Have you tried this patch with a value of auto_increment > 1? just run set global auto_increment_increment = 2; in a MySQL shell. Try to run the associated test (might need tweaking). restart your sql server to revert this change, or rerun the same command with value 1.

also minor whitespace to fix in the patch.

+++ modules/dblog/dblog.module	22 Sep 2010 17:57:08 -0000
@@ -101,12 +101,26 @@ function dblog_init() {
+  // Get the row limit set for the watchdog table (default 1000).
+  $row_limit = variable_get('dblog_row_limit', 1000);
+ ¶
+  // For row limit n, get the wid of the nth row in descending wid order.
bleen’s picture

Issue tags: +Needs tests

I think this needs a test too

hbergin’s picture

Issue tags: -Needs tests

Thanks for the very helpful and speedy feedback. Here's a summary of the tests I have run on the patches submitted:

Running the cron task when:
1) No rows in the watchdog table
2) Less rows than the limit in the watchdog table
3) More rows than the limit in the watchdog table
4) Same number of rows as the limit in the watchdog table
5) Tests 1-4 with the increment value changed to greater than 1
6) Tests 1-4 with the increment value returned to 1
7) Tests 2-4 with rows deleted directly from the table out of wid sequence order (e.g. not oldest or newest rows)
8) DBQuery fails (e.g. returned result is FALSE)

Does this cover the testing scenarios you had in mind?
If so, I'll go ahead and re-roll the patch without the errant white space.
If not, could you give me a pointer or more info on the associated test you mentioned?

bleen’s picture

Issue tags: +Needs tests

hbergin: Those are exactly the test scenarios we should be covering, but we should write some simple tests (http://drupal.org/simpletest) so that this error doesn't resurface. I'm not 100% certain we can write a test that checks how the database performs with an autoimcrement of >1, but we should look into it at the very least

hbergin’s picture

StatusFileSize
new1.61 KB

Here is the patch without the errant white space, as there's no need to hold off on that fix. Simple test is a new area for me, but I'm happy to help investigate as I agree there's great value in automated test suites.

scor’s picture

The testcase should be as auto_increment agnostic as possible and not try to change the value of the local db auto_increment. In other words, the test should pass regardless of the value of auto_increment. There is actually a more general issue about that if you are interested: #549662: Core unit tests assume auto_increment values which should uncover plenty of issues like this one where tests don't pass when auto_increment_increment and auto_increment_offset are different from 1.

hbergin’s picture

Great, I was reading that general issue thread and was thinking the same thing.

I see the dblog.test file verifyCron() function already includes the test for rows exceeding the limit in the watchdog table, so I could look to complement this test scenario with:

Running the cron task when:
1) No rows in the watchdog table
2) Less rows than the limit in the watchdog table
3) More rows than the limit in the watchdog table (already part of simple test)
4) Same number of rows as the limit in the watchdog table
5) Tests 2-4 with rows deleted directly from the table out of wid sequence order (e.g. not oldest or newest rows)

scor’s picture

I don't think dblog.test needs any change at all since it relies on COUNT(wid). The test fails when run on a db with auto_increment_increment > 1, but it should now pass with your patch #17.

hbergin’s picture

I agree the code already in dblog.test works just fine using COUNT(wid).

My thought was that the verifyCron() function only includes one test scenario at the moment, and we could add additional scenarios from the ones listed in #19.

hbergin’s picture

Checked out the HEAD code today. Here are patches rolled against this latest source code. Both were tested using the D7 development testing feature and run through the coder module.

dblog-cron-watchdogcleanup-917134.patch
Same code changes as in #17, but against latest source code.

dblog-cron-watchdogcleanup-tests-917134.patch
As per #21, this patch adds test variations to dblog.test verifyCron() function, using similar format to the already existing test.

Test were added to check the resulting rows in the watchdog table after running the cron task when:
1) No rows in the watchdog table
2) Less rows than the limit in the watchdog table
3) Same number of rows as the limit in the watchdog table
4) More rows than the limit in the watchdog table, where rows are out of wid sequence order (e.g. due to ad hoc direct deletions from the table)

dries’s picture

+++ modules/dblog/dblog.module	1 Oct 2010 20:31:24 -0000
@@ -100,12 +100,26 @@ function dblog_init() {
+  // Get the row limit set for the watchdog table (default 1000).

This code comment is redundant.

hbergin’s picture

dblog-cron-watchdogcleanup-917134.patch
Redundant comment removed as per #23.

dblog-cron-watchdogcleanup-tests-917134.patch
Comment edits.

Status: Needs review » Needs work
Issue tags: -backport, -Needs tests, -auto_increment_*

The last submitted patch, dblog-cron-watchdogcleanup-tests-917134.patch, failed testing.

hbergin’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work
Issue tags: +backport, +Needs tests, +auto_increment_*

The last submitted patch, dblog-cron-watchdogcleanup-tests-917134.patch, failed testing.

hbergin’s picture

Status: Needs work » Needs review

#25 had 2 failures in database_test.test related to function testAlterRemoveRange().
#27 had 1 failure in upgrade.test related to function public function testBasicUpgrade()

Could not spot any direct correlation with patch to dblog.test verifyCron() changes which only accesses the watchdog table and runs cron. The fails in #25 and #27 were in different places, with the same patch code.

Requeuing once more.

hbergin’s picture

hbergin’s picture

D6 equivalent syntax for dblog_cron() solution backport. Tested against D6 install.

function dblog_cron() {
  $row_limit = variable_get('dblog_row_limit', 1000);
  if ($row_limit > 0) {
    $min_row = db_result(db_query_range('SELECT wid FROM {watchdog} ORDER BY wid DESC', ($row_limit-1), 1));
    if ($min_row) {
      db_query('DELETE FROM {watchdog} WHERE wid < %d', $min_row);
    }
  }
}
dries’s picture

Status: Needs review » Fixed

Committed to CVS HEAD. Thanks!

andypost’s picture

Status: Fixed » Needs work

This patch should have follow-up to use Database::getConnection('default', 'default') except direct db_select()
last commit kills ability to use master/slave feature for dblog
also db_select is bad here because it's alterable

bleen’s picture

Version: 7.x-dev » 6.x-dev
Issue tags: -Needs tests

andypost ... i would open a new issue for that and just reference this one for more info

this issue should stay open for the backport however

hbergin’s picture

Status: Needs work » Needs review
StatusFileSize
new923 bytes

Similar tests were run on this d6 backport patch as listed in #15. Also run through coder module.

scor’s picture

Version: 6.x-dev » 7.x-dev

The tests (second patch in #24) were not committed (nor reviewed).

mr.baileys’s picture

Status: Needs review » Needs work

The last submitted patch, dblog-cron-d6backport-917134.patch, failed testing.

andypost’s picture

@bleen18 I think no reason in different issue because this still needs work

hbergin’s picture

@andypost, does this revised syntax address your concerns? If so, I can look at re-rolling a patch with this syntax.

When I first wrote the patch, some api documentation indicated that db_query_range was going to be deprecated, but I do not see that in the current documentation, so am assuming it's OK to use it now.

$row_limit = variable_get('dblog_row_limit', 1000);

if ($row_limit > 0) {
  $query = 'SELECT wid FROM {watchdog} ORDER BY wid DESC';
  $min_row = db_query_range($query, $row_limit-1, 1)->fetchField();

  if ($min_row) {
    Database::getConnection('default', 'default')->delete('watchdog')
      ->condition('wid', $min_row, '<')
      ->execute();
  }
}
hbergin’s picture

StatusFileSize
new971 bytes

Patch updated to use syntax in #39 to address #32.
Run on local D7 HEAD install using simpletest module and coder module.

hbergin’s picture

Status: Needs work » Needs review
hbergin’s picture

As per #21, this patch adds test variations to dblog.test verifyCron() function, using similar format to the already existing test.

Test were added to check the resulting rows in the watchdog table after running the cron task when:
1) No rows in the watchdog table
2) Less rows than the limit in the watchdog table
3) Same number of rows as the limit in the watchdog table
4) More rows than the limit in the watchdog table, where rows are out of wid sequence order (e.g. due to ad hoc direct deletions from the table).

andypost’s picture

Status: Needs review » Needs work

@hbergin, please merge #40 and #42 in one patch, I think it's RTBC if both in one

hbergin’s picture

Status: Needs work » Needs review
StatusFileSize
new6.11 KB

Thanks for the feedback, @andypost. Here is a patch which merges #40 and #42, as requested in #43.

mgifford’s picture

Assigned: hbergin » Unassigned
Issue summary: View changes
Status: Needs review » Needs work

Patch no longer applies.

  • Dries committed f45d9a2 on 8.3.x
    - Patch #917134 by hbergin: dblog_cron() deletes too many records when...

  • Dries committed f45d9a2 on 8.3.x
    - Patch #917134 by hbergin: dblog_cron() deletes too many records when...

  • Dries committed f45d9a2 on 8.4.x
    - Patch #917134 by hbergin: dblog_cron() deletes too many records when...

  • Dries committed f45d9a2 on 8.4.x
    - Patch #917134 by hbergin: dblog_cron() deletes too many records when...
dagmar’s picture

Status: Needs work » Needs review
StatusFileSize
new5.88 KB

Relloled.

  • Dries committed f45d9a2 on 9.1.x
    - Patch #917134 by hbergin: dblog_cron() deletes too many records when...

Status: Needs review » Closed (outdated)

Automatically closed because Drupal 7 security and bugfix support has ended as of 5 January 2025. If the issue verifiably applies to later versions, please reopen with details and update the version.