Добрый день!
Столкнулся с критической проблемой, ставящей под сомнение весь проект.
Создан кастомный 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...
Комментарии
Для множественных пост-запросов используйте subrequests, либо кастомный эндпоинт.
Спасибо!
Похоже, что subrequests - это как раз то, что нужно.
Либо из самого реакта запускать запросы последовательно, то есть каждый последующий запускать только после получения ответа от предыдущего.
Subrequests работает, штука отличная, но к текущей проблеме, к сожалению, это не имеет отношения.
Я всё ещё получаю упомянутую ошибку Lock wait timeout exceeded при множественных POST-операциях.
Видимо, всё же необходимо использовать Lock API.
Вопрос только в том, как прописать в моем кастомном модуле момент, когда нужно сделать acquire lock, а когда release?
Так не надо слать много POST-запросов) Запросы небось однотипные на создание/апдейт сущностей? Если да, то ситуация такая - нужно получать последний айдишник из таблицы сущностей + лочить таблицу. А поскольку параллельно идёт слишком много запросов, то нагрузка на сервер возрастает и увеличивается время обработки каждого из запросов, поэтому времени блокировки может не хватить. Кроме того, при таких запросах есть ненулевая вероятность получить фатал типа "Duplicate entry".
Так что самый правильный выход - пересмотреть логику фронтенд-приложения, чтобы не слать параллельно много запросов.
Приложение - это галерея фотографий. Контент создаётся с помощью Друпал, выводится через React.

Пользователь может создавать в Друпал албомы, а из React он может добавить фото сразу в несколько альбомов:
При этой операции исполняются множественные POST запросы через subrequests.
Буду искать способы управления этим замком на время исполнения подобных операций.
А потом упрётесь в ограничение времени выполнения скрипта)) Тут явно нужно делать цикл на async await
На async/await у меня вся логика построена, иначе ничего не работает вообще:) Проблема как тригернуть этот замок из Реакта.
Зачем? Вы точно представляете, как работает lock в базе данных?
Нет, но это советуется во многих источниках в качестве решения ошибки 1205.
По идее мне нужно сделать aquire lock перед цепью последовательных запросов и release по выполнению.
Буду признателен за идеи, как это реализовать:)
1. У вас запросы не последовательные, а параллельные.
2. aquire lock нужен для того, чтобы заблочить таблицы, пока операция не выполнится. У вас же запускаются параллельно несколько операций, каждая со своим локом. Если вы поставите лок перед операциями, то ни одна из них не выполнится.
Логично...
Может быть зайти со стороны Entity API и привязать логику Lock, опираясь на это?
Я настоятельно рекомендую зайти со стороны отправки запроса. Не нужно посылать одновременно несколько запросов на создание/обновление сущностей. Это всегда будет упираться в ресурсы: где-то сервер слабее, где-то за раз захотят создать больше контента, а где-то больше юзеров и нагрузка на сервер выше. И этим невозможно управлять со стороны php. Абсолютно никак. Потому что раздельные запросы всегда поднимают раздельные экземпляры приложения, которые не только не могут взаимодействовать между собой, но даже не могут точно знать о существовании друг друга.
Можно переделать логику и завернуть все запросы в один сабреквест, но тут очень легко можно упереться в лимит времени выполнения php-скрипта. Поэтому я бы переделал саму отправку запросов, а именно слал бы каждый следующий только после получения ответа от предыдущего. Плюс такого решения в том, что со стороны бэкенда не будет локов или таймаутов, а на фронте можно будет создать процентный индикатор.
Огромное спасибо, так и сделали в итоге!
Я тестировал вчера до ночи, ошибка эта очень и очень странная. Блюпринт содержит в себе 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
}
}
Как бороться с этим чудом, не знает никто, лучше найти другой путь, совершенно верно Вы подсказали.
Хм.. если я правильно понял суть, "альбом" это сущность с "многострочным" полем типа entityreference на сущность "фото"?
Не знаю всей логики работы приложения, поэтому только предположу:
а может практичнее реализовать связь "наоборот" - в сущность "фото" добавить ссылку (поле entityreference) на альбомы, в которые это фото входит?
Тогда POST-запрос на добавление фото и "связи" его со всеми необходимыми альбомами будет только ОДИН.
И "процедура" добавления сущности фото или ее обновления в БД будет только ОДНА.
Все верно. Сущность "фото" также имеет ссылку на альбомы. Мы думали отказаться от первого варианта, но он нужен для выборки альбомов по участниками, никак не придумали, как безболезненно упразднить эту ноду.
А, так значит "альбомы" общие для пользователей (я предполагал, что у каждого пользователя свои "личные" альбомы)
Тогда, имхо, как раз логичнее и практичнее оставить только связь "фото" -> "альбом"
"фото" же связано с "участником" (наверное стандартное поле "автор")?
Останется только добавить к сущности "альбом" псевдо-поля ссылки на ссылающиеся на них фото и пользователей-участников
примеры модулей для "обратной связи" на drupal.org есть.
Поищите по словам "entity back reference"
может какой подойдет без "допиливания" или как минимум послужит основой-примером для кастомного модуля.
Вот именно с такой ошибкой я не сталкивался, но по опыту могу сказать, что в jsonapi иногда бывают очень странные ошибки, которые даже воспроизвести не всегда получается.
Как я понял, JSONAPI к этому не имеет отношения. Ошибка возникает на другом уровне - во время создания новых нодов. Я даже закрыл issue для JSONAPI, созданное ранее.