Race conditions with lock/cache, session storage - add a non-transactional database connection

Created on 30 September 2014, over 9 years ago
Updated 22 June 2024, 6 days ago

Contact person responsible and ready to help

@Fabianx

Problem/Motivation

Drupal promises you can use different cache and lock backends like Memcache, Redis, etc. instead of the Database backend but that is essentially not true:

- Because Drupal uses transactions, and the same database connection is used for the cache and lock backends, there is an implicit coupling of the transaction to the cache or lock consistency.

Examples:

function x() {
  lock_acquire();
  $transaction = db_transaction();
  lock_release();
}

Looks right doesn't it? And it will always work correctly, because the lock will get released at the same time as the transaction, BUT as soon as you use another backend, you have introduced a race condition, where the lock is released, before the new data was written.

Correct would be:

function x() {
  lock_acquire();

  $transaction = db_transaction();
  $transaction->commit();

  lock_release();
}

@see #356399: Optimize the route rebuilding process to rebuild on write โ†’

Another example is the recent usage of drupal.org having to use the cache_consistent module.

Here the race condition was more like:

  node_save($node);
  // This invalidates the field cache -> with DB: okay, with memcache: BOOM
  // A new request comes in and loads the old node
  // Repopulates the field cache.
  // Now the transaction is committed.

All of this happens, because core takes an implicit coupling of cache, lock and database for granted.

Proposed resolution

- Use a different DB connection for cache and lock backends, so that writes / reads to / from those systems are no longer coupled.
- Obviously there is a drawback to several connections, so lets make that configurable.

BUT the default at least for the test suite should be to use one DB connection per backend.

OR: if we never use transactions on lock() or cache() backends, we could have one DB connection that is transactional and one that is not.

Remaining tasks

User interface changes

API changes

๐Ÿ› Bug report
Status

Needs work

Version

11.0 ๐Ÿ”ฅ

Component
Databaseย  โ†’

Last updated about 12 hours ago

  • Maintained by
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands @daffie
Created by

๐Ÿ‡ฉ๐Ÿ‡ชGermany Fabianx

