Problem/Motivation

On a production site if I have two runners both processing the same queues I hit deadlocks.

 Executing: /var/www/html/vendor/bin/drush advancedqueue:queue:process entity_usage_custom_views --timeout=20 --uri=REDACTED [1088.15 sec, 14.75 MB]
> 
> In ExceptionHandler.php line 96:
>                                                                                
>   SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to   
>   get lock; try restarting transaction: UPDATE "advancedqueue" SET "payload"=  
>   :db_update_placeholder_0, "state"=:db_update_placeholder_1, "message"=:db_u  
>   pdate_placeholder_2, "num_retries"=:db_update_placeholder_3, "available"=:d  
>   b_update_placeholder_4, "processed"=:db_update_placeholder_5, "expires"=:db  
>   _update_placeholder_6                                                        
>   WHERE "job_id" = :db_condition_placeholder_0; Array                          
>   (                                                                            
>       [:db_update_placeholder_0] => {"entity_type":"media","entity_id":2468}   
>       [:db_update_placeholder_1] => success                                    
>       [:db_update_placeholder_2] =>                                            
>       [:db_update_placeholder_3] => 0                                          
>       [:db_update_placeholder_4] => 1787741344                                 
>       [:db_update_placeholder_5] => 1787741580                                 
>       [:db_update_placeholder_6] => 0                                          
>       [:db_condition_placeholder_0] => 237422                                  
>   )                                                                            
>                                                                                
> 
> In PdoTrait.php line 109:
>                                                                                
>   SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to   
>   get lock; try restarting transaction  

Proposed resolution

This occurs because we call cleanupQueue a lot and this updates lots of rows using the secondary index and claim and update use the primary index to grab a single row.

The fix is to:

  1. Scope cleanup to the current queue: add ->condition('queue_id', $this->queueId) to the cleanupQueue() update. Cuts cross-queue contention entirely (the biggest win, one line).
  2. Replace the range UPDATE with select-ids-then-update-by-PK: SELECT job_id FROM {advancedqueue} WHERE queue_id = :qid AND expires <> 0 AND expires < :now ORDER BY job_id ASC, then UPDATE ... WHERE job_id IN (:ids). This swaps secondary-index gap locks for primary-key row locks — the same lock type claimJob()/updateJob() already take — and locking in ascending job_id order gives all code paths a consistent global lock order, which is the standard deadlock-avoidance technique.
  3. Retry on deadlock: wrap the cleanup update (and claimJob()'s claim update) in a small retry loop that catches \Drupal\Core\Database\DatabaseExceptionWrapper, checks for SQLSTATE 40001, and retries a few times with jitter. Deadlocks can't be fully eliminated under arbitrary concurrency — MySQL always picks a victim to roll back — so the app must be able to retry that victim.

Remaining tasks

Implement and test.

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

alexpott created an issue. See original summary.

alexpott’s picture

Issue summary: View changes

alexpott’s picture

Status: Active » Needs review
alexpott’s picture

Status: Needs review » Reviewed & tested by the community

I've tested this on a production release that was getting deadlocks and now it's not deadlocking so that's great.

  • alexpott committed 37450d9c on 8.x-1.x
    fix: #3619249 Deadlocks caused by cleanupQueue() and claimJob()/...
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.