## Linear issue https://linear.app/chatwoot/issue/CW-6289/limit-the-number-of-notifications-per-user-to-300 ## Description Limits the number of notifications per user to 300 by introducing an async trim job that runs after each notification creation. This prevents unbounded notification growth that was causing DB CPU spikes. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] This change requires a documentation update ## How Has This Been Tested? - Added unit tests for TrimUserNotificationsJob ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Implements a dedicated purge job to control notification volume and scheduling. > > - Introduces `Notification::RemoveOldNotificationJob` (queue: `purgable`) to delete notifications older than 1 month and trim each user to the 300 most recent (deterministic by `created_at DESC, id DESC`) > - Adds daily cron (`remove_old_notification_job` at 22:30 UTC, queue `purgable`) in `config/schedule.yml` > - Removes ad-hoc triggering of the purge from `TriggerScheduledItemsJob` > - Adds/updates specs covering enqueue queue, old-notification deletion, per-user trimming, and combined behavior > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ea2b48e36df96cd15d4119d1dd7dcf5250695de. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
55 lines
1.7 KiB
Ruby
55 lines
1.7 KiB
Ruby
class Notification::RemoveOldNotificationJob < ApplicationJob
|
|
queue_as :purgable
|
|
|
|
NOTIFICATION_LIMIT = 300
|
|
OLD_NOTIFICATION_THRESHOLD = 1.month
|
|
|
|
def perform
|
|
remove_old_notifications
|
|
trim_user_notifications
|
|
end
|
|
|
|
private
|
|
|
|
def remove_old_notifications
|
|
Notification.where('created_at < ?', OLD_NOTIFICATION_THRESHOLD.ago)
|
|
.delete_all
|
|
end
|
|
|
|
def trim_user_notifications
|
|
# Find users with more than NOTIFICATION_LIMIT notifications
|
|
user_ids_exceeding_limit.each do |user_id|
|
|
trim_notifications_for_user(user_id)
|
|
end
|
|
end
|
|
|
|
def user_ids_exceeding_limit
|
|
Notification.group(:user_id)
|
|
.having('COUNT(*) > ?', NOTIFICATION_LIMIT)
|
|
.pluck(:user_id)
|
|
end
|
|
|
|
def trim_notifications_for_user(user_id)
|
|
# Find the cutoff notification (the 301st when we want to keep top 300)
|
|
# Order by created_at DESC, then id DESC for deterministic ordering
|
|
cutoff = Notification.where(user_id: user_id)
|
|
.order(created_at: :desc, id: :desc)
|
|
.offset(NOTIFICATION_LIMIT)
|
|
.limit(1)
|
|
.pick(:created_at, :id)
|
|
|
|
return unless cutoff
|
|
|
|
cutoff_time, cutoff_id = cutoff
|
|
|
|
# Delete notifications older than cutoff, or same timestamp but lower/equal ID
|
|
# Since we order by id DESC, higher IDs are kept (come first), lower IDs deleted
|
|
# This avoids race conditions: notifications created after finding the cutoff
|
|
# will have timestamps > cutoff_time and won't be incorrectly deleted
|
|
Notification.where(user_id: user_id)
|
|
.where('created_at < ? OR (created_at = ? AND id <= ?)',
|
|
cutoff_time, cutoff_time, cutoff_id)
|
|
.delete_all
|
|
end
|
|
end
|