SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded при множественных POST запросах через JSONAPI

Главные вкладки

Аватар пользователя Max-Z Max-Z 20 февраля 2019 в 16:22

Добрый день!
Столкнулся с критической проблемой, ставящей под сомнение весь проект.
Создан кастомный Drupal модуль, включающий в себя React-приложение. Приложение управляет контентом Drupal через модуль JSONAPI. При отправке множественных POST запросов на создание контента проходит только первый-второй, дальше появляется ошибка:

Drupal\Core\Entity\EntityStorageException: SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction: UPDATE {node} SET vid=:db_update_placeholder_0, type=:db_update_placeholder_1, uuid=:db_update_placeholder_2, langcode=:db_update_placeholder_3 WHERE nid = :db_condition_placeholder_0; Array ( [:db_update_placeholder_0] => 13933 [:db_update_placeholder_1] => album [:db_update_placeholder_2] => bfc43049-b13e-455b-9383-28e7a86a07c1 [:db_update_placeholder_3] => fr [:db_condition_placeholder_0] => 8423 ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 783 of /opt/bitnami/apps/drupal/htdocs/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).

Сайт хостится на вирт. машине через Google cloud. Может ли это быть исключительно проблемой производительности дев-сервера?
Хотелось бы понять природу данной ошибки.
Я так понимаю, все дороги ведут сюда
https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Lock!LockBackendI...

Комментарии

Аватар пользователя gun_dose gun_dose 20 февраля 2019 в 19:21

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

Аватар пользователя Max-Z Max-Z 22 февраля 2019 в 17:04

Subrequests работает, штука отличная, но к текущей проблеме, к сожалению, это не имеет отношения.
Я всё ещё получаю упомянутую ошибку Lock wait timeout exceeded при множественных POST-операциях.
Видимо, всё же необходимо использовать Lock API.
Вопрос только в том, как прописать в моем кастомном модуле момент, когда нужно сделать acquire lock, а когда release?

Аватар пользователя gun_dose gun_dose 22 февраля 2019 в 17:09

Так не надо слать много POST-запросов) Запросы небось однотипные на создание/апдейт сущностей? Если да, то ситуация такая - нужно получать последний айдишник из таблицы сущностей + лочить таблицу. А поскольку параллельно идёт слишком много запросов, то нагрузка на сервер возрастает и увеличивается время обработки каждого из запросов, поэтому времени блокировки может не хватить. Кроме того, при таких запросах есть ненулевая вероятность получить фатал типа "Duplicate entry".

Так что самый правильный выход - пересмотреть логику фронтенд-приложения, чтобы не слать параллельно много запросов.

Аватар пользователя Max-Z Max-Z 22 февраля 2019 в 17:17

Приложение - это галерея фотографий. Контент создаётся с помощью Друпал, выводится через React.
Пользователь может создавать в Друпал албомы, а из React он может добавить фото сразу в несколько альбомов:

При этой операции исполняются множественные POST запросы через subrequests.
Буду искать способы управления этим замком на время исполнения подобных операций.

Аватар пользователя Max-Z Max-Z 22 февраля 2019 в 17:26

На async/await у меня вся логика построена, иначе ничего не работает вообще:) Проблема как тригернуть этот замок из Реакта.

Аватар пользователя Max-Z Max-Z 22 февраля 2019 в 17:48

Нет, но это советуется во многих источниках в качестве решения ошибки 1205.
По идее мне нужно сделать aquire lock перед цепью последовательных запросов и release по выполнению.
Буду признателен за идеи, как это реализовать:)

Аватар пользователя gun_dose gun_dose 22 февраля 2019 в 18:06

1. У вас запросы не последовательные, а параллельные.
2. aquire lock нужен для того, чтобы заблочить таблицы, пока операция не выполнится. У вас же запускаются параллельно несколько операций, каждая со своим локом. Если вы поставите лок перед операциями, то ни одна из них не выполнится.

Аватар пользователя gun_dose gun_dose 22 февраля 2019 в 20:42
3

Я настоятельно рекомендую зайти со стороны отправки запроса. Не нужно посылать одновременно несколько запросов на создание/обновление сущностей. Это всегда будет упираться в ресурсы: где-то сервер слабее, где-то за раз захотят создать больше контента, а где-то больше юзеров и нагрузка на сервер выше. И этим невозможно управлять со стороны php. Абсолютно никак. Потому что раздельные запросы всегда поднимают раздельные экземпляры приложения, которые не только не могут взаимодействовать между собой, но даже не могут точно знать о существовании друг друга.

