Bound concurrent D1 upserts in saved-keyword metric refresh (#338)
refreshSavedKeywordMetrics fanned out one Promise per keyword across an entire location/language group, so a project with thousands of saved keywords could trigger thousands of simultaneous D1 upserts, pressuring Worker memory/CPU and D1 concurrency (potential OOM / availability DoS). Restore bounded DB-write batching: upsert in chunks of 100 so concurrent writes stay capped regardless of group size.
This commit is contained in:
parent
1a74904b67
commit
c993ef4931
@ -7,6 +7,11 @@ import {
|
||||
import type { BillingCustomerContext } from "@/server/billing/subscription";
|
||||
import type { RefreshSavedKeywordMetricsInput } from "@/types/schemas/keywords";
|
||||
|
||||
// Cap concurrent D1 upserts per group. A project can accumulate thousands of
|
||||
// saved keywords in one location/language, and fanning out one promise each
|
||||
// would flood D1/Worker resources; write in bounded chunks instead.
|
||||
const REFRESH_UPSERT_BATCH_SIZE = 100;
|
||||
|
||||
export async function refreshSavedKeywordMetrics(
|
||||
input: RefreshSavedKeywordMetricsInput,
|
||||
billingCustomer: BillingCustomerContext,
|
||||
@ -41,8 +46,10 @@ export async function refreshSavedKeywordMetrics(
|
||||
metrics.map((metric) => [metric.keyword.toLowerCase(), metric]),
|
||||
);
|
||||
|
||||
for (let i = 0; i < groupRows.length; i += REFRESH_UPSERT_BATCH_SIZE) {
|
||||
const chunk = groupRows.slice(i, i + REFRESH_UPSERT_BATCH_SIZE);
|
||||
await Promise.all(
|
||||
groupRows.map((r) => {
|
||||
chunk.map((r) => {
|
||||
const metric = byKeyword.get(r.row.keyword.toLowerCase());
|
||||
if (!metric) return Promise.resolve();
|
||||
return KeywordResearchRepository.upsertKeywordMetric({
|
||||
@ -59,6 +66,7 @@ export async function refreshSavedKeywordMetrics(
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
updated += byKeyword.size;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user