40p01 ошибка обнаружена взаимоблокировка

I digged this issue yesterday and found out that it is caused by deadlock that appears when we are trying to insert a block validation record.
Here are the logs form RDS database:

2020-10-29 10:12:25 UTC:172.31.27.161(53549):fuse@fusenet_explorer:[16327]:ERROR:  deadlock detected
2020-10-29 10:12:25 UTC:172.31.27.161(53549):fuse@fusenet_explorer:[16327]:DETAIL:  Process 16327 waits for ShareLock on transaction 37769062; blocked by process 16295.
	Process 16295 waits for AccessShareLock on tuple (240712,40) of relation 18711 of database 18697; blocked by process 31566.
	Process 31566 waits for ShareLock on transaction 37769078; blocked by process 16327.
	Process 16327: SELECT b0."hash" FROM "blocks" AS b0 WHERE (b0."number" = ANY($1) AND b0."consensus") ORDER BY b0."hash" FOR UPDATE
	Process 16295: INSERT INTO "block_rewards" AS b0 ("address_hash","address_type","block_hash","inserted_at","reward","updated_at") VALUES ($1,$2,$3,$4,$5,$6),($7,$8,$9,$10,$11,$12),($13,$14,$15,$16,$17,$18) ON CONFLICT ("address_hash","address_type","block_hash") DO UPDATE SET "reward" = EXCLUDED.reward WHERE (EXCLUDED.reward IS DISTINCT FROM b0."reward") RETURNING "updated_at", "inserted_at", "block_hash", "address_hash", "reward", "address_type"
	Process 31566: SELECT b0."hash" FROM "blocks" AS b0 WHERE (b0."number" = ANY($1) AND b0."consensus") ORDER BY b0."hash" FOR UPDATE
2020-10-29 10:12:25 UTC:172.31.27.161(53549):fuse@fusenet_explorer:[16327]:HINT:  See server log for query details.
2020-10-29 10:12:25 UTC:172.31.27.161(53549):fuse@fusenet_explorer:[16327]:CONTEXT:  while locking tuple (240712,41) in relation "blocks"
2020-10-29 10:12:25 UTC:172.31.27.161(53549):fuse@fusenet_explorer:[16327]:STATEMENT:  SELECT b0."hash" FROM "blocks" AS b0 WHERE (b0."number" = ANY($1) AND b0."consensus") ORDER BY b0."hash" FOR UPDATE

From I research I believe these lines of code are affected => https://github.com/poanetwork/blockscout/blob/master/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex#L46, however I’m not that good in building ECTO queries on Elixir and I don’t think I can fix it fast enough.
@vbaranov Could you look at it, please?

PostgreSQL присваивает пятисимвольные коды ошибок, которые соответствуют «SQLSTATE». Приложения обычно проверяют код ошибки и только потом обращаются к текстовому сообщению об ошибке. Коды ошибок и текстовое сообщение не меняются в новых выпусках. Обратите внимание, что не все коды ошибок, создаваемые PostgreSQL, определены в стандарте SQL. Некоторые дополнительные коды ошибок были добавлены самостоятельно или заимствованы из других баз данных.

Первые два символа кода ошибки представляют класс ошибок, а последние три символа — конкретное условие в этом классе. Таким образом, если вы не знаете значение конкретного кода ошибки, вы можете определить причину по классу ошибки.

Все коды ошибок и классы ошибок перечислены в статье. Для каждого класса ошибок существует свой «стандарт», код ошибки с тремя последними нулями. Этот код указывается только для условий ошибки, которые принадлежат к определенному классу, но не имеют конкретного кода. Читайте так же: Расшифровка кодов ошибок MySQL.

Класс 00 — Успешное завершение

00000

successful_completion

УСПЕШНОЕ ЗАВЕРШЕНИЕ

Класс 01 — Предупреждение

01000

warning

ПРЕДУПРЕЖДЕНИЕ

0100C

dynamic_result_sets_returned

ВОЗВРАЩЕНЫ ДИНАМИЧЕСКИЕ НАБОРЫ РЕЗУЛЬТАТОВ

01008

implicit_zero_bit_padding

НЕЯВНОЕ ЗАПОЛНЕНИЕ НУЛЕВОГО БИТА

01003

null_value_eliminated_in_set_function

НУЛЕВОЕ ЗНАЧЕНИЕ ИСКЛЮЧЕНО В ФУНКЦИИ SET

01007

privilege_not_granted

ПРИВИЛЕГИЯ НЕ ПРЕДОСТАВЛЕНА

1006

privilege_not_revoked

ПРИВИЛЕГИЯ НЕ ОТОЗВАНА

01004

string_data_right_truncation

УСЕЧЕНИЕ ПРАВОЙ ЧАСТИ СТРОКОВЫХ ДАННЫХ

01P01

deprecated_feature

УСТАРЕВШАЯ ФУНКЦИЯ

Класс 02 — Нет данных (это также класс предупреждения в соответствии со стандартом SQL)

02000

no_data

НЕТ ДАННЫХ

02001

no_additional_dynamic_result_sets_returned

ДОПОЛНИТЕЛЬНЫЕ ДИНАМИЧЕСКИЕ НАБОРЫ РЕЗУЛЬТАТОВ НЕ ВОЗВРАЩАЮТСЯ

Класс 03 — SQL-запрос еще не завершен

03000

sql_statement_not_yet_complete

SQL-ЗАПРОС ЕЩЕ НЕ ЗАВЕРШЕН

Класс 08 — Исключение подключения

08000

connection_exception

ИСКЛЮЧЕНИЕ СОЕДИНЕНИЯ

08003

connection_does_not_exist

СОЕДИНЕНИЕ НЕ СУЩЕСТВУЕТ

08006

connection_failure

СБОЙ СОЕДИНЕНИЯ

08001

sqlclient_unable_to_establish_sqlconnection

SQLCLIENT НЕ МОЖЕТ УСТАНОВИТЬ SQL-СОЕДИНЕНИЕ

