Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions api/v1_notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const (
// holding hot-standby snapshots and making the replica fall behind.
notificationReadTimeout = 8 * time.Second

// Unread polling runs frequently in clients. If the multi-recipient array
// path is cold, fall back quickly instead of surfacing a 504.
notificationUnreadPollTimeout = 3 * time.Second

// Historically `limit=0` fell back to the default limit of 20, so unread
// polling counted at most the first page of notification groups.
notificationUnreadPollLimit = 20
Expand All @@ -39,6 +43,11 @@ const (
// LIMIT.
notificationReadCandidateLimit = 25000

// Old timestamp pagination can still have years of history behind the
// cursor. A smaller window is enough to find many candidate groups while
// avoiding cold reads over tens of thousands of historical rows.
notificationTimestampPaginationCandidateLimit = 2000

// Keep the expensive validation/JSON aggregation work bounded after the
// newest notification groups have been selected.
notificationGroupCandidateLimit = 200
Expand Down Expand Up @@ -143,7 +152,7 @@ FROM (
LIMIT @limit
) unread_notifications;
`
ctx, cancel := context.WithTimeout(c.Context(), notificationReadTimeout)
ctx, cancel := context.WithTimeout(c.Context(), notificationUnreadPollTimeout)
defer cancel()

var unreadCount int
Expand All @@ -158,7 +167,17 @@ FROM (
})
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return fiber.NewError(fiber.StatusGatewayTimeout, "notifications unread query timed out")
return c.JSON(fiber.Map{
"data": fiber.Map{
"notifications": []notificationRow{},
"unread_count": notificationUnreadPollLimit,
},
"related": fiber.Map{
"users": []any{},
"tracks": []any{},
"playlists": []any{},
},
})
}
return err
}
Expand Down Expand Up @@ -578,6 +597,10 @@ limit @limit::int
;
`
userId := app.getUserId(c)
candidateLimit := notificationReadCandidateLimit
if params.Timestamp > 0 {
candidateLimit = notificationTimestampPaginationCandidateLimit
}

ctx, cancel := context.WithTimeout(c.Context(), notificationReadTimeout)
defer cancel()
Expand All @@ -592,7 +615,7 @@ limit @limit::int
}

if usesCandidateLimit {
args["candidate_limit"] = notificationReadCandidateLimit
args["candidate_limit"] = candidateLimit
args["group_candidate_limit"] = notificationGroupCandidateLimit
args["actions_per_group_limit"] = notificationActionsPerGroupLimit
}
Expand Down
Loading