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.
Обсудить в форуме.