008004

sqlserver_rejected_establishment_of_sqlconnection

SQLSERVER ОТКЛОНИЛ СОЗДАНИЕ SQL-СОЕДИНЕНИЯ

08007

transaction_resolution_unknown

РАЗРЕШЕНИЕ ТРАНЗАКЦИИ НЕИЗВЕСТНО

08P01

protocol_violation

Нарушение ПРОТОКОЛА

Класс 09 — Спровоцированное исключение действия

09000

triggered_action_exception

ИСКЛЮЧЕНИЕ СРАБОТАВШЕГО ДЕЙСТВИЯ

Класс 0A — Функция не поддерживается

0A000

feature_not_supported

ФАТУРА НЕ ПОДДЕРЖИВАЕТСЯ Класс 0B — Неправильная инициация транзакции

0B000

invalid_transaction_initiation

НЕКОРРЕКТНОЕ ИНИЦИИРОВАНИЕ ТРАНЗАКЦИИ

Класс 0F — Исключение локатора

0F000

locator_exception

ИСКЛЮЧЕНИЕ ЛОКАТОРА

0F001

invalid_locator_specification

НЕДЕЙСТВИТЕЛЬНАЯ СПЕЦИФИКАЦИЯ ЛОКАТОРА

Класс 0L — Неверный грантодатель

0L000

invalid_grantor

НЕДЕЙСТВИТЕЛЬНЫЙ ПРАВОДАТЕЛЬ

0LP01

invalid_grant_operation

НЕДОПУСТИМАЯ ОПЕРАЦИЯ ГРАНТА

Класс 0P — Неверная спецификация роли

0P000

invalid_role_specification

НЕДОПУСТИМАЯ СПЕЦИФИКАЦИЯ РОЛИ

Класс 21 — Нарушение кардинальности

21000

cardinality_violation

НАРУШЕНИЕ КАРДИНАЛЬНОСТИ

Класс 22 — Исключение данных

22000

data_exception

ИСКЛЮЧЕНИЕ ДАННЫХ

2202E

array_subscript_error

ОШИБКА ПОДСКРИПТА МАССИВА

22021

character_not_in_repertoire

СИМВОЛ ОТСУТСТВУЕТ В РЕПЕРТУАРЕ

22008

datetime_field_overflow

ПЕРЕПОЛНЕНИЕ ПОЛЯ DATETIME

22012

division_by_zero

ДЕЛЕНИЕ НА НОЛЬ

22005

error_in_assignment

ОШИБКА В ПРИСВОЕНИИ

2200B

escape_character_conflict

КОНФЛИКТ УПРАВЛЯЮЩИХ СИМВОЛОВ

22022

indicator_overflow

ПЕРЕПОЛНЕНИЕ ИНДИКАТОРА

22015

interval_field_overflow

ПЕРЕПОЛНЕНИЕ ПОЛЯ ИНТЕРВАЛА

2201E

invalid_argument_for_logarithm

НЕДОПУСТИМЫЙ АРГУМЕНТ ДЛЯ ЛОГАРИФМА

2201F

invalid_argument_for_power_function

НЕДОПУСТИМЫЙ АРГУМЕНТ ДЛЯ ФУНКЦИИ ПИТАНИЯ

2201G

invalid_argument_for_width_bucket_function

НЕДОПУСТИМЫЙ АРГУМЕНТ ДЛЯ ФУНКЦИИ ШИРИНЫ ВЕДРА

22018

invalid_character_value_for_cast

НЕДОПУСТИМОЕ ЗНАЧЕНИЕ СИМВОЛА ДЛЯ БРОСКА

22007

invalid_datetime_format

НЕВЕРНЫЙ ФОРМАТ ВРЕМЕНИ ДАТЫ

22019

invalid_escape_character

НЕДОПУСТИМЫЙ УПРАВЛЯЮЩИЙ СИМВОЛ

2200D

invalid_escape_octet

НЕДОПУСТИМЫЙ УПРАВЛЯЮЩИЙ ОКТЕТ

22025

invalid_escape_sequence

НЕВЕРНАЯ ПОСЛЕДОВАТЕЛЬНОСТЬ СИМВОЛОВ

22P06

nonstandard_use_of_escape_character

НЕСТАНДАРТНОЕ ИСПОЛЬЗОВАНИЕ УПРАВЛЯЮЩЕГО СИМВОЛА

22010

invalid_indicator_parameter_value

НЕДОПУСТИМОЕ ЗНАЧЕНИЕ ПАРАМЕТРА ИНДИКАТОРА

22020

invalid_limit_value

НЕДОПУСТИМОЕ ПРЕДЕЛЬНОЕ ЗНАЧЕНИЕ

22023

invalid_parameter_value

НЕДОПУСТИМОЕ ЗНАЧЕНИЕ ПАРАМЕТРА

2201B

invalid_regular_expression

НЕВЕРНОЕ РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ

22009

invalid_time_zone_displacement_value

НЕДОПУСТИМОЕ ЗНАЧЕНИЕ СМЕЩЕНИЯ ЧАСОВОГО ПОЯСА

2200C

invalid_use_of_escape_character

НЕКОРРЕКТНОЕ ИСПОЛЬЗОВАНИЕ УПРАВЛЯЮЩЕГО СИМВОЛА

2200G

most_specific_type_mismatch

НАИБОЛЕЕ СПЕЦИФИЧЕСКОЕ НЕСООТВЕТСТВИЕ ТИПОВ

22004

null_value_not_allowed

НУЛЕВОЕ ЗНАЧЕНИЕ НЕДОПУСТИМО

22002

null_value_no_indicator_parameter

НУЛЕВОЕ ЗНАЧЕНИЕ НЕТ ПАРАМЕТРА ИНДИКАТОРА

22003

numeric_value_out_of_range

ЧИСЛОВОЕ ЗНАЧЕНИЕ ВНЕ ДИАПАЗОНА

22026

