При высокой нагрузке близкой к 100% пользователь может совершить транзакцию на создание объекта в базе. Сама транзакция может подвиснуть. Пользователь не дожидаясь ответа обновляет страницу и снова создает объект. Получается уже две транзакции в очереди. По итогу когда очередь доходит до выполнения имеем в базе два одинаковых объекта (созданных вплоть до ms) что ломает логику программы.
Очень плохо что вы довели систему до нагрузки 100%. Обычно concurrency работает хорошо когда мы не доводим до такого состояния. Есть даже такой термин thread starvation (голодание потоков) когда потоки никак не могут получить квант времени.
Без кода и лог-файлов тут нечего обсуждать. Я просто могу дать несколько направлений на подумать.
1)
CQRS (Command-Query-Separation) - это шаблон разработки при котором команды на изменение данных и запросы на их чтение идут независимо и существуют как-бы в разных временных эпохах. Это дает возможность масштабировать системы довольно сильно. И такие системы обычно лишены блокирок.
2)
Idempotency- это два свойства бизнес операций. Идемпотентность например предполагает что если платежная система дважды продублировала ваш платеж (MQ/сетевые replays) за покупку чашки Кофе например то это не означает что с вас банк снимет дважды деньги. На самом деле каждая ваша карточная операция имеет уникальный ID и с точки зрения биллинга будет применение платежа только 1 раз с одним уникальным ID. Второй платеж-дубль будет проигнорирован. Это свойство часто используется в Apache Kafka как один из способов поднять скорость и надежность.
3) Когда ваша база или сервер приложений находтся в состоянии как-бы "агонии" то не стоит пытатся добивать ее повторами операций. А стоит на некоторое время прикрыть канал операций. Или разорвать цепь предохранителя. Как делают в электрике при повышенной нагрузке. Есть такой шаблон
Cirquit Breaker. Аварийный размыкатель. Netflix его активно использует.
Вот подумайте.