From 6a9b2b8d1de38c9e68dc23303f1909f05a077116 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 18 Dec 2025 15:04:37 +0100 Subject: [PATCH] Add index on (user_id, shown) in notification table to optimize markNotificationsShown queries and prevent deadlocks. Implement transaction handling in markNotificationsShown method for atomic updates. --- ...0000-add-notification-user-shown-index.cjs | 30 +++++++++++++++++++ backend/services/falukantService.js | 25 ++++++++++++---- 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 backend/migrations/20260118000000-add-notification-user-shown-index.cjs diff --git a/backend/migrations/20260118000000-add-notification-user-shown-index.cjs b/backend/migrations/20260118000000-add-notification-user-shown-index.cjs new file mode 100644 index 0000000..d2b876e --- /dev/null +++ b/backend/migrations/20260118000000-add-notification-user-shown-index.cjs @@ -0,0 +1,30 @@ +"use strict"; + +module.exports = { + async up(queryInterface, Sequelize) { + // Create index on (user_id, shown) to optimize markNotificationsShown queries + // This prevents deadlocks by allowing fast lookups and reducing lock contention + await queryInterface.sequelize.query(` + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'i' + AND c.relname = 'idx_notification_user_id_shown' + AND n.nspname = 'falukant_log' + ) THEN + CREATE INDEX idx_notification_user_id_shown + ON falukant_log.notification (user_id, shown); + END IF; + END$$; + `); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.sequelize.query(` + DROP INDEX IF EXISTS falukant_log.idx_notification_user_id_shown; + `); + } +}; + diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 5c9351b..40ecea0 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -4382,11 +4382,26 @@ class FalukantService extends BaseService { async markNotificationsShown(hashedUserId) { const user = await getFalukantUserOrFail(hashedUserId); - const [count] = await Notification.update( - { shown: true }, - { where: { userId: user.id, shown: false } } - ); - return { updated: count }; + + // Use transaction to prevent deadlocks and ensure atomicity + const transaction = await sequelize.transaction({ + isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED + }); + + try { + const [count] = await Notification.update( + { shown: true }, + { + where: { userId: user.id, shown: false }, + transaction + } + ); + await transaction.commit(); + return { updated: count }; + } catch (error) { + await transaction.rollback(); + throw error; + } } async getPoliticalOfficeHolders(hashedUserId) {