string_data_length_mismatch

НЕСООТВЕТСТВИЕ ДЛИНЫ СТРОКОВЫХ ДАННЫХ

22001

string_data_right_truncation

УСЕЧЕНИЕ СТРОКОВЫХ ДАННЫХ СПРАВА

22011

substring_error

ОШИБКА ПОДСТРОКИ

22027

trim_error

ОШИБКА ТРИМ

22024

unterminated_c_string

НЕЗАВЕРШЕННАЯ СТРОКА C

2200F

zero_length_character_string

СТРОКА СИМВОЛОВ НУЛЕВОЙ ДЛИНЫ

22P01

floating_point_exception

ИСКЛЮЧЕНИЕ ПЛАВАЮЩЕЙ ТОЧКИ

22P02

invalid_text_representation

НЕКОРРЕКТНОЕ ПРЕДСТАВЛЕНИЕ ТЕКСТА

22P03

invalid_binary_representation

НЕКОРРЕКТНОЕ ДВОИЧНОЕ ПРЕДСТАВЛЕНИЕ

22P04

bad_copy_file_format

НЕПРАВИЛЬНЫЙ ФОРМАТ ФАЙЛА КОПИИ

22P05

untranslatable_character

НЕПЕРЕВОДИМЫЙ СИМВОЛ

Класс 23 — Нарушение ограничений целостности

23000

integrity_constraint_violation

НАРУШЕНИЕ ОГРАНИЧЕНИЯ ЦЕЛОСТНОСТИ

23001

restrict_violation

НАРУШЕНИЕ ОГРАНИЧЕНИЙ

23502

not_null_violation

НАРУШЕНИЕ НЕ НУЛЯ

23503

foreign_key_violation

НАРУШЕНИЕ ВНЕШНЕГО КЛЮЧА

23505

unique_violation

УНИКАЛЬНОЕ НАРУШЕНИЕ

23514

check_violation

НАРУШЕНИЕ ПРОВЕРКИ

Класс 24 — Неверное состояние курсора

24000

invalid_cursor_state

НЕДОПУСТИМОЕ СОСТОЯНИЕ КУРСОРА

Класс 25 — Неверное состояние транзакции

25000

invalid_transaction_state

НЕДЕЙСТВИТЕЛЬНОЕ СОСТОЯНИЕ ТРАНЗАКЦИИ

25001

active_sql_transaction

АКТИВНАЯ SQL ТРАНЗАКЦИЯ

25002

branch_transaction_already_active

ТРАНЗАКЦИЯ ОТДЕЛЕНИЯ УЖЕ АКТИВНА

25008

held_cursor_requires_same_isolation_level

УДЕРЖИВАЕМЫЙ КУРСОР ТРЕБУЕТ ОДИНАКОВОГО УРОВНЯ ИЗОЛЯЦИИ

25003

inappropriate_access_mode_for_branch_transaction

НЕСООТВЕТСТВУЮЩИЙ РЕЖИМ ДОСТУПА ДЛЯ ТРАНЗАКЦИИ ВЕТВЛЕНИЯ

25004

inappropriate_isolation_level_for_branch_transaction

НЕСООТВЕТСТВУЮЩИЙ УРОВЕНЬ ИЗОЛЯЦИИ ДЛЯ ТРАНЗАКЦИИ ФИЛИАЛА

25005

no_active_sql_transaction_for_branch_transaction

НЕТ АКТИВНОЙ SQL-ТРАНЗАКЦИИ ДЛЯ ТРАНЗАКЦИИ ФИЛИАЛА

25006

read_only_sql_transaction

SQL-ТРАНЗАКЦИЯ ТОЛЬКО ДЛЯ ЧТЕНИЯ

25007

schema_and_data_statement_mixing_not_supported

СМЕШИВАНИЕ СХЕМЫ И ОПЕРАТОРА ДАННЫХ НЕ ПОДДЕРЖИВАЕТСЯ

25P01

no_active_sql_transaction

НЕТ АКТИВНОЙ SQL ТРАНЗАКЦИИ

25P02

in_failed_sql_transaction

В НЕУДАЧНОЙ ТРАНЗАКЦИИ SQL

Класс 26 — Неверное имя SQL-запроса

26000

invalid_sql_statement_name

НЕДОПУСТИМОЕ ИМЯ SQL-ОПЕРАТОРА

Класс 27 — Нарушение триггерного изменения данных

27000

triggered_data_change_violation

НАРУШЕНИЕ ПРАВИЛ ИЗМЕНЕНИЯ ДАННЫХ

Класс 28 — Неверная спецификация авторизации

28000

invalid_authorization_specification

НЕДОПУСТИМАЯ СПЕЦИФИКАЦИЯ АВТОРИЗАЦИИ

Класс 2B — Зависимые дескрипторы привилегий все еще существуют

2B000

dependent_privilege_descriptors_still_exist

ЗАВИСИМЫЕ ДЕСКРИПТОРЫ ПРИВИЛЕГИЙ ВСЕ ЕЩЕ СУЩЕСТВУЮТ

2BP01

dependent_objects_still_exist

ЗАВИСИМЫЕ ОБЪЕКТЫ ВСЕ ЕЩЕ СУЩЕСТВУЮТ

Класс 2D — Некорректное завершение транзакции

2D000

invalid_transaction_termination

НЕКОРРЕКТНОЕ ЗАВЕРШЕНИЕ ТРАНЗАКЦИИ

Класс 2F — Исключение SQL-маршрута

2F000

sql_routine_exception

ИСКЛЮЧЕНИЕ SQL-ПРОЦЕДУРЫ

2F005

function_executed_no_return_statement

ФУНКЦИЯ ВЫПОЛНЕНА НЕТ ЗАЯВЛЕНИЯ О ВОЗВРАТЕ

2F002

modifying_sql_data_not_permitted

МОДИФИКАЦИЯ ДАННЫХ SQL ЗАПРЕЩЕНА

2F003

