Database primary keys can exceed maximum integer storeable (has actually occurred, in watchdog)

Created on 13 September 2019, almost 5 years ago
Updated 1 May 2023, about 1 year ago

Problem/Motivation

Original report

Watchdog wid reaches maximum integer storeable

To reproduce: have a site which generates a large number of Notices and Warnings (eg. uninitialised variables, unset array keys) per page load, and which receives a very large amount of traffic.

Expected behaviour: dblog automatically limits the number of log messages to the specified value, site continues to function without issue.

Actual behaviour: because dblog never resets the wid auto_increment value, the wid eventually reaches 2147483648. At this point the site suffers a White Screen of Death; all requests fail with a 500 code but no errors are logged in the server logs (as is usual with such errors) and altering error logging does not produce any on-screen errors. (Probably because the attempt to log the DB exception raised fails in its own right.)

Detection of issue: the only ways to find out what is happening are a) to know this is a possible issue and check the current value of the auto_increment, or b) to use a debugger to step through the code line-by-line until a notice is generated.

Possible fixes or remediation:

1. Have dblog output the database error to the server logs, to screen, or somewhere accessible to developers.

2. Have dblog be aware of the maximum value an integer can take in the database used, and refrain from attempting to add any more entries once the wid gets close to that maximum.

3. Have dblog periodically reset all extant wids to start at 1, and reset the auto_increment.

Issue was observed in Drupal v7.67, but definitely affects all v7 variants, and may well affect v8 as well.

Steps to reproduce

Proposed resolution

Changing the primary key value from a regular serial field to a big serial field. The maximum value that could be stored was 2147483647 and will become 9223372036854775807.

Remaining tasks

Postponed on 🐛 Schema::changeField() has bug when changing regular serial field to big serial field Fixed

User interface changes

API changes

Data model changes

Release notes snippet

🐛 Bug report
Status

Fixed

Version

10.1

Component
Database 

Last updated about 8 hours ago

  • Maintained by
  • 🇳🇱Netherlands @daffie
Created by

