при удалении сообщения нужно атомарно:
1. Удалить запись из БД
2. Удалить файл из S3
3. Разослать уведомление по WebSocket (in-memory хранятся)
А зачем вообще все три составляющие пихать в транзакцию?
Тут атомарность и транзакционность только для БД нужна, для остального нужна идемпотентность и асинхронность. Удаление из S3 это просто "подчистка кеша/артефактов" по сути, а WebSocket — как ненадёжный best-effort event notify
hint. Последние два могут вообще прийти заметно позже, чем факт транзакции в БД, кто от этого пострадает то? Просто держишь БД за источник консистентной истины, а остальное доводишь уже в отдельном воркере асинхронно как логический коммит.
Например, можно при транзакции БД помечать записать как soft-deleted, а уже когда отрабатывает полный коммит подчистки всех артефактов, можно сделать hard delete, если память не бесконечная.
Поллинг — ну такое, лучше будет pub/sub. Поллинг можно оставить как fallback.