prohibited_sql_statement_attempted

ПОПЫТКА ЗАПРЕЩЕННОГО SQL-ОПЕРАТОРА

2F004

reading_sql_data_not_permitted

ЧТЕНИЕ SQL-ДАННЫХ ЗАПРЕЩЕНО

Класс 34 — Неверное имя курсора

34000

invalid_cursor_name

НЕДОПУСТИМОЕ ИМЯ КУРСОРА

Класс 38 — Исключение внешней программы

38000

external_routine_exception

ИСКЛЮЧЕНИЕ ВНЕШНЕЙ ПРОЦЕДУРЫ

38001

containing_sql_not_permitted

СОДЕРЖАНИЕ SQL НЕ РАЗРЕШЕНО

38002

modifying_sql_data_not_permitted

МОДИФИКАЦИЯ ДАННЫХ SQL НЕ РАЗРЕШЕНА

38003

prohibited_sql_statement_attempted

ПРЕДПРИНЯТА ПОПЫТКА ВЫПОЛНЕНИЯ ЗАПРЕЩЕННОГО SQL-ЗАПРОСА

38004

reading_sql_data_not_permitted

ЧТЕНИЕ SQL-ДАННЫХ НЕ РАЗРЕШЕНО

Класс 39 — Исключение при вызове внешнего маршрута

39000

external_routine_invocation_exception

ИСКЛЮЧЕНИЕ ВЫЗОВА ВНЕШНЕЙ ПРОЦЕДУРЫ

39001

invalid_sqlstate_returned

ВОЗВРАЩЕНО НЕВЕРНОЕ СОСТОЯНИЕ SQLSTATE

39004

null_value_not_allowed

НУЛЕВОЕ ЗНАЧЕНИЕ НЕДОПУСТИМО

39P01

trigger_protocol_violated

НАРУШЕН ПРОТОКОЛ ТРИГГЕРА

39P02

srf_protocol_violated

НАРУШЕН ПРОТОКОЛ SRF

Класс 3B — Исключение точки сохранения

3B000

savepoint_exception

ИСКЛЮЧЕНИЕ ТОЧКИ СОХРАНЕНИЯ

3B001

invalid_savepoint_specification

НЕВЕРНАЯ СПЕЦИФИКАЦИЯ ТОЧКИ СОХРАНЕНИЯ

Класс 3D — Неверное имя каталога

3D000

invalid_catalog_name

НЕДОПУСТИМОЕ ИМЯ КАТАЛОГА

Класс 3F — Неверное имя схемы

3F000

invalid_schema_name

НЕКОРРЕКТНОЕ ИМЯ СХЕМЫ

Класс 40 — Откат транзакции

40000

transaction_rollback

ОТКАТ ТРАНЗАКЦИИ

40002

transaction_integrity_constraint_violation

НАРУШЕНИЕ ОГРАНИЧЕНИЙ ЦЕЛОСТНОСТИ ТРАНЗАКЦИИ

40001

serialization_failure

СБОЙ СЕРИАЛИЗАЦИИ

40003

statement_completion_unknown

ЗАВЕРШЕНИЕ ОПЕРАЦИИ НЕИЗВЕСТНО

40P01

deadlock_detected

ОБНАРУЖЕН ТУПИК

Класс 42 — Ошибка синтаксиса или нарушение правила доступа

42000

syntax_error_or_access_rule_violation

СИНТАКСИЧЕСКАЯ ОШИБКА ИЛИ НАРУШЕНИЕ ПРАВИЛА ДОСТУПА

42601

syntax_error

ОШИБКА СИНТАКСИСА

42501

insufficient_privilege

НЕДОСТАТОЧНАЯ ПРИВИЛЕГИЯ

42846

cannot_coerce

НЕ МОЖЕТ СОХРАНИТЬСЯ

42803

grouping_error

ОШИБКА ГРУППИРОВКИ

42830

invalid_foreign_key

НЕДОПУСТИМЫЙ ВНЕШНИЙ КЛЮЧ

42602

invalid_name

НЕПРАВИЛЬНОЕ ИМЯ

42622

name_too_long

СЛИШКОМ ДЛИННОЕ ИМЯ

42939

reserved_name

ЗАРЕГИСТРИРОВАННОЕ ИМЯ

42804

datatype_mismatch

НЕСООТВЕТСТВИЕ ТИПОВ ДАННЫХ

42P18

indeterminate_datatype

НЕОПРЕДЕЛЕННЫЙ ТИП ДАННЫХ

42809

wrong_object_type

НЕПРАВИЛЬНЫЙ ТИП ОБЪЕКТА

42703

undefined_column

НЕОПРЕДЕЛЁННЫЙ СТОЛБЕЦ

42883

undefined_function

НЕОПРЕДЕЛЁННАЯ ФУНКЦИЯ

42P01

undefined_table

НЕОПРЕДЕЛЁННАЯ ТАБЛИЦА

42P02

undefined_parameter

НЕОПРЕДЕЛЁННЫЙ ПАРАМЕТР

42704

undefined_object

НЕОПРЕДЕЛЕННЫЙ ОБЪЕКТ

42701

duplicate_column

ДУБЛИРУЮЩИЙ СТОЛБЕЦ

42P03

duplicate_cursor

ДУБЛИРУЮЩИЙ КУРСОР

42P04

duplicate_database

ДУБЛИРОВАНИЕ БАЗЫ ДАННЫХ

42723

duplicate_function

ДУБЛИРУЮЩАЯ ФУНКЦИЯ

42P05

duplicate_prepared_statement

ДУБЛИРОВАНИЕ ПОДГОТОВЛЕННОГО ОПЕРАТОРА

42P06

duplicate_schema

ДУБЛИРОВАНИЕ СХЕМЫ

42P07

duplicate_table

ДУБЛИРУЮЩАЯ ТАБЛИЦА

42712

duplicate_alias

ДУБЛИКАТ ПСЕВДОНИМА

42710

duplicate_object