🇬🇧United Kingdom Niall Jackson

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇦🇷Argentina dagmar Argentina

    🐛 Schema::changeField() has bug when changing regular serial field to big serial field Fixed was merged for D10 and I just provided the backport for D9.5 I think this is now unblocked.

  • 🇦🇷Argentina dagmar Argentina
  • Status changed to Needs review about 1 year ago
  • 🇦🇷Argentina dagmar Argentina

    Upgraded to work with Drupal 10.1.x. The lines from patch #49 regarding core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php now seems included in another file core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php.

    I think I also addressed comment .3 from #48 🐛 Database primary keys can exceed maximum integer storeable (has actually occurred, in watchdog) Fixed

  • 🇦🇷Argentina dagmar Argentina
  • 🇳🇱Netherlands Arantxio Dordrecht

    Thanks for this input @dagmar, I think we could make this .install function even shorter using some inspiration from issue #3191391.

    Let's see if this breaks the tests or not.

  • 🇦🇷Argentina dagmar Argentina

    I updated the issue summary to describe the proposed solution.

  • Status changed to Needs work about 1 year ago
  • 🇳🇱Netherlands daffie
    +++ b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
    @@ -0,0 +1,79 @@
    +    $insert = Database::getConnection()->insert('watchdog');
    +
    +    $connection = $this->prophesize(Connection::class);
    +    $connection->getTarget()->willReturn(DbLog::DEDICATED_DBLOG_CONNECTION_TARGET);
    +    $connection->insert('watchdog')->willReturn(new class ($insert) {
    +
    +      /**
    +       * The decorated query object.
    +       *
    +       * @var \Drupal\Core\Database\Query\Insert
    +       */
    +      private $decorated;
    +
    +      /**
    +       * Constructor for this anonymous class.
    +       *
    +       * @param \Drupal\Core\Database\Query\Insert $decorated
    +       *   The decorated query object.
    +       */
    +      public function __construct(Insert $decorated) {
    +        $this->decorated = $decorated;
    +      }
    +
    +      /**
    +       * Overrides \Drupal\Core\Database\Query\Insert::fields().
    +       *
    +       * Purposefully sets the 'wid' column of the query to a value which will
    +       * overflow the 32-bit integer limit.
    +       */
    +      public function fields(array $fields, array $values = []) {
    +        $fields['wid'] = PHP_INT_MAX;
    +        return $this->decorated->fields($fields, $values);
    +      }
    +
    +    });
    +    $this->container->set('database', $connection->reveal());
    +    $this->generateLogEntries(1);
    

    Can we replace this part with:
    - insert row into table with a value for wid that is bigger than the regular maximum integer value.
    - test that the row exists in the table with the correct value for the wid field
    - insert another row without a value for wid. With the auto-increment functionality it should get the previous wid value plus 1.
    - test that the new row exists in the table with the correct value for the wid field.

  • 🇮🇳India Akram Khan Cuttack, Odisha

    added updated patch and addressed #71

  • 🇳🇱Netherlands daffie
    1. +++ b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
      @@ -0,0 +1,78 @@
      +    $connection = $this->prophesize(Connection::class);
      

      We do not need to prophesize the database connection. Just use the regular one

    2. +++ b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
      @@ -0,0 +1,78 @@
      +      $fields['wid'] = PHP_INT_MAX;
      

      Can we change the value of PHP_MAX_INT with its value times 1000. That will be 2147483647000.

    All we need to do is to test that we can insert a value for the wid field that is bigger than 2147483647 and that for values bigger than that the auto increment functionality works. No prophesize functionality needed.

  • 🇮🇳India Akram Khan Cuttack, Odisha

    Addressed #73 The updated code inserts a row with a big value for wid and another row without a value for wid. It then tests that the first row exists with the expected value for wid and that the second row exists with the expected auto-incremented value for wid.

  • 🇳🇱Netherlands daffie
    1. +++ b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
      @@ -0,0 +1,63 @@
      +    $insert->fields(['wid' => PHP_INT_MAX]);
      ...
      +      ->condition('wid', PHP_INT_MAX)
      ...
      +      ->condition('wid', PHP_INT_MAX + 1)
      

      Can you replace PHP_INT_MAX with 2147483647000.

    2. +++ b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
      @@ -0,0 +1,63 @@
      +    $insert = Database::getConnection()->insert('watchdog');
      ...
      +    $insert = Database::getConnection()->insert('watchdog');
      ...
      +    $result = Database::getConnection()->select('watchdog')
      ...
      +    $result = Database::getConnection()->select('watchdog')
      

      Can we use the variable $connection. Please add: $connection = Database::getConnection();

  • Status changed to Needs review about 1 year ago
  • 🇮🇳India _pratik_ Banglore

    Addressed #75 comment.
    Thanks

  • Status changed to Needs work about 1 year ago
  • 🇳🇱Netherlands daffie

    @Akram Khan and @pratik: You are supposed to run the test you are working on your local machine. To make sure that it passes. Now it is not.

  • Status changed to Needs review about 1 year ago
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MySQL 5.7
    last update about 1 year ago
    29,203 pass
  • 🇳🇱Netherlands Arantxio Dordrecht

    I've adjusted the patch so the test don't fail. The dblog table has a lot of not-null fields, so we need to fill these to insert the log.

    I've also removed the use cases for FakeLogEntries and ProphecyTrait, we still pass the test and we don't need to alter the baseline anymore.

  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & sqlite-3.27
    last update about 1 year ago
    29,203 pass
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & pgsql-14.1
    last update about 1 year ago
    29,203 pass
  • Status changed to RTBC about 1 year ago
  • 🇳🇱Netherlands daffie

    All code changes look good to me.
    I have updated the IS.
    For me it is RTBC.

  • Status changed to Fixed about 1 year ago
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.69.0 2024