Live updates comments and jobs are added and updated live.
  • Triaged core major

    There is consensus among core maintainers that this is a major issue. Only core committers should add this tag.

  • Needs issue summary update

    Issue summaries save everyone time if they are kept up-to-date. See Update issue summary task instructions.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

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

  • The Needs Review Queue Bot โ†’ tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide โ†’ to find step-by-step guides for working with issues.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom catch

    This came up in #2833539-134: SQLSTATE[40001]: Serialization failure: 1213 Deadlock โ†’ - see the comment which reaches similar conclusions to this issue (but also has at least one possibly alternative approach).

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States greenSkin

    Re-rolled patch from #41.

  • last update 11 months ago
    Patch Failed to Apply
  • last update 11 months ago
    Custom Commands Failed
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada joseph.olstad
     โ•ฐโฏ $ diff D10.0.x-nontransactional_connection-2347867-61.patch D10.1.x-nontransactional_connection-2347867-62.patch 
    2c2
    < index e3c1e094..f2ed065d 100644
    ---
    > index ed531f5027..13bbd46cdd 100644
    5c5
    < @@ -213,7 +213,7 @@ services:
    ---
    > @@ -220,7 +220,7 @@ services:
    14,15c14
    < @@ -386,6 +386,10 @@ services:
    <      class: Drupal\Core\Database\Connection
    ---
    > @@ -405,6 +405,10 @@ services:
    17a17
    >    Drupal\Core\Database\Connection: '@database'
    25,26c25,26
    < @@ -485,7 +489,7 @@ services:
    <        - [setContainer, ['@service_container']]
    ---
    > @@ -527,7 +531,7 @@ services:
    >    Drupal\Core\Queue\QueueFactory: '@queue'
    30a31
    >    Drupal\Core\Queue\QueueDatabaseFactory: '@queue.database'
    33,34c34
    <      arguments: ['@request_stack']
    < @@ -870,13 +874,13 @@ services:
    ---
    > @@ -959,13 +963,13 @@ services:
    51c51
    < index 96ac2ee9..78cac386 100644
    ---
    > index 96ac2ee948..78cac38694 100644
    63,64c63,64
    < diff --git a/core/tests/Drupal/KernelTests/Core/Database/TransactionTest.php b/core/tests/Drupal/KernelTests/Core/Database/TransactionTest.php
    < index e41df19c..c1145c5f 100644
    ---
    > diff --git a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php
    > index ebbf60826a..e09d8dce65 100644
    75c75
    < @@ -573,4 +574,54 @@ public function testQueryFailureInTransaction() {
    ---
    > @@ -575,4 +576,54 @@ public function testQueryFailureInTransaction() {
    
  • last update 11 months ago
    Custom Commands Failed
  • last update 11 months ago
    Custom Commands Failed
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada joseph.olstad

    According to the current dictionary, nontransactional is not a word.

    I would argue otherwise as this makes perfect sense to those of us who know what transactional processing is in a relational database sense from a contemporary RDBMS world.

    How do we go about adding nontransactional to the dictionary so that we can continue with automated testing?

  • ๐Ÿ‡จ๐Ÿ‡ญSwitzerland Berdir Switzerland

    You can add it to misc/cspell/dictionary.txt.

  • last update 11 months ago
    Custom Commands Failed
  • last update 11 months ago
    Custom Commands Failed
  • last update 11 months ago
    Custom Commands Failed
  • last update 11 months ago
    Custom Commands Failed
  • last update 11 months ago
    Custom Commands Failed
  • last update 11 months ago
    29,458 pass
  • last update 11 months ago
    29,911 pass
  • last update 11 months ago
    Patch Failed to Apply
  • last update 11 months ago
    29,458 pass
  • 25:11
    24:21
    Running
  • Status changed to Needs review 11 months ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada joseph.olstad

    Rerolled, has tests, passes tests

  • Status changed to Needs work 11 months ago
  • The Needs Review Queue Bot โ†’ tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide โ†’ to find step-by-step guides for working with issues.

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada joseph.olstad

    Rerolled 66 to resolve a little fuzz on the 11.x patch
    the 10.1.x patch still applies cleanly.

  • Status changed to Needs review 11 months ago
  • last update 11 months ago
    29,961 pass
  • Status changed to Needs work 10 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    This looks good!

    Wonder if a change record could be added though for the new service database.nontransactional? If it's something that other contrib modules could leverage.

    Think it could be RTBC after that.

    Thanks.

  • ๐Ÿ‡จ๐Ÿ‡ฆCanada joseph.olstad

    @smustgrave, I have created a draft change record.

    https://www.drupal.org/node/3384048 โ†’

  • Status changed to RTBC 10 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Thanks!

  • last update 10 months ago
    Patch Failed to Apply
  • Status changed to Needs work 10 months ago
  • ๐Ÿ‡จ๐Ÿ‡ฆCanada joseph.olstad

    latest patch needs a reroll

  • Status changed to Needs review 10 months ago
  • last update 10 months ago
    30,139 pass
  • ๐Ÿ‡ท๐Ÿ‡บRussia kostyashupenko Omsk

    Rerolled

  • Status changed to Needs work 10 months ago
  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    This is a good idea, but as it is this patch is practically not doing what it's meant to do.

    +++ b/core/core.services.yml
    @@ -415,6 +415,10 @@ services:
    +  database.nontransactional:
    +    class: Drupal\Core\Database\Connection
    +    factory: Drupal\Core\Database\Database::getConnection
    +    arguments: [nontransactional]
    

    This service will try to fetch a db connection with 'nontransactional' as a target, which unless it is defined in the settings.php, will fall back to the same 'default' target, see this code in Database::getConnection:

        // If the requested target does not exist, or if it is ignored, we fall back
        // to the default target. The target is typically either "default" or
        // "replica", indicating to use a replica SQL server if one is available. If
        // it's not available, then the default/primary server is the correct server
        // to use.
        if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) {
          $target = 'default';
        }
    

    So you would still doing the cache and log operations on the same default Connection object.

    The test works because you are preliminarily adding the 'nontransactional' database target in

    +++ b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php
    @@ -680,4 +681,55 @@ public function testConnectionDeprecations(): void {
    +    // Setup a secondary database connection.
    +    $connection_info = Database::getConnectionInfo('default');
    +    Database::addConnectionInfo('default', 'nontransactional', $connection_info['default']);
    

    So something will have to happen similarily in the getConnection() method if (hopefully) we won't be changing settings.php.

    I also think if we open such a transaction-free connection, we should prevent it from executing startTransaction(). Could be a follow up, but IMHO it would be better to get it straight from the beginning.

  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Also, the IS could use some updatesโ€ฆ the code is still D7.

  • First commit to issue fork.
  • Merge request !8273Decorator pattern โ†’ (Open) created by bradjones1
  • Pipeline finished with Failed
    24 days ago
    Total: 177s
    #190164
  • Pipeline finished with Failed
    24 days ago
    Total: 597s
    #190168
  • Pipeline finished with Failed
    23 days ago
    Total: 569s
    #191080
  • Pipeline finished with Failed
    23 days ago
    Total: 720s
    #191124
  • Pipeline finished with Failed
    23 days ago
    Total: 3777s
    #191170
  • Pipeline finished with Failed
    23 days ago
    Total: 185s
    #191212
  • Pipeline finished with Failed
    23 days ago
    Total: 489s
    #191217
  • Pipeline finished with Failed
    22 days ago
    Total: 174s
    #192050
  • Pipeline finished with Success
    22 days ago
    Total: 567s
    #192057
  • Status changed to Needs review 22 days ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States bradjones1 Digital Nomad Life

    OK, marking this NR.

    Digging into this a bit, I discovered/decided a few things:

    • Most of Drupal core expects that you'll really only have one default target and (maybe) a replica target. Non-replica targets for a database key, especially default, aren't anticipated in production and this results in a required bugfix here. Basically, the assumption was that if there were more than one target, it must be a replica, and thus we need to disable replicas during certain actions. This was discovered during a regression surfaced in testing where the session was re-started due to the replica kill switch service.
    • Speaking of replicas, we use that term a lot around the DB API and it's not entirely clear that replica is a reserved word that specifically designates a replica or group of replica connection targets. It was particularly difficult to find all instances of this "DB replica" vs. other uses of the word. I think constants are a great answer to this question, so in the course of fixing above bug in adding this functionality, I extracted all these words into constants.
    • I landed on a decorator pattern for the non-transactional connection, which wraps the underlying driver's connection object. As I have navigated in the course of my work on JSON data support, messing with signatures for the various connection classes and interfaces is no small feat, and there are also contrib drivers to think about. Decoration is a good solution here, but as you can see it results in a lot of boilerplate. I marked this class both final and internal, and it did still require a change broadening the potential return types of connection methods. (see the event enable and disable methods.) As I expect this to really be an internal API (there's little to no reason to override the non-transactional decorator class) I'm hoping this is sufficient. Otherwise we need to add a bunch of code to the connection classes that boils down to throwing an exception if you try and start a transaction on this connection. I don't think that's worth it.
    • This also required a few tweaks to performance testing (though with no real change in performance) due to there now being two connections always in play - transactional and non.
  • Pipeline finished with Success
    22 days ago
    Total: 579s
    #192144
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States bradjones1 Digital Nomad Life
  • ๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

    Great idea tp use a decorator. Added some comments/ideas inline - free thoughts.

  • Pipeline finished with Success
    21 days ago
    Total: 543s
    #193146
  • Status changed to Needs work 15 days ago
  • The Needs Review Queue Bot โ†’ tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide โ†’ to find step-by-step guides for working with issues.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States bradjones1 Digital Nomad Life
Production build 0.69.0 2024