ДУБЛИРУЮЩИЙ ОБЪЕКТ

42702

ambiguous_column

НЕОДНОЗНАЧНЫЙ СТОЛБЕЦ

42725

ambiguous_function

НЕОДНОЗНАЧНАЯ ФУНКЦИЯ

42P08

ambiguous_parameter

НЕОДНОЗНАЧНЫЙ ПАРАМЕТР

42P09

ambiguous_alias

НЕОДНОЗНАЧНЫЙ ПСЕВДОНИМ

42P10

invalid_column_reference

НЕДОПУСТИМАЯ ССЫЛКА НА СТОЛБЕЦ

42611

invalid_column_definition

НЕКОРРЕКТНОЕ ОПРЕДЕЛЕНИЕ СТОЛБЦА

42P11

invalid_cursor_definition

НЕКОРРЕКТНОЕ ОПРЕДЕЛЕНИЕ КУРСОРА

42P12

invalid_database_definition

НЕДОПУСТИМОЕ ОПРЕДЕЛЕНИЕ БАЗЫ ДАННЫХ

42P13

invalid_function_definition

НЕВЕРНОЕ ОПРЕДЕЛЕНИЕ ФУНКЦИИ

42P14

invalid_prepared_statement_definition

НЕДОПУСТИМОЕ ОПРЕДЕЛЕНИЕ ПОДГОТОВЛЕННОГО ОПЕРАТОРА

42P15

invalid_schema_definition

НЕВЕРНОЕ ОПРЕДЕЛЕНИЕ СХЕМЫ

42P16

invalid_table_definition

НЕВЕРНОЕ ОПРЕДЕЛЕНИЕ ТАБЛИЦЫ

42P17

invalid_object_definition

НЕВЕРНОЕ ОПРЕДЕЛЕНИЕ ОБЪЕКТА

Класс 44 — Нарушение проверки

44000

with_check_option_violation

НАРУШЕНИЕ ОПЦИИ ПРОВЕРКИ

Класс 53 — Недостаточно ресурсов

53000

insufficient_resources

НЕДОСТАТОЧНОЕ КОЛИЧЕСТВО РЕСУРСОВ

53100

disk_full

ДИСК ПЕРЕПОЛНЕН

53200

out_of_memory

МАЛО ПАМЯТИ

53300

too_many_connections

СЛИШКОМ МНОГО СОЕДИНЕНИЙ

Класс 54 — Превышен лимит программы

54000

program_limit_exceeded

ПРЕВЫШЕН ЛИМИТ ПРОГРАММЫ

54001

statement_too_complex

СЛИШКОМ СЛОЖНОЕ УТВЕРЖДЕНИЕ

54011

too_many_columns

СЛИШКОМ МНОГО СТОЛБЦОВ

54023

too_many_arguments

СЛИШКОМ МНОГО АРГУМЕНТОВ

Класс 55 — Объект не находится в состоянии предпосылки

55000

object_not_in_prerequisite_state

ОБЪЕКТ НЕ НАХОДИТСЯ В СОСТОЯНИИ ПРЕДПОСЫЛКИ

55006

object_in_use

ОБЪЕКТ В ИСПОЛЬЗОВАНИИ

55P02

cant_change_runtime_param

НЕВОЗМОЖНО ИЗМЕНИТЬ ПАРАМЕТР ВРЕМЕНИ ВЫПОЛНЕНИЯ

55P03

lock_not_available

БЛОКИРОВКА НЕДОСТУПНА

Класс 57 — Вмешательство оператора

57000

operator_intervention

ВМЕШАТЕЛЬСТВО ОПЕРАТОРА

57014

query_canceled

ЗАПРОС ОТМЕНЕН

57P01

admin_shutdown

ОТКЛЮЧЕНИЕ АДМИНИСТРАТОРА

57P02

crash_shutdown

АВАРИЙНОЕ ОТКЛЮЧЕНИЕ

57P03

cannot_connect_now

НЕВОЗМОЖНО ПОДКЛЮЧИТЬСЯ

Класс 58 — Системная ошибка (ошибки, внешние по отношению к самому PostgreSQL)

58030

io_error

ОШИБКА ВВОДА-ВЫВОДА

58P01

undefined_file

НЕОПРЕДЕЛЕННЫЙ ФАЙЛ

58P02

duplicate_file

ДУБЛИКАТ ФАЙЛА

Класс F0 — Ошибка файла конфигурации

F0000

config_file_error

ОШИБКА ФАЙЛА КОНФИГУРАЦИИ

F0001

lock_file_exists

СУЩЕСТВУЕТ ФАЙЛ БЛОКИРОВКИ

Класс P0 — Ошибка PL/pgSQL

P0000

plpgsql_error

PLPGSQL ERROR

P0001

raise_exception

ПОДНЯТЬ ИСКЛЮЧЕНИЕ

P0002

no_data_found

НЕ НАЙДЕНЫ ДАННЫЕ

P0003

too_many_rows

СЛИШКОМ МНОГО СТРОК

Класс XX — Внутренняя ошибка

XX000

internal_error

ВНУТРЕННЯЯ ОШИБКА

XX001

data_corrupted

ДАННЫЕ ИСПОРЧЕНЫ

XX002

index_corrupted

ПОВРЕЖДЁННЫЙ ИНДЕКС

Вам не нужно никаких явных LOCK зайти в тупик. Вот очень простая демонстрация с нуля только с ВСТАВКАМИ:

create table a(i int primary key);
create table b(i int primary key);

Сессия № 1 делает:

begin;
insert into a values(1);

Затем сессия № 2 делает:

begin;
insert into b values(1);
insert into a values(1);
-- here it goes into waiting for session #1 to finish its transaction

Затем сессия № 1 делает:

insert into b values(1);

И тогда возникает тупик:

ОШИБКА: обнаружен тупик
ДЕТАЛИ: Процесс 9571 ожидает ShareLock для транзакции 4150; заблокирован процессом 9501.
Процесс 9501 ожидает ShareLock для транзакции 4149; заблокирован процессом 9571.
СОВЕТ: см. Журнал сервера для деталей запроса.

