Problem/Motivation

The method Drupal\Core\Database\Connection::nextId() is programmed that it only works when it is used for a single sequence. It is not used in core since 10.2 #3337513: Fix batch process race conditions by making ‘bid (batch id)’ auto-increment. When a contrib or custom module starts using the method and sets the sequence ID for getting a new ID, the change is very high that sooner then later that the database will throw an error that insert a record with an existing ID.

Proposed resolution

Deprecate the method Drupal\Core\Database\Connection::nextId() and the {sequences} table. Modules should use autoincrement tables where necessary.

Remaining tasks

review/commit

User interface changes

no

API changes

  • The method Drupal\Core\Database\Connection::nextId() is deprecated and removed in Drupal 11;
  • The method Drupal\mysql\Driver\Database\mysql\nextIdDelete() is deprecated and removed in Drupal 11;
  • The sequences table is deprecated and removed in Drupal 12;
  • Calling KernelTestBase::installSchema() for the table {sequences} is deprecated.

Data model changes

CommentFileSizeAuthor
#146 2665216-146.patch77.55 KBandypost
#146 interdiff.txt707 bytesandypost
#139 2665216-139.patch77.82 KBandypost
#139 interdiff.txt1.89 KBandypost
#137 2665216-135.patch77 KBandypost
#137 interdiff.txt683 bytesandypost
#134 2665216-134.patch76.33 KBandypost
#134 interdiff.txt9.76 KBandypost
#129 2665216-129.patch76.33 KBandypost
#129 interdiff.txt8.06 KBandypost
#125 2665216-124.patch76.33 KBandypost
#125 interdiff.txt1.05 KBandypost
#123 2665216-123.patch76.33 KBandypost
#123 interdiff.txt8.16 KBandypost
#121 2665216-121.patch76.32 KBandypost
#121 interdiff.txt6.98 KBandypost
#116 xx0124d.patch5.53 KBmondrake
#115 xx0124c.patch5.53 KBmondrake
#112 xx0124a.patch4.74 KBmondrake
#107 interdiff_104_107.txt929 bytesgidarai
#107 2665216-107.patch75.78 KBgidarai
#104 interdiff_101_104.txt622 bytesgidarai
#104 2665216-104.patch75.55 KBgidarai
#101 interdiff_100_101.txt1000 bytesgidarai
#101 2665216-101.patch75.46 KBgidarai
#100 interdiff_99_100.txt109.19 KBgidarai
#100 2665216-100.patch75.46 KBgidarai
#99 2665216-99.patch74.3 KB_utsavsharma
#99 interdiff_95-99.txt1.13 KB_utsavsharma
#95 interdiff_94_95.txt684 bytesgidarai
#95 2665216-95.patch74.38 KBgidarai
#94 interdiff_89_94.txt8.6 KBgidarai
#94 2665216-94.patch73.65 KBgidarai
#89 interdiff_88_89.txt498 bytesgidarai
#89 2665216-89.patch70.36 KBgidarai
#88 2665216-88.patch70.36 KBgidarai
#71 2665216-71.patch80.16 KBdaffie
#71 interdiff-2665216-68-71.txt9.06 KBdaffie
#68 2665216-68.patch77.17 KBdaffie
#61 2665216-61.patch76.42 KBdaffie
#57 2665216-57.patch68.88 KBdaffie
#57 interdiff-2665216-56-57.txt556 bytesdaffie
#56 2665216-56.patch68.58 KBdaffie
#56 interdiff-2665216-54-56.txt1.87 KBdaffie
#54 2665216-54.patch68.23 KBdaffie
#54 interdiff-2665216-53-54.txt1.29 KBdaffie
#53 2665216-53.patch67.63 KBdaffie
#53 interdiff-2665216-51-53.txt802 bytesdaffie
#51 2665216-51.patch67.61 KBandypost
#42 2665216-42.patch65.95 KBandypost
#42 interdiff.txt626 bytesandypost
#40 2665216-40.patch66.38 KBandypost
#40 interdiff.txt1.32 KBandypost
#38 2665216-38.patch66.01 KBandypost
#38 interdiff.txt2.4 KBandypost
#36 2665216-36.patch63.91 KBandypost
#36 interdiff.txt50.22 KBandypost
#33 sequences_table_is_only-2665216-33.patch24.63 KBandypost
#26 sequences_table_is_only-2665216-26.patch25.78 KBshashikant_chauhan
#20 sequences_table_is_only-2665216-20.patch25.94 KBjofitz
#16 sequences_table_is_only-2665216-16.patch26.54 KBdimaro
#11 2665216-11.patch25.95 KBheykarthikwithu
#6 2665216-6.patch24.58 KBmohit_aghera
#2 2665216-2.patch24.7 KBdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner created an issue. See original summary.

