Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 293784

Invalidating 'node_list' and other broad cache tags early in a transaction severely increases lock wait time and probability of deadlock

$
0
0

Problem/Motivation

Suppose you have some content entity type (foo) with an entity reference to nodes, and you have contrib or custom code that needs to update something about the referenced nodes whenever the host entity is updated. So you implement hook_foo_presave() or hook_foo_update(), and in that function you load the referenced nodes, do something to them, and save them.

By design, all of this happens within a single database transaction: SqlContentEntityStorage::save() starts the transaction prior to invoking hook_foo_presave() and doesn't commit the transaction until after all hook_foo_update() implementations complete.

Meanwhile, when the first child node is saved, Entity::invalidateTagsOnSave() invalidates several broad tags ('node_list' and '4xx-response') that are not specific to the particular node, which causes DatabaseCacheTagsChecksum::invalidateTags() to send a merge (UPDATE) query to the corresponding records in the {cachetags} table.

This causes the database engine (e.g., InnoDB) to acquire a row lock on those records, which it doesn't release until the end of the transaction. However, if there are more child nodes that need to be saved, or other slow hook implementations that need to be performed, prior to the end of the transaction, then these locks could last 10, 20, or more seconds.

Now suppose you have concurrent users editing foo entities on this site. Even if they're editing different foo entities with different child nodes, they all end up competing for the same lock on the 'node_list' cache tag. Depending on how long they each need to wait, this could end up appearing as a deadlock, or in some cases, even lead to real deadlock.

Proposed resolution

Change DatabaseCacheTagsChecksum to delay updating the database with cache tag invalidations until just before the end of the transaction. However, also have its isValid() return FALSE while there are not-yet-committed invalidations. This way, other requests continue seeing the old value until the end of the transaction (which is already the case in HEAD, since that's how transactions work), while later code in the same request (e.g., other presave/update hooks) continues to see the invalidation as having already occurred (just as in HEAD).

If done right, I think this is fully backwards compatible, while reducing to practically 0 the time that a {cachetags} record lock is held.

Remaining tasks

  • Write patch.
  • Review if the idea and implementation are sound.
  • Figure out what test coverage to add.

User interface changes

None.

API changes

A public method addition to Drupal\Core\Database\Connection for adding callbacks to run just prior to transaction commit.

Data model changes


Viewing all articles
Browse latest Browse all 293784

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>