Skip to content

Commit 13a090c

Browse files
feat: spread feed fetch scheduling deterministically over the hour (#2227)
Previously, all RSS feeds were fetched at the top of each hour (minute 0), which could cause load spikes. This change spreads feed fetches evenly throughout the hour using a deterministic hash of the feed ID. Each feed is assigned a target minute (0-59) based on its ID hash, ensuring consistent scheduling across restarts while distributing the load evenly. Co-authored-by: Claude <[email protected]>
1 parent e3cc546 commit 13a090c

File tree

1 file changed

+31
-0
lines changed

1 file changed

+31
-0
lines changed

apps/workers/workers/feedWorker.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ import logger from "@karakeep/shared/logger";
1414
import { DequeuedJob, getQueueClient } from "@karakeep/shared/queueing";
1515
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
1616

17+
/**
18+
* Deterministically maps a feed ID to a minute offset within the hour (0-59).
19+
* This ensures feeds are spread evenly across the hour based on their ID.
20+
*/
21+
function getFeedMinuteOffset(feedId: string): number {
22+
// Simple hash function: sum character codes
23+
let hash = 0;
24+
for (let i = 0; i < feedId.length; i++) {
25+
hash = (hash << 5) - hash + feedId.charCodeAt(i);
26+
hash = hash & hash; // Convert to 32-bit integer
27+
}
28+
// Return a minute offset between 0 and 59
29+
return Math.abs(hash) % 60;
30+
}
31+
1732
export const FeedRefreshingWorker = cron.schedule(
1833
"0 * * * *",
1934
() => {
@@ -30,16 +45,32 @@ export const FeedRefreshingWorker = cron.schedule(
3045
const currentHour = new Date();
3146
currentHour.setMinutes(0, 0, 0);
3247
const hourlyWindow = currentHour.toISOString();
48+
const now = new Date();
49+
const currentMinute = now.getMinutes();
3350

3451
for (const feed of feeds) {
3552
const idempotencyKey = `${feed.id}-${hourlyWindow}`;
53+
const targetMinute = getFeedMinuteOffset(feed.id);
54+
55+
// Calculate delay: if target minute has passed, schedule for next hour
56+
let delayMinutes = targetMinute - currentMinute;
57+
if (delayMinutes < 0) {
58+
delayMinutes += 60;
59+
}
60+
const delayMs = delayMinutes * 60 * 1000;
61+
62+
logger.debug(
63+
`[feed] Scheduling feed ${feed.id} at minute ${targetMinute} (delay: ${delayMinutes} minutes)`,
64+
);
65+
3666
FeedQueue.enqueue(
3767
{
3868
feedId: feed.id,
3969
},
4070
{
4171
idempotencyKey,
4272
groupId: feed.userId,
73+
delayMs,
4374
},
4475
);
4576
}

0 commit comments

Comments
 (0)