dawehner’s picture

Status: Needs review » Needs work

The last submitted patch, 2: 2665216-2.patch, failed testing.

jibran’s picture

Issue tags: +Needs reroll

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mohit_aghera’s picture

Re-rolling patch for 8.1.x-dev

mohit_aghera’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 6: 2665216-6.patch, failed testing.

The last submitted patch, 6: 2665216-6.patch, failed testing.

heykarthikwithu’s picture

Assigned: Unassigned » heykarthikwithu
heykarthikwithu’s picture

Assigned: heykarthikwithu » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
25.95 KB
dawehner’s picture

@heykarthikwithu Please ensure to provide interdiffs in the future.

Status: Needs review » Needs work

The last submitted patch, 11: 2665216-11.patch, failed testing.

The last submitted patch, 11: 2665216-11.patch, failed testing.

dimaro’s picture

Issue tags: +Needs reroll
dimaro’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
26.54 KB

Rerolled #11.
Simple rebase fixed it.

Status: Needs review » Needs work

The last submitted patch, 16: sequences_table_is_only-2665216-16.patch, failed testing.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dimaro’s picture

Issue tags: +Needs reroll

Tagging this issue.

jofitz’s picture

Status: Needs review » Needs work

The last submitted patch, 20: sequences_table_is_only-2665216-20.patch, failed testing.

jofitz’s picture

Issue tags: -Needs reroll

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dimaro’s picture

Issue tags: +Needs reroll
ashishdalvi’s picture

Issue tags: +dcm2017

We will work on it at DCM2017

shashikant_chauhan’s picture

Rerolled the patch.

Status: Needs review » Needs work

The last submitted patch, 26: sequences_table_is_only-2665216-26.patch, failed testing.

vijaycs85’s picture

Issue tags: -Needs reroll

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

Status: Needs review » Needs work

The last submitted patch, 33: sequences_table_is_only-2665216-33.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

andypost’s picture

Clean-up all new usage and tune a bit ID generation

Also removed useless method and renamed ensureSequuencesTableExists()

Status: Needs review » Needs work

The last submitted patch, 36: 2665216-36.patch, failed testing. View results

andypost’s picture

Status: Needs review » Needs work

The last submitted patch, 38: 2665216-38.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
FileSize
1.32 KB
66.38 KB

it should fix most of failed tests

Status: Needs review » Needs work

The last submitted patch, 40: 2665216-40.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
Related issues: +#2949229: SQLite findTables Returns Empty Array on External DB.
FileSize
626 bytes
65.95 KB

Fix one more test

Status: Needs review » Needs work

The last submitted patch, 42: 2665216-42.patch, failed testing. View results

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

daffie’s picture