Можно переделать логику и завернуть все запросы в один сабреквест, но тут очень легко можно упереться в лимит времени выполнения php-скрипта. Поэтому я бы переделал саму отправку запросов, а именно слал бы каждый следующий только после получения ответа от предыдущего. Плюс такого решения в том, что со стороны бэкенда не будет локов или таймаутов, а на фронте можно будет создать процентный индикатор.

Аватар пользователя Max-Z Max-Z 23 февраля 2019 в 11:09

Огромное спасибо, так и сделали в итоге!
Я тестировал вчера до ночи, ошибка эта очень и очень странная. Блюпринт содержит в себе 2 вида POST-запросов. Если я оставляю только вот этот, ошибки нет, независимо от кол-ва запросов в блюпринте два-шесть-десять, не важно:

{
                        requestId: this.randomId(1, 1000000000),
                        uri: `${prodURL}/jsonapi/node/puzzle/${uuid}/relationships/field_albums`,
                        action: 'create',
                        body: JSON.stringify({
                            "data": [
                                {
                                    "type": "node--album",
                                    "id": album_uuid
                                }
                            ]
                        }),
                        headers: {
                            'Accept': 'application/vnd.api+json',
                            'Content-Type': 'application/vnd.api+json',
                            'X-CSRF-Token': this.props.data.xcsrfToken,
                            'Authorization': auth
                        }

                    }

Но если я оставляю только второй вид, ошибка выскакивает от 2-х и более запросов моментально:

{
                        requestId: this.randomId(1, 1000000000),
                        uri: `${prodURL}/jsonapi/node/album/${album_uuid}/relationships/field_puzzles`,
                        action: 'create',
                        body: JSON.stringify({
                            "data": [
                                {
                                    "type": "node--puzzle",
                                    "id": uuid
                                }
                            ]
                        }),
                        headers: {
                            'Accept': 'application/vnd.api+json',
                            'Content-Type': 'application/vnd.api+json',
                            'X-CSRF-Token': this.props.data.xcsrfToken,
                            'Authorization': auth
                        }

                    }

Как бороться с этим чудом, не знает никто, лучше найти другой путь, совершенно верно Вы подсказали.

Аватар пользователя Orion76 Orion76 23 февраля 2019 в 14:43
2

Max-Z wrote:

Пользователь может создавать в Друпал албомы, а из React он может добавить фото сразу в несколько альбомов:

Хм.. если я правильно понял суть, "альбом" это сущность с "многострочным" полем типа entityreference на сущность "фото"?

Не знаю всей логики работы приложения, поэтому только предположу:
а может практичнее реализовать связь "наоборот" - в сущность "фото" добавить ссылку (поле entityreference) на альбомы, в которые это фото входит?

Тогда POST-запрос на добавление фото и "связи" его со всеми необходимыми альбомами будет только ОДИН.
И "процедура" добавления сущности фото или ее обновления в БД будет только ОДНА.

Аватар пользователя Max-Z Max-Z 24 февраля 2019 в 20:54

Все верно. Сущность "фото" также имеет ссылку на альбомы. Мы думали отказаться от первого варианта, но он нужен для выборки альбомов по участниками, никак не придумали, как безболезненно упразднить эту ноду.

Аватар пользователя Orion76 Orion76 25 февраля 2019 в 5:12
1

А, так значит "альбомы" общие для пользователей (я предполагал, что у каждого пользователя свои "личные" альбомы)

Тогда, имхо, как раз логичнее и практичнее оставить только связь "фото" -> "альбом"

"фото" же связано с "участником" (наверное стандартное поле "автор")?

Останется только добавить к сущности "альбом" псевдо-поля ссылки на ссылающиеся на них фото и пользователей-участников

примеры модулей для "обратной связи" на drupal.org есть.

Поищите по словам "entity back reference"
может какой подойдет без "допиливания" или как минимум послужит основой-примером для кастомного модуля.

Аватар пользователя gun_dose gun_dose 23 февраля 2019 в 11:26

Вот именно с такой ошибкой я не сталкивался, но по опыту могу сказать, что в jsonapi иногда бывают очень странные ошибки, которые даже воспроизвести не всегда получается.

Аватар пользователя Max-Z Max-Z 23 февраля 2019 в 11:51

Как я понял, JSONAPI к этому не имеет отношения. Ошибка возникает на другом уровне - во время создания новых нодов. Я даже закрыл issue для JSONAPI, созданное ранее.