То же самое может случиться с простыми ОБНОВЛЕНИЯМИ или комбинацией ОБНОВЛЕНИЙ и ВСТАВКИ. Эти операции принимают неявные блокировки, и если они происходят в разных сеансах в разных порядках, они могут зайти в тупик.

Получаю время от времени ошибку

Message: SQLSTATE[40P01]: Deadlock detected: 7 ERROR: deadlock
detected

Запрос вида (для примера сокращу, в запросе может быть до 250 строк и много контента):

INSERT INTO link (
                        parsed,
                        error,
                        error_message,
                        url,
                        title,
                        author_name,
                        date,
                        date_parsed,
                        updated_at,
                        rss_id,
                        content,
                        content_tsvector
                    )  VALUES (true, false, '', 'https://medium.com/@scottishforsocial/a-leading-voice-from-india-transforming-indian-education-8655f91dd7b7',  'A leading voice from India-Transforming Indian Education', 'ScottishHigh', '2020-08-06 04:50:16', '2020-08-07 08:30:14', '2020-08-07 08:30:14',
            221808, 'content', to_tsvector('content')) ON CONFLICT DO NOTHING
                    RETURNING id, rss_id

Так же есть ньюанс что подобные запросы выполняются на 40 серверах к одной базе данных

Как с таким бороться?

Автор: Николай Матюшенков

Оригинальная публикация: https://habrahabr.ru/post/327292/

Вы встречались с ошибками, которые возникают время от времени в продакшне, но никак не воспроизводятся локально? Бывает, изучаешь такой баг и вдруг понимаешь, что он проявляется только при одновременном параллельном выполнении скриптов. Изучив код, понимаешь как это исправить, чтобы такого больше не повторялось. Но на такое исправление хорошо бы написать тест…

В статье я расскажу о своем подходе к тестированию таких ситуаций. А также приведу несколько наглядных (и наверное даже классических) примеров багов, которые удобно протестировать с помощью этого подхода. Все примеры багов живые — то, что встречается в работе.

Забегая вперед сразу скажу, что в конце статьи будет ссылка на github, куда я выложил готовое решение, позволяющее тестировать параллельные консольные процессы легко и просто.

Пример номер один. Параллельное добавление одного и того же

Задача. У нас есть приложение с базой данных (PostgreSQL) и нам надо наладить импорт данных из сторонней системы. Допустим, есть таблица account (id, name) и связи идентификаторов с внешней системой в таблице account_import (id, external_id). Давайте набросаем простой механизм приема сообщений.

При приеме сообщения будем сперва проверять — есть ли такие записи у нас в базе. Если есть, то будем обновлять имеющиеся. Если нет, то будем добавлять в базу.

$data = json_decode($jsonInput, true); // ‘{«id»:1,»name»:»account1″}’

try {

$connection->beginTransaction();

// Проверим, есть ли такая запись в базе

$stmt = $connection->prepare(«SELECT id FROM account_import

WHERE external_id = :external_id»);

$stmt->execute([

‘:external_id’ => $data[‘id’],

]);

$row = $stmt->fetch();

usleep(100000); // 0.1 sec

// Если импортируемая запись в базе есть, то обновим ее

if ($row) {

$stmt = $connection->prepare(«UPDATE account SET name = :name WHERE id = (

SELECT id FROM account_import WHERE external_id = :external_id

)»);

$stmt->execute([

‘:name’ => $data[‘name’],

‘:external_id’ => $data[‘id’],

]);

$accountId = $row[‘id’];

}

// Иначе создадим новую запись

else {

$stmt = $connection->prepare(«INSERT INTO account (name) VALUES (:name)»);

$stmt->execute([

‘:name’ => $data[‘name’],

]);

$accountId = $connection->lastInsertId();

$stmt = $connection->prepare(«INSERT INTO account_import (id, external_id)

VALUES (:id, :external_id)»);

$stmt->execute([

‘:id’ => $accountId,

‘:external_id’ => $data[‘id’],

]);

}

$connection->commit();

}

catch (Throwable $e) {

$connection->rollBack();

throw $e;

}

С первого взгляда выглядит хорошо. Но если данные в нашу систему могут передаваться не строго последовательно, тут можем столкнуться с проблемой. Задержка 0.1 секунды в этом примере нам нужна, чтобы гарантированно воспроизвести проблему. Что будет, если выполнить импорт одних и тех же данных параллельно? Вероятно, вместо того, чтобы данные были добавлены, а потом обновлены, будет попытка повторной вставки данных и, как следствие, ошибка нарушения первичного ключа в account_import. 

Чтобы исправить ошибку, ее хорошо бы сперва воспроизвести. А лучше всего — написать тест, который воспроизводит ошибку. Я решил для этого запускать команды асинхронно с помощью bash и написал простой скрипт для этого, который можно использовать не только в связке с PHP.

Идея проста — запускаем в фоне несколько экземпляров команд, потом ждем когда все они завершатся, и проверяем коды выполнения. Если среди кодов выполнения есть отличные от нуля, значит мы нашли баг. В упрощенном виде скрипт будет выглядеть так:

# Команда, которую будем проверять

COMMAND=”echo -e ‘{«id»:1,»name»:»account1″}’ | ./cli app:import”

# PID-ы запущенных фоновых процессов

pids=()

# Результаты выполнения фоновых процессов

results=()

# Ожидаемые результаты выполнения фоновых процессов (нули)

expects=()

# Запустим процессы в фоне и перенаправим вывод в stderr

for i in $(seq 2)

do

eval $COMMAND 1>&2 & pids+=($!) ; echo -e ‘>>>’ Process ${pids[i-1]} started 1>&2

done

# Ожидаем завершения каждого процесса и сохраняем результаты в $results

for pid in «${pids[@]}»

do

wait $pid

results+=($?)

