2017-09-22 10 views
3

Nous avons une application qui lit à partir d'un flux de données et augmente cette information dans une base de données. Les données sont des modifications qui se produisent sur Google Drive, ce qui signifie que de nombreux événements ayant un impact sur les mêmes objets peuvent se produire très proches les uns des autres.Deadlocks Postgres sur upserts concurrentes

Nous sommes confrontés à des interblocages lors de la conversion de ces informations dans la base de données, voici ce qui ressort du journal. Je l'ai reconstruit et aseptisé la requête pour une meilleure lisibilité:

ERROR: deadlock detected 
DETAIL: Process 10586 waits for ShareLock on transaction 166892743; blocked by process 10597. 
    Process 10597 waits for ShareLock on transaction 166892741; blocked by process 10586. 
    Process 10586: 
      INSERT INTO documents 
       (version, source, source_id, ingestion_date) 
      VALUES 
       (0, 'googledrive', 'alpha', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'beta', '2017-09-21T07:03:51.074Z') 
       (0, 'googledrive', 'gamma', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'delta', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'epsilon', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'zeta', '2017-09-21T07:03:51.074Z') 

      ON CONFLICT (source, source_id) 
      DO UPDATE 
      SET 
       ingestion_date = EXCLUDED.ingestion_date, 
       version = documents.version + 1 

      RETURNING source_id, source, uid 

    Process 10597: 
      INSERT INTO documents 
       (version, source, source_id, ingestion_date) 
      VALUES 
       (0, 'googledrive', 'delta', '2017-09-21T07:03:51.167Z'), 
       (0, 'googledrive', 'gamma', '2017-09-21T07:03:51.167Z') 

      ON CONFLICT (source, source_id) 
      DO UPDATE 
      SET 
       ingestion_date = EXCLUDED.ingestion_date, 
       version = documents.version + 1 

      RETURNING source_id, source, uid 

HINT: See server log for query details. 
CONTEXT: while locking tuple (3908269,11) in relation "documents" 
STATEMENT: 
      INSERT INTO documents 
       (version, source, source_id, ingestion_date) 
      VALUES 
       (0, 'googledrive', 'alpha', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'beta', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'gamma', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'delta', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'epsilon', '2017-09-21T07:03:51.074Z'), 
       (0, 'googledrive', 'zeta', '2017-09-21T07:03:51.074Z') 

      ON CONFLICT (source, source_id) 
      DO UPDATE 
      SET 
       ingestion_date = EXCLUDED.ingestion_date, 
       version = documents.version + 1 

      RETURNING source_id, source, uid 

Le schéma:

Column  |   Type    |        Modifiers 
----------------+-----------------------------+------------------------------------------------------------------- 
uid    | uuid      | not null default gen_random_uuid() 
date_created | timestamp without time zone | not null default now() 
sequence_id  | bigint      | not null default nextval('documents__sequence_id__seq'::regclass) 
version   | integer      | not null default 0 
source   | text      | not null 
source_id  | text      | not null 
ingestion_date | timestamp without time zone | not null 

Indexes: 
    "documents__pkey" PRIMARY KEY, btree (uid) 
    "documents__sequence_id__unique" UNIQUE CONSTRAINT, btree (sequence_id) 
    "documents__source__source_id__deleted" UNIQUE, btree (source, source_id) 
    "documents__ingestion_date__idx" btree (ingestion_date) 
    "documents__source_id__source__idx" btree (source_id, source) 

Je soupçonne que le problème est quelque chose comme « la première transaction verrouillait lignes avec source_id alpha, bêta, gamma en séquence , pendant ce temps, la deuxième transaction bloquait les lignes avec le delta source_id, gamma dans l'ordre inverse, et l'impasse s'est produite au point où ils ont tous deux verrouillé gamma et delta ", mais le timing ici est très serré!

Quelle serait la solution pour cela? Trier notre liste de valeurs par source_id?

Répondre

2

je peux penser à trois solutions:

  1. Vous insérez une seule ligne par déclaration, mais c'est inefficace.

  2. Vous triez les lignes avant de les insérer.

  3. Vous réessayez une transaction en cas d'erreur de blocage ou de sérialisation.

Je préférerais la troisième solution sauf si les erreurs se produisent très souvent.

1

syntaxe de votre requête permet de commander les valeurs facilement:

INSERT INTO documents 
      (version, source, source_id, ingestion_date) 
    SELECT * FROM (
     VALUES 
      (0, 'googledrive', 'alpha', '2017-09-21T07:03:51.074Z'), 
      (0, 'googledrive', 'beta', '2017-09-21T07:03:51.074Z') 
      (0, 'googledrive', 'gamma', '2017-09-21T07:03:51.074Z'), 
      (0, 'googledrive', 'delta', '2017-09-21T07:03:51.074Z'), 
      (0, 'googledrive', 'epsilon', '2017-09-21T07:03:51.074Z'), 
      (0, 'googledrive', 'zeta', '2017-09-21T07:03:51.074Z') 
    ) AS v ORDER BY source, source_id 

     ON CONFLICT (source, source_id) 

Cela devrait résoudre votre problème. Les performances devraient être bonnes, car le tri sera minuscule.

+0

Vous avez raison, merci. –