-
Notifications
You must be signed in to change notification settings - Fork 590
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
feat: create database tables and queries for notifications #13536
base: main
Are you sure you want to change the base?
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. Join @dannykopping and the rest of your teammates on |
1cb776a
to
25636ec
Compare
coderd/database/migrations/testdata/fixtures/000219_notifications.up.sql
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments below, but nothing blocking.
1c4046c
to
538bc42
Compare
Signed-off-by: Danny Kopping <danny@coder.com>
Signed-off-by: Danny Kopping <danny@coder.com>
Signed-off-by: Danny Kopping <danny@coder.com>
('b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12', 'B', TRUE, template, template, 'Group 1'), | ||
('c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a13', 'C', TRUE, template, template, 'Group 2'); | ||
|
||
INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably fine in a test fixture, but we should avoid using the schema name public
since that's not what it's always called in customer databases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, good catch!
@@ -0,0 +1,5 @@ | |||
DROP TABLE IF EXISTS notification_messages; | |||
DROP TABLE IF EXISTS notification_templates; | |||
DROP TYPE IF EXISTS notification_receiver; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this type is created in the UP migration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this was a rename gone wrong.
enabled boolean DEFAULT TRUE NOT NULL, | ||
title_template text NOT NULL, | ||
body_template text NOT NULL, | ||
actions jsonb, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is "actions"? It doesn't even appear in your INSERT query and doesn't appear to be used anywhere in the PR upstack from here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your other comment about the InsertNotificationTemplate
is correct; this is actually not needed right now. It'll be used later on; I'll remove it.
The actions
are the calls-to-action which are associated with the template; see the insert at the bottom of this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it now. The upstack PR does use it, I can see. I didn't find it because I was looking at the main NotificationTemplate
type, and it's used in FetchNewMessageMetadataRow
leased_until = NOW() + CONCAT(sqlc.arg('lease_seconds')::int, ' seconds')::interval | ||
WHERE id IN (SELECT nm.id | ||
FROM notification_messages AS nm | ||
LEFT JOIN notification_templates AS nt ON (nm.notification_template_id = nt.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does this need to join to notification_templates
? I don't see any reference to nt
in the WHERE
clause...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another good catch; this is the result of an incomplete refactoring.
SKIP LOCKED | ||
LIMIT sqlc.arg('count')) | ||
RETURNING id) | ||
SELECT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be easier to read as a single UPDATE
e.g.
UPDATE notification_messages as nm
SET
...
FROM notification_templates as nt
WHERE
nm.notification_template_id = nt.template_id AND
...
RETURNING
...
It's not standard SQL, but postgres essentially does a join to tables in FROM
, which allows you to use them in SET
(not needed here) and RETURNING
(what we want).
If that's too strange, I at least think it's clearer to return all the columns of notification_messages
we want from the UPDATE
, then do a single join to notification_templates
, rather than returning the IDs and then doing two joins: back to itself and to notification_templates
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will look into this 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other idea: this query and also the FetchNewMessageMetadata
query are complicated by the need to pull NotificationTemplate data. But there is a small, finite number of NotificationTemplates, and we don't allow modifications... so, we could just lazily load the NotificationTemplates and cache them in the notifier/manager.
That makes the queries simpler to understand (FetchNewMessageMetadata is probably not needed since you can just query the user), and likely faster to execute since Postgres doesn't need to do a join.
At some point down the line if we allow people to modify the templates at runtime, we'd need to invalidate the cache, but that seems pretty simple via the PubSub.
notification_messages | ||
SET updated_at = NOW(), | ||
status = 'leased'::notification_message_status, | ||
status_reason = 'Leased by notifier ' || sqlc.arg('notifier_id')::int, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned that in a distributed system, these IDs are assigned locally, and thus don't really tell us which notifier has the message leased
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point! Funnily enough I had this as a UUID previously but reverted to an int for some reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be implemented in the PR up the stack.
leased_until = NULL, | ||
next_retry_after = NULL | ||
FROM (SELECT id, sent_at | ||
FROM new_values) AS subquery |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we really need both the WITH
clause and this clause remapping new_values
to subquery
? Is it not supported to just have
FROM (SELECT UNNEST(@ids::uuid[]) AS id,
UNNEST(@sent_ats::timestamptz[]) AS sent_at) as new_values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it is, thanks 👍
FROM notification_messages AS nested | ||
WHERE nested.updated_at < NOW() - INTERVAL '7 days' | ||
-- ensure we don't clash with the notifier | ||
FOR UPDATE SKIP LOCKED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this SELECT FOR UPDATE SKIP LOCKED
business is just a performance optimization, and unless we're running these queries very often I don't think it's needed. A plain ole DELETE
statement will wait for locked rows. The SKIP LOCKED
construction causes it to skip locked rows rather than wait for them.
@@ -0,0 +1,139 @@ | |||
-- name: InsertNotificationTemplate :one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? I thought all templates are going to be set by migrations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessary right now, correct. This was from an earlier design phase and I missed it; will remove.
Signed-off-by: Danny Kopping <danny@coder.com>
538bc42
to
438bcba
Compare
Creates two new tables:
notification_templates
: templates are used to create notifications from, and specify a unique event in the systemnotification_messages
: each record corresponds to a separate notification which must be delivered to a userAcquireNotificationMessages
)coderd/database/migrations/000219_notifications.up.sql
inserts a single template ("Workspace Deleted"), for which we are creating this vertical slice in this initial stack of PRs.