expects+=(0)

echo -e ‘<<<‘ Process $pid finished 1>&2

done

# Сравним полученные результаты с ожидаемыми

result=`( IFS=$’, ‘; echo «${results[*]}» )`

expect=`( IFS=$’, ‘; echo «${expects[*]}» )`

if [ «$result» != «$expect» ]

then

exit 1

fi

Полную версию скрипта выложил на github.

На основе этой команды мы можем дописать к PHPUnit новые assert-ы. Тут уже все проще и я не буду подробно останавливаться на этом. Скажу только, что в вышеупомянутом проекте они реализованы. Чтобы их использовать достаточно подключить трейт AsyncTrait к вашему тесту.

Напишем такой тест.

use AppCommandInitializer;

use MnvxPProcessAsyncTrait;

use MnvxPProcessCommandCommand;

use PHPUnitFrameworkTestCase;

use SymfonyComponentConsoleTesterCommandTester;

class ImportCommandTest extends TestCase

{

use AsyncTrait;

public function testImport()

{

$cli = Initializer::create();

$command = $cli->find(‘app:delete’);

// Удаляем запись c external_id = 1,

// чтобы проверить случай параллельного добавления одной и той же записи

$commandTester = new CommandTester($command);

$commandTester->execute([

‘externalId’ => 1,

]);

$asnycCommand = new Command(

‘echo -e ‘{«id»:1,»name»:»account1″}’ | ./cli app:import’, // Тестируемая команда

dirname(__DIR__), // Каталог, из которого будет запускаться команда

2 // Количество запускаемых экземпляров команд

);

// Запуск проверки

$this->assertAsyncCommand($asnycCommand);

}

}

В результате запуска теста получим такой вывод.

$ ./vendor/bin/phpunit

PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

F 1 / 1 (100%)

Time: 230 ms, Memory: 6.00MB

There was 1 failure:

1) ImportCommandTest::testImport

Failed asserting that command

echo -e ‘{«id»:1,»name»:»account1″}’ | ./cli app:import (path: /var/www/pprocess-playground, count: 2)

executed in parallel.

Output:

>>> Process 18143 started

>>> Process 18144 started

Account 25 imported correctly

[DoctrineDBALExceptionUniqueConstraintViolationException]

An exception occurred while executing ‘INSERT INTO account_import (id, exte

rnal_id) VALUES (:id, :external_id)’ with params [«26», 1]:

SQLSTATE[23505]: Unique violation: 7 ОШИБКА:  повторяющееся значение ключа

нарушает ограничение уникальности «account_import_pkey»

DETAIL:  Ключ «(external_id)=(1)» уже существует.

——-

app:import

<<< Process 18143 finished

<<< Process 18144 finished

.

/var/www/pprocess-playground/vendor/mnvx/pprocess/src/AsyncTrait.php:19

/var/www/pprocess-playground/tests/ImportCommandTest.php:30

FAILURES!

Tests: 1, Assertions: 1, Failures: 1.

Причину мы уже обсудили. Теперь попробуем добавить принудительную блокировку параллельного выполнения фрагмента нашего скрипта (тут используется malkusch/lock).

$mutex = new FlockMutex(fopen(__FILE__, ‘r’));

$mutex->synchronized(function () use ($connection, $data) {

// наш код из блока try

});

$ ./vendor/bin/phpunit

PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 361 ms, Memory: 6.00MB

OK (1 test, 1 assertion)

Этот и другие примеры я выложил на github, если вдруг кому-то понадобится.

Пример номер два. Подготовка данных в таблице

Этот пример будет немного интереснее. Допустим, у нас есть таблица пользователей users (id, name) и мы желаем хранить в таблице users_active (id) список активных в настоящий момент пользователей.

У нас будет команда, которая каждый раз будет удалять все записи из таблицы users_acitve и добавлять туда данные заново.

try {

$connection->beginTransaction();

$connection->prepare(«DELETE FROM users_active»)->execute();

usleep(100000); // 0.1 sec

$connection->prepare(«INSERT INTO users_active (id) VALUES (3), (5), (6), (10)»)->execute();

$connection->commit();

$output->writeln(‘<info>users_active refreshed</info>’);

}

catch (Throwable $e) {

$connection->rollBack();

throw $e;

}

Тут только с первого взгляда все хорошо. На самом же деле при параллельном запуске снова получим ошибку.

Напишем тест, чтобы ее воспроизвести.

use MnvxPProcessAsyncTrait;

use MnvxPProcessCommandCommand;

use PHPUnitFrameworkTestCase;

class DetectActiveUsersCommandTest extends TestCase

{

use AsyncTrait;

public function testImport()

{

$asnycCommand = new Command(

‘./cli app:detect-active-users’, // Тестируемая команда

dirname(__DIR__), // Каталог, из которого будет запускаться команда

2 // Количество запускаемых экземпляров команд

);

// Запуск проверки

$this->assertAsyncCommand($asnycCommand);

}

}

Запускаем тест и видим текст ошибки:

$ ./vendor/bin/phpunit tests/DetectActiveUsersCommandTest.php

PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 287 ms, Memory: 4.00MB

There was 1 failure:

1) DetectActiveUsersCommandTest::testImport

Failed asserting that command

./cli app:detect-active-users (path: /var/www/pprocess-playground, count: 2)

executed in parallel.

Output:

>>> Process 24717 started

>>> Process 24718 started

users_active refreshed

<<< Process 24717 finished

[DoctrineDBALExceptionUniqueConstraintViolationException]

An exception occurred while executing ‘INSERT INTO users_active (id) VALUES

(3), (5), (6), (10)’:

SQLSTATE[23505]: Unique violation: 7 ОШИБКА:  повторяющееся значение ключа

нарушает ограничение уникальности «users_active_pkey»

DETAIL:  Ключ «(id)=(3)» уже существует.

——-

app:detect-active-users

<<< Process 24718 finished

.

