-
Notifications
You must be signed in to change notification settings - Fork 930
feat: add notification deduplication trigger #14172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Signed-off-by: Danny Kopping <danny@coder.com>
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
DROP TRIGGER IF EXISTS update_notification_message_dedupe_hash ON notification_messages; | ||
DROP FUNCTION IF EXISTS compute_notification_message_dedupe_hash(); | ||
ALTER TABLE IF EXISTS notification_messages | ||
DROP COLUMN IF EXISTS dedupe_hash; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
-- Add a column to store the hash. | ||
ALTER TABLE IF EXISTS notification_messages | ||
ADD COLUMN IF NOT EXISTS dedupe_hash TEXT NULL; | ||
|
||
COMMENT ON COLUMN notification_messages.dedupe_hash IS 'Auto-generated by insert/update trigger, used to prevent duplicate notifications from being enqueued on the same day'; | ||
|
||
-- Ensure that multiple notifications with identical hashes cannot be inserted into the table. | ||
CREATE UNIQUE INDEX ON notification_messages (dedupe_hash); | ||
|
||
-- Computes a hash from all unique messages fields and the current day; this will help prevent duplicate messages from being sent within the same day. | ||
-- It is possible that a message could be sent at 23:59:59 and again at 00:00:00, but this should be good enough for now. | ||
-- This could have been a unique index, but we cannot immutably create an index on a timestamp with a timezone. | ||
CREATE OR REPLACE FUNCTION compute_notification_message_dedupe_hash() RETURNS TRIGGER AS | ||
$$ | ||
BEGIN | ||
NEW.dedupe_hash := MD5(CONCAT_WS(':', | ||
NEW.notification_template_id, | ||
NEW.user_id, | ||
NEW.method, | ||
NEW.payload::text, | ||
ARRAY_TO_STRING(NEW.targets, ','), | ||
DATE_TRUNC('day', NEW.created_at AT TIME ZONE 'UTC')::text | ||
johnstcn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
)); | ||
RETURN NEW; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
|
||
COMMENT ON FUNCTION compute_notification_message_dedupe_hash IS 'Computes a unique hash which will be used to prevent duplicate messages from being enqueued on the same day'; | ||
CREATE TRIGGER update_notification_message_dedupe_hash | ||
BEFORE INSERT OR UPDATE | ||
ON notification_messages | ||
FOR EACH ROW | ||
EXECUTE FUNCTION compute_notification_message_dedupe_hash(); | ||
dannykopping marked this conversation as resolved.
Show resolved
Hide resolved
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,14 +13,15 @@ WHERE nt.id = @notification_template_id | |
AND u.id = @user_id; | ||
|
||
-- name: EnqueueNotificationMessage :exec | ||
INSERT INTO notification_messages (id, notification_template_id, user_id, method, payload, targets, created_by) | ||
INSERT INTO notification_messages (id, notification_template_id, user_id, method, payload, targets, created_by, created_at) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added |
||
VALUES (@id, | ||
@notification_template_id, | ||
@user_id, | ||
@method::notification_method, | ||
@payload::jsonb, | ||
@targets, | ||
@created_by); | ||
@created_by, | ||
@created_at); | ||
|
||
-- Acquires the lease for a given count of notification messages, to enable concurrent dequeuing and subsequent sending. | ||
-- Only rows that aren't already leased (or ones which are leased but have exceeded their lease period) are returned. | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ import ( | |
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/coder/quartz" | ||
|
||
"github.com/coder/serpent" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another instance of import grouping, this is the job of the tooling tbh (and it's failing), so up to you if you want to fix it 😄. (There are actually a few more of these too but I'll leave those uncommented.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll leave this for now; we need the tooling to be updated as you suggest. |
||
|
||
"github.com/coder/coder/v2/coderd/database" | ||
|
@@ -61,7 +63,7 @@ func TestMetrics(t *testing.T) { | |
method: handler, | ||
}) | ||
|
||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer")) | ||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer"), quartz.NewReal()) | ||
require.NoError(t, err) | ||
|
||
user := createSampleUser(t, store) | ||
|
@@ -228,7 +230,7 @@ func TestPendingUpdatesMetric(t *testing.T) { | |
method: handler, | ||
}) | ||
|
||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer")) | ||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer"), quartz.NewReal()) | ||
require.NoError(t, err) | ||
|
||
user := createSampleUser(t, store) | ||
|
@@ -305,7 +307,7 @@ func TestInflightDispatchesMetric(t *testing.T) { | |
method: delayer, | ||
}) | ||
|
||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer")) | ||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer"), quartz.NewReal()) | ||
require.NoError(t, err) | ||
|
||
user := createSampleUser(t, store) | ||
|
@@ -384,7 +386,7 @@ func TestCustomMethodMetricCollection(t *testing.T) { | |
customMethod: webhookHandler, | ||
}) | ||
|
||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer")) | ||
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer"), quartz.NewReal()) | ||
require.NoError(t, err) | ||
|
||
user := createSampleUser(t, store) | ||
|
Uh oh!
There was an error while loading. Please reload this page.