+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
@@ -351,7 +351,15 @@ public function nextId($existing = 0) {
-    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
+    try {
+      $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
+    }
+    catch (\Exception $e) {
+      if (!$this->ensureSequencesTableExists()) {
+        throw $e;
+      }
+      $id = 0;
+    }

Quick point: I do not think that PostgreSQL uses the sequences table.

andypost’s picture

@daffie sadly all databases doing it, and onlyto generate user id

daffie’s picture

sadly all databases doing it, and onlyto generate user id

That maybe the case, only the PostgreSQL driver does not use the sequences table to store the sequence.

andypost’s picture

@daffie as I see PG using sequence from this table instead of the table(

daffie’s picture

andypost’s picture

re-roll but it needs work for pgsql driver, just wanna make sure what bot will tell

Status: Needs review » Needs work

The last submitted patch, 51: 2665216-51.patch, failed testing. View results

daffie’s picture

daffie’s picture

andypost’s picture

@daffie Thank you a lot!

btw I'm thinking about implementation... most of databases does not need table to create sequence, the only usage of this table is user entity and batch, see #3028709: Change behavior of PostgreSQL to not share sequence for batch and users

Moreover this API (\Drupal\Core\Database\Connection::nextId() via sequences table) is more like workaround for limitations of each supported database so naming is still a question for me

- getSequencesSchema() should it remain public? as we can't define sequence with schema
- ensureSequencesTableExists() should it remain mention "table"? as other databases can create just sequences for batch and users

Probably both needs better docblocks

+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
@@ -263,7 +263,15 @@ public function nextId($existing = 0) {
+    try {
+      $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
+    }
+    catch (\Exception $e) {
+      if (!$this->ensureSequencesTableExists()) {
+        throw $e;

So only this thing left to decide, pgsql needs only named sequence but creates a table

daffie’s picture

@andypost: Thank you for the review!

Changes:
1. Changed the method getSequencesSchema() from public to protected.
2. Updated the docblock of the method ensureSequencesTableExists().
3. Removed the create table part from the PostgreSQL driver.

daffie’s picture

andypost’s picture

Used to check for nextId() usage in contrib

There's only 7 usages, mostly database drivers (mongodb, oracle, sqlserv)

Maybe decouple this api into new service and deprecate the nextId() method in connection?

It could use key_value a sequences collection instead of database specific implementation

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

daffie’s picture

Added CR and updated the Is for the fact that calling KernelTestBase::installSchema() for the table sequences is deprecated.
Rerolled the patch and added the deprecation.

mondrake’s picture

+++ b/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -170,6 +170,11 @@ abstract class Connection {

@@ -170,6 +170,11 @@ abstract class Connection {
    */
   protected $escapedNames = [];
 
+  /**
+   * The sequences table name.
+   */
+  const SEQUENCES_TABLE_NAME = 'sequences';
+
   /**
    * List of escaped table names, keyed by unescaped names.
    *
@@ -2083,4 +2088,48 @@ public function getPagerManager(): PagerManagerInterface {

@@ -2083,4 +2088,48 @@ public function getPagerManager(): PagerManagerInterface {
     return \Drupal::service('pager.manager');
   }
 
+  /**
+   * Defines the schema for the sequences table.
+   *
+   * @return array
+   *   Schema definition array.
+   */
+  protected static function sequencesSchemaDefinition() {
+    return [
+      'description' => 'Stores IDs.',
+      'fields' => [
+        'value' => [
+          'description' => 'The value of the sequence.',
+          'type' => 'serial',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ],
+      ],
+      'primary key' => ['value'],
+    ];
+  }
+
+  /**
+   * Check if the table sequences exists and create it if not.
+   *
+   * @return bool
+   *   TRUE when table sequences exists, FALSE otherwise.
+   */
+  protected function ensureSequencesTableExists() {
+    try {
+      $schema = $this->schema();
+      if (!$schema->tableExists(static::SEQUENCES_TABLE_NAME)) {
+        $schema->createTable(static::SEQUENCES_TABLE_NAME, $this->sequencesSchemaDefinition());
+        return TRUE;
+      }
+    }
+    // If another process has already created the table, attempting to
+    // recreate it will throw an exception. In this case just catch the
+    // exception and do nothing.
+    catch (SchemaObjectExistsException $e) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
 }

How about not adding this to the base Connection class, but just to the MySql and SQLite implementations? Assuming that each RDBMS should have its own way to manage sequences. Yes, it's a bit boilerplate for MySql and SQLite, but would be cleaner in general.

EDIT - BTW then you won't even need a schema, just a 'CREATE TABLE' statement that can be driver specific at that point.

daffie’s picture

@mondrake Thank you for your review!

How about not adding this to the base Connection class, but just to the MySql and SQLite implementations? Assuming that each RDBMS should have its own way to manage sequences. Yes, it's a bit boilerplate for MySql and SQLite, but would be cleaner in general.

I can move the code into a new trait called "sequencesTrait.php" and add to trait to the connection classes of MySQL and SQLite. I that a good solution?

BTW then you won't even need a schema, just a 'CREATE TABLE' statement that can be driver specific at that point.

I know I can do that and I did not because in similar situations in lib/Drupal/Core have used the same split as in this patch. This solution is also used by Batch, Cache, Config, Flood, KeyValue, Lock, Menu, Queue and Routing.

I am only a bit worried about a BC break. This happens when you use a contrib database driver to create a new site. The problem that I do not see how this can be fixed in core. Maybe it would be better to fix this in the contrib database drivers. What do you think?

andypost’s picture

I see no reason in trait and the table at all, it used to store only 1 row which could be stored in key value table - no extra API needed for that

mondrake’s picture

#64 I'm not sure. That would mean replacing a DB feature (using sequences where supported by the RDBMS, like in PGSql and Oracle, or emulating them, like in MySql and SQLite) with a service which is much more overhead. IIUC this issue's focus is on cleaning up the DB API layer.

Separate discussion IMHO is if we need Connection::nextId() at all in core: it seems to be used only in UserStorage and form.inc, and mentioned in KeyValueEntityStorage but not actually used. If we can find alternatives there, we might deprecate nextId altogether, but I'd say this is for a separate issue.

daffie’s picture

Maybe it is better to look if we can deprecate the Connection::nextId() functionality.

We have 2 instances in core that use the functionality:

  1.     // Assign an arbitrary id: don't rely on a serial column in the 'batch'
        // table, since non-progressive batches skip database storage completely.
        $batch['id'] = \Drupal::database()->nextId();
      

    I think we can replaced it here by just generating a random integer.

  2.     $entity->uid->value = $this->database->nextId($this->database->query('SELECT MAX([uid]) FROM {' . $this->getBaseTable() . '}')->fetchField());
      

    I do not understand we just do not use a serial field for the user id. It is an entity like all other entities. Or is it for user id zero is the anonymous user. Is that the problem? If so, is it still valid?
    Can we solve it in another way, something specific in the user module and deprecate it in the database system? Let the user module store the value in the keyvalue table.

It would be great to remove this from the database system.

@andypost and @mondrake: Thank you for your help!

mondrake’s picture

BTW, this

$entity->uid->value = $this->database->nextId($this->database->query('SELECT MAX([uid]) FROM {' . $this->getBaseTable() . '}')->fetchField());

is rather bad because if anything else than UserStorage is using the sequence generated by nextId() and expecting it to be incrementing, this statement may (ok, in edge situations) reset the sequence to a lower value if the max(id) of the user base table is lower than the current value of the sequence.

So... now I am +1 for deprecating instead.

daffie’s picture

@mondrake If you like the patch then I will update the title, summary and the CR. I have deprecated the methodConnection::nextId() and the table sequences. The table can be removed in D10. The usage of the method have been replaced in form.inc and userstorage.php.

mondrake’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

IMHO #68 makes sense; we need an IS update to reflect the new direction of the issue.

  1. +++ b/core/includes/form.inc
    @@ -898,7 +898,7 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
    -    $batch['id'] = \Drupal::database()->nextId();
    +    $batch['id'] = mt_rand(1, 2147483647);
    

    Maybe overkilling here: would it make sense to use Lock::acquire() to lock the ID while in use by the batch?

  2. +++ b/core/modules/system/system.install
    @@ -1326,6 +1326,8 @@ function system_install() {
    +  // @deprecated The sequences table has been deprecated in drupal:9.3.0 and is
    +  // removed from drupal:10.0.0. See https://www.drupal.org/node/3220378.
    

    Not sure using @deprecated is correct here. Maybe just open a followup for the removal in D10 and use @todo to refer to it. BTW I think in D10 we should also have an update function that will actually remove the {sequences} table in live sites.

  3. +++ b/core/modules/user/src/UserStorage.php
    @@ -21,8 +21,14 @@ protected function doSaveFieldItems(ContentEntityInterface $entity, array $names
    +      $key_value = \Drupal::service('keyvalue');
    

    I think this can be injected, by overriding the ::createInstance and ::__construct methods.

  4. +++ b/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php
    @@ -194,6 +192,8 @@ public function testScriptLoad() {
    +    // Special handling of sequences table which is created on demand.
    +    $schema->dropTable('sequences');
    

    Is this still needed?

  5. +++ b/core/tests/Drupal/KernelTests/Core/Database/NextIdTest.php
    @@ -46,6 +62,8 @@ public function testDbNextId() {
    +    $this->expectDeprecation('Connection::nextId() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Modules should use instead the keyvalue storage for the last used id. See https://www.drupal.org/node/3220378');
    +
         // Only run this test for the 'mysql' driver.
         $driver = $this->connection->driver();
    

    expectDeprecation should be moved to after the block checking if the test should be skipped.

daffie’s picture

Title: sequences table is only used by a core service but it depends on system install » Deprecate Drupal\Core\Database\Connection::nextId(), the sequences table and KernelTestBase::installSchema()
Issue summary: View changes
Issue tags: -Needs issue summary update

Updated the title, the IS and the CR.

daffie’s picture

@mondrake: Thank you for your review.

For 69.1: I have changed the solution to use the keyvalue store to store the batch ID. The Lock::acquire() look to me as a bit of a overkill and such a lock does expire. Where a keyvalue store one does not.

For #69.2: I am not sure what the correct way is to deprecate a table. I do not think we have one. My idea was that the release manager would do a code base search for "@deprecated" before he/she would release D10.0.0 and therefor the comment would be found and fixed. I have less confidence that a @todo will be fixed in the D10.0.0 release. Technically I think that a @todo is better then a @deprecated. Maybe I should ask a release manager about this. ;-)

For #69.3: Done!

For #69.4: Removed!

For #69.5: Done!

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Assuming this is green, it's RTBC for me now.

I was wondering whether we should also deprecate Connection::makeSequenceName(). It's only used internally by PgSql so maybe it does no longer need to be public API. But that could be a followup.

daffie’s picture

For the official BC policy (https://www.drupal.org/about/core/policies/core-change-policies/drupal-8...):

While the database query API and schema API itself are stable, the schema of any particular table is not. Writing queries directly against core tables is not recommended and may result in code that breaks between minor versions.

mondrake’s picture

Title: Deprecate Drupal\Core\Database\Connection::nextId(), the sequences table and KernelTestBase::installSchema() » Deprecate Drupal\Core\Database\Connection::nextId(), the {sequences} table and KernelTestBase::installSchema()
mondrake’s picture

Title: Deprecate Drupal\Core\Database\Connection::nextId(), the {sequences} table and KernelTestBase::installSchema() » Deprecate Drupal\Core\Database\Connection::nextId() and the {sequences} table and schema
Issue summary: View changes

Adjusting title, we are not deprecating KernelTestBase::installSchema() altogether.

mondrake’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/user/src/UserStorage.php
    @@ -14,6 +24,57 @@
    +    $this->keyValueStore = $key_value_store;
    

    We need to make this optional and trigger a deprecation if it is not passed in. Just in case.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Database/NextIdTest.php
    @@ -23,13 +24,28 @@ class NextIdTest extends DatabaseTestBase {
    -    $this->installSchema('system', 'sequences');
    +
    +    $table_specification = [
    +      'description' => 'Stores IDs.',
    +      'fields' => [
    +        'value' => [
    +          'description' => 'The value of the sequence.',
    +          'type' => 'serial',
    +          'unsigned' => TRUE,
    +          'not null' => TRUE,
    +        ],
    +       ],
    +      'primary key' => ['value'],
    +    ];
    +    $this->connection->schema()->createTable('sequences', $table_specification);
    

    This change is not necessary - it is a legacy test so triggering a deprecation won't cause the test to fail and then we'll keep test coverage of system_schema()

  3. I think we need to deprecate \Drupal\Core\Database\Driver\mysql\Connection::nextIdDelete() too. It now should never be called during regular runtime - which means in D10 \Drupal\Core\Database\Driver\mysql\Connection::__destruct() can be removed.
  4. One big concern I have is database locking while multiple processes create users. The current implementation of nextId() is highly performant for getting the next ID - but the clean-up can cause issues.
  5. We need an update path to migrate the current value to state - if this is where we should store it.
andypost’s picture

@alexpott Looking at #1650930: Use READ COMMITTED by default for MySQL transactions I'm not sure 4) is "highly performant"

+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
@@ -309,7 +309,11 @@ public function mapConditionOperator($operator) {
     $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', [], ['return' => Database::RETURN_INSERT_ID]);
     // This should only happen after an import or similar event.
     if ($existing_id >= $new_id) {

It makes 3 queries instead of using auto-increment

https://git.drupalcode.org/project/drupal/-/blob/9.3.x/core/lib/Drupal/C...

alexpott’s picture

@andypost the three queries don't happen in usually... normally it is just $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', [], ['return' => Database::RETURN_INSERT_ID]); and an insert is definitely quicker than what we're doing with state... we're doing a select and update on every user creation. And the cleanup of the sequences table does not occur on user time but at shutdown.

catch’s picture

Reading the recent comments got me thinking about trying to use NO_AUTO_VALUE_ON_ZERO and autoincrement for users.

@alexpott found #838992: Change the uid field from integer to serial by leveraging NO_AUTO_VALUE_ON_ZERO on MySQL which I'll move to Drupal 9 now.

If that's possible, I think we'd need:

1. An issue to change the setting
2. An issue specifically for user storage to switch to autoincrement away from sequences
3. This issue minus the user bits.

alexpott’s picture

#80 sounds like a great plan - also looking at the Batch stuff I;'m not sure what that uses this either - the queue table is a serial and maybe the memory stuff could create it's own ID... I think we should delegate ID creation to the batch backend and not use key value.

Ghost of Drupal Past’s picture

The current implementation of the sequences API, while it has it's own problems, is atomic.

The new patch, on the other hand is race prone.

  1. Process #1 reads the K-V value, it's X
  2. Process #2 reads the K-V value, it's X
  3. Process #1 increments X (in PHP)
  4. Process #2 increments X (in PHP)
  5. Process #1 saves the database record
  6. Process #2 also saves the database record with the same ID. Hopefully, a UNIQUE index is set which causes it to die instead of corrupting the database. If it handles the database error then it can retry, however there is no guarantee it won't be stuck in a retry loop for example if two drush commands are running in parallel which inserts user records.

Eliminating the uses of this in core is valuable and adding a note discouraging users from it is also great. Moving the cleanup to cron to increase performance would be a separate fix. But I would not remove this useful API as there is no replacement for it.

Ghost of Drupal Past’s picture

The method Drupal\Core\Database\Connection::nextId() is programmed that it only works when it is used for a single sequence. When a contrib or custom module starts using the method and sets the sequence ID for getting a new ID, the chance is very high that sooner then later that the database will throw an error that insert a record with an existing ID.

While it certainly returns only one sequence of integers it never reuses them so there's no way this can happen. If a module starts using this API where existing IDs exist the API provides for this. I do not understand this.

Also when getting a new bach ID, the existing sequence Id is not changed.

I do not understand this either. Every nextId changes the sequence value. That's what nextId does. It is the only thing it does.

So I think this issue should revert to #61 and also some other fixes as mentioned already would be valuable:

  1. #838992: Change the uid field from integer to serial by leveraging NO_AUTO_VALUE_ON_ZERO on MySQL
  2. PP on that, remove the use of nextId in userstorage
  3. Remove the use of nextId in batch separately from this issue
  4. Move cleanup to cron to avoid a stampede in #2886441: Sequences API (mysql) kills MySQL server performance due to cleanup phase
  5. Document the usage of nextId is discouraged and the return value of insert execute is preferred.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

gidarai’s picture

Assigned: Unassigned » gidarai
Status: Needs work » Needs review
FileSize
70.36 KB

I have rerolled the patch for 10.1.x

gidarai’s picture

FileSize
70.36 KB
498 bytes

fixing coding standards

gidarai’s picture

Style guide fix (previous comments where not added because of spam protection)

Status: Needs review » Needs work

The last submitted patch, 2665216-89.patch, failed testing. View results

andypost’s picture

Needs codestyle fixes and deprecation version

+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -1653,6 +1653,11 @@ public function commit() {
+   * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Modules

+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -739,6 +739,9 @@ protected function installSchema($module, $tables) {
+        @trigger_error('Installing the table sequences with the method KernelTestBase::installSchema() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. The table is now lazy loaded and therefore will be installed automatically when used. See https://www.drupal.org/node/3143286', E_USER_DEPRECATED);

it should be 10.1.0 and removed from 11.0.0

gidarai’s picture

Code style fixes and deprecation version changes

gidarai’s picture

FileSize
74.38 KB
684 bytes

missing part of previous patch added to current patch

The last submitted patch, 94: 2665216-94.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 95: 2665216-95.patch, failed testing. View results

daffie’s picture

+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -739,6 +739,9 @@ protected function installSchema($module, $tables) {
+        @trigger_error('Installing the table sequences with the method KernelTestBase::installSchema() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. The table is now lazy loaded and therefore will be installed automatically when used. See https://www.drupal.org/node/3143286', E_USER_DEPRECATED);

This text is not right. The part "The table is now lazy loaded and therefore will be installed automatically when used." can be removed.

_utsavsharma’s picture

Addressed #98.
Please review.

gidarai’s picture

Status: Needs work » Needs review
FileSize
75.46 KB
109.19 KB

Fixed failing test (NextIdTest)

gidarai’s picture

Removed white space before a closing "}"

catch’s picture

+++ b/core/includes/form.inc
@@ -899,7 +899,9 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
 
     // Assign an arbitrary id: don't rely on a serial column in the 'batch'
     // table, since non-progressive batches skip database storage completely.
-    $batch['id'] = \Drupal::database()->nextId();
+    $key_value_store = \Drupal::service('keyvalue')->get('batch');
+    $batch['id'] = $key_value_store->get('batch_id', 0) + 1;
...
 

I think it's probably fine to start the batch ID from 0 again rather than having an update to copy over from sequences, but just noting in case we think it would be worth it? Everything looks good here otherwise.

We'll need an explicit Drupal 11 (or even Drupal 12?) follow-up to add an update to drop the table.

mondrake’s picture

+++ b/core/includes/form.inc
@@ -899,7 +899,9 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
+    $key_value_store = \Drupal::service('keyvalue')->get('batch');
+    $batch['id'] = $key_value_store->get('batch_id', 0) + 1;
+    $key_value_store->set('batch_id', $batch['id']);

The only concern I have here is that if by chance this happens in the context of a transaction, and the transaction gets rolled back, the counter would be rolled back as well? But I think it's the same with the current API anyway. In the past I saw doctrine/dbal supporting a sequence proxy - but they were opening a separate connection just for the purpose of not meddling with transactional operations.

gidarai’s picture

Added the requested comment by @catch.

longwave’s picture

+++ b/core/includes/form.inc
@@ -899,7 +899,9 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
+    $key_value_store = \Drupal::service('keyvalue')->get('batch');
+    $batch['id'] = $key_value_store->get('batch_id', 0) + 1;
+    $key_value_store->set('batch_id', $batch['id']);

@chx's concerns about race conditions here still apply, as far as I can see.

If modules that previously used sequences are expected to do this themselves, should we add an explicit API that can safely increment a value and return it? Other key value stores such as Memcache and Redis offer an atomic increment operation, for example. This could be part of the keyvalue API or a separate service.

longwave’s picture

Alternatively, why do non-progressive batches even need an ID, if it is never stored? If we can remove that then we can make bid a serial column and then we never need sequences in core at all.

gidarai’s picture

FileSize
75.78 KB
929 bytes

Changed patch to use the lock service to prevent batches from getting the same ID when multiple batch processes try to get a new batch ID by locking the batch process.

longwave’s picture

+++ b/core/includes/form.inc
@@ -899,7 +899,15 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
+    if ($lock->acquire('batch')) {

Seems unlikely, but we need to do something if we fail to acquire the lock, as we won't have an ID to use.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@chx's concerns about race conditions here still apply, as far as I can see.

The concern has been addressed.
Back to RTBC.

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/includes/form.inc
@@ -899,7 +899,15 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
+    // processes try to get a new batch ID.
+    $lock = \Drupal::lock();
+    if ($lock->acquire('batch')) {
+      $batch['id'] = $key_value_store->get('batch_id', 0) + 1;
+      $key_value_store->set('batch_id', $batch['id']);
+      $lock->release('batch');
+    }
 

This won't work - it would mean no batch ID is assigned at all if another process has a lock, which is as or more likely than the original race condition.

daffie’s picture

As the solution with the lock service does not work, lets try the solution from @mondrake. See: https://www.drupal.org/project/drupal/issues/3257824#comment-14883465.

mondrake’s picture

Here's an updated version of the patch referenced in #111.

Unfortunately there is a problem with the update path here - the update itseltf is run in a batch, so using the new code with the old table before it is changed by the upate function is failing.

catch’s picture

It's not pretty, but for similar problems, we've hard-coded the database queries in the code that actually runs updates so that it's guaranteed to run before any updates do at all. Obviously this will run every time until that code is removed again in Drupal 11, so needs to be re-entrant and ideally do a minimal check so it can return early.

mondrake’s picture

Confused about why this is not failing on SQLite too.

mondrake’s picture

Let's see this. I think, however, we need a separate issue to do this one, and postpone this one on that.

mondrake’s picture

Tried understanding why #112 was not failing on SQLite, and it looks like when an int is defined as primary key, it is automatically incremented when no value is passed for the PK in INSERT.

The {batch} table is created like

    CREATE TABLE "test35615194"."testmk2" (\n
    "bid" INTEGER NOT NULL CHECK ("bid">= 0), \n
    "token" VARCHAR(64) NOT NULL, \n
    "timestamp" INTEGER NOT NULL, \n
    "batch" BLOB NULL DEFAULT NULL, \n
     PRIMARY KEY ("bid")\n
    )

Then inserting

  'sql' => "INSERT INTO "test35615194"."testmk2" ("timestamp", "token", "batch") VALUES (?, ?, ?)"
  'args' => array:3 [
    0 => 1674582649
    1 => ""
    2 => null
  ]

Yields for SELECT * FROM "test35615194"."testmk2"

  0 => {#3557
    +"bid": "1"
    +"token": ""
    +"timestamp": "1674582649"
    +"batch": null
  }

You never stop learning...

Anyway, for the purpose here this is just fine.

gidarai’s picture

Status: Needs work » Postponed

Changed status to 'postponed' (didn't do it before)

catch’s picture

andypost’s picture

Status: Needs work » Needs review
FileSize
6.98 KB
76.32 KB

Re-roll for #107 and few changes

The test core/modules/mysql/tests/src/Kernel/mysql/NextIdTest.php is running only on mysql, so probably needs other base test to use

daffie’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -739,6 +739,9 @@ protected function installSchema($module, $tables) {
+        @trigger_error('Installing the table sequences with the method KernelTestBase::installSchema() is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3143286', E_USER_DEPRECATED);

Tiny nitpick: We are going to remove the sequences table in Drupal 12 not Drupal 11.

andypost’s picture

Filed new CR (restored from revision) https://www.drupal.org/node/3349345 and said for removal in 12.0 and updated all deprecations

andypost’s picture

andypost’s picture

daffie’s picture

@andypost: The plan is to remove the method Connection::nextId() in Drupal 11 and to remove the table "sequences" in Drupal 12. We wait with dropping the sequences table because we cannot deprecate a database table. That is the plan for now. Better ideas are welcome!

Status: Needs review » Needs work

The last submitted patch, 125: 2665216-124.patch, failed testing. View results

andypost’s picture

I see no reason to hack into db layer to try throw when table is used, so need to revert 12 to 11 and keep in KernelTestBase

andypost’s picture

daffie’s picture

Issue summary: View changes
andypost’s picture

mondrake’s picture

Status: Needs review » Postponed
mondrake’s picture

Issue summary: View changes
Status: Postponed » Needs work

Blocker is in.

andypost’s picture

re-roll for 10.2

andypost’s picture

Version: 10.1.x-dev » 11.x-dev
Assigned: gidarai » Unassigned
daffie’s picture

Status: Needs review » Needs work

The testbot is failing.

andypost’s picture

New WorkspaceAssociationTest usage fixed

mondrake’s picture

Status: Needs review » Needs work

For PostgreSQL MySql, shouldn't public function nextIdDelete() in its Connection class be deprecated as well?

andypost’s picture

I bet you meaning Mysql driver because there's only usage of this function could be found, which was added in #3129043: Move core database drivers to modules of their own

Added deprecation for #138

mondrake’s picture

#139 - yes, and I need more coffee. Thanks!

andypost’s picture

Status: Needs work » Needs review
daffie’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

All code changes look good to me.
All deprecations have testing.
All use of the deprecated methods have been removed.
I have updated the IS and the CR for the deprecation of Drupal\mysql\Driver\Database\mysql\nextIdDelete().
For me it is RTBC.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 139: 2665216-139.patch, failed testing. View results

daffie’s picture

Status: Needs work » Reviewed & tested by the community
mondrake’s picture

Status: Reviewed & tested by the community » Needs work

Needs a reroll. And, BTW, I think MySql Connection::$needsCleanup property should be deprecated too.

andypost’s picture

Test core/modules/file/tests/src/Kernel/Views/RelationshipUserFileDataTest.php is fixed via #2628230: Adding File Usage "File" relationship results in broken/missing handler

Added deprecation for #145 as the property protected, I don't think we need magic getter/setter to deprecate it

daffie’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Read back through this issue and saw all the comments relating to the batch and users table that we eventually split out to other issues with much better implementations, nice to finally get to the point where all this issue is doing is adding deprecations!!

Committed 6204ba9 and pushed to 11.x. Thanks!

  • catch committed 6204ba9c on 11.x
    Issue #2665216 by andypost, gidarai, daffie, mondrake, _utsavsharma,...
catch’s picture

Issue summary: View changes
andypost’s picture

Thank you! I renamed follow-up from D12 to D11 - #3335756: Drop sequences table in Drupal 12

andypost’s picture

CR looks ready to be published but I think we should remove the table in D12

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.