/var/www/pprocess-playground/vendor/mnvx/pprocess/src/AsyncTrait.php:19

/var/www/pprocess-playground/tests/DetectActiveUsersCommandTest.php:19

FAILURES!

Tests: 1, Assertions: 1, Failures: 1.

По тексту ошибки понятно, что снова INSERT выполняется параллельно и это приводит к нежелательным последствиям. Попробуем сделать блокировку на уровне записей — добавим строчку после старта транзакции:

$connection->prepare(«SELECT id FROM users_active FOR UPDATE»)->execute();

Запускаем тест — ошибка ушла. Наш тест запускает два экземпляра процесса. Давайте увеличим в нашем тесте количество экземпляров до 3-х и посмотрим, что будет.

$asnycCommand = new Command(

‘./cli app:detect-active-users’, // Тестируемая команда

dirname(__DIR__), // Каталог, из которого будет запускаться команда

3 // Количество запускаемых экземпляров команд

);

И снова имеем ту же ошибку. В чем дело, мы же добавили блокировку?! Немного подумав, можно догадаться, что такая блокировка поможет только если в таблице users_active есть записи. В случае же, когда работают 3 процесса одновременно, получается картина такая — первый процесс получает блокировку. Второй и третий процесс ждут завершения транзакции первого процесса. Как только транзакция будет завершена, продолжат выполняться параллельно и второй и третий процесс, что приведет к нежелательным последствиям.

Чтобы починить, сделаем блокировку более общую. Например,

$connection->prepare(«SELECT id FROM users WHERE id IN (3, 5, 6, 10) FOR UPDATE»)->execute();

Либо вместо DELETE мы могли просто воспользоваться TRUNCATE, которая блокирует всю таблицу.

Пример номер три. Deadlock

Бывает, что сама по себе команда не приводит к проблемам, но одновременный вызов двух разных команд, работающих с одними и теми же ресурсами приводит к проблемам. Найти причины таких багов бывает нелегко. Но если причина найдена, то тест написать точно стоит, чтобы избежать возвращения проблемы в будущем при внесении изменений в код.

Напишем пару таких команд. Это классический случай, когда возникает взаимная блокировка.

Первая команда сперва обновляет запись с, потом с.

try {

$connection->beginTransaction();

$connection->prepare(«UPDATE deadlock SET value = value + 1 WHERE id = 1»)->execute();

usleep(100000); // 0.1 sec

$connection->prepare(«UPDATE deadlock SET value = value + 1 WHERE id = 2»)->execute();

$connection->commit();

$output->writeln(‘<info>Completed without deadlocks</info>’);

}

catch (Throwable $e) {

$connection->rollBack();

throw $e;

}

Вторая команда сперва обновляет запись с, потом с.

try {

$connection->beginTransaction();

$connection->prepare(«UPDATE deadlock SET value = value + 1 WHERE id = 2»)->execute();

usleep(100000); // 0.1 sec

$connection->prepare(«UPDATE deadlock SET value = value + 1 WHERE id = 1»)->execute();

$connection->commit();

$output->writeln(‘<info>Completed without deadlocks</info>’);

}

catch (Throwable $e) {

$connection->rollBack();

throw $e;

}

Тест будет выглядеть так.

use MnvxPProcessAsyncTrait;

use MnvxPProcessCommandCommandSet;

use PHPUnitFrameworkTestCase;

class DeadlockCommandTest extends TestCase

{

use AsyncTrait;

public function testImport()

{

$asnycCommand = new CommandSet(

[   // Тестируемые команды

‘./cli app:deadlock-one’,

‘./cli app:deadlock-two’

],

dirname(__DIR__), // Каталог, из которого будет запускаться команда

1 // Количество запускаемых экземпляров команд

);

// Запуск проверки

$this->assertAsyncCommands($asnycCommand);

}

}

В результате запуска теста увидим причину ошибки:

$ ./vendor/bin/phpunit tests/DeadlockCommandTest.php

PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.19 seconds, Memory: 4.00MB

There was 1 failure:

1) DeadlockCommandTest::testImport

Failed asserting that commands

./cli app:deadlock-one, ./cli app:deadlock-two (path: /var/www/pprocess-playground, count: 1)

executed in parallel.

Output:

>>> Process 5481 started: ./cli app:deadlock-one

>>> Process 5481 started: ./cli app:deadlock-two

[DoctrineDBALExceptionDriverException]

An exception occurred while executing ‘UPDATE deadlock SET value = value +

1 WHERE id = 1′:

SQLSTATE[40P01]: Deadlock detected: 7 ОШИБКА:  обнаружена взаимоблокировка

DETAIL:  Процесс 5498 ожидает в режиме ShareLock блокировку «транзакция 294

738″; заблокирован процессом 5499.

Процесс 5499 ожидает в режиме ShareLock блокировку «транзакция 294737»; заб

локирован процессом 5498.

HINT:  Подробности запроса смотрите в протоколе сервера.

CONTEXT:  при изменении кортежа (0,48) в отношении «deadlock»

——-

app:deadlock-two

Completed without deadlocks

<<< Process 5481 finished

<<< Process 5484 finished

.

/var/www/pprocess-playground/vendor/mnvx/pprocess/src/AsyncTrait.php:39

/var/www/pprocess-playground/tests/DeadlockCommandTest.php:22

FAILURES!

Tests: 1, Assertions: 1, Failures: 1.

Проблема лечится добавлением блокировки по аналогии с первым примером. Либо пересмотром структуры базы или алгоритма работы с данными.

Резюмируем

При параллельном исполнении кода могут возникать неожиданные ситуации, при исправлении которых полезно написать тесты. Мы рассмотрели несколько таких ситуаций и написали тесты, воспользовавшись pprocess.

Обсудить в форуме.

Понравилась статья? Поделить с друзьями:
  • 40e9 ошибка бмв
  • 40e0 ошибка bmw
  • 40d4 ошибка bmw
  • 40bx400 ошибка 6
  • 40a4 ошибка bmw