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 { BillingCustomerContext } from "@/server/billing/subscription";
|
||||||
import type { RefreshSavedKeywordMetricsInput } from "@/types/schemas/keywords";
|
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(
|
export async function refreshSavedKeywordMetrics(
|
||||||
input: RefreshSavedKeywordMetricsInput,
|
input: RefreshSavedKeywordMetricsInput,
|
||||||
billingCustomer: BillingCustomerContext,
|
billingCustomer: BillingCustomerContext,
|
||||||
@ -41,8 +46,10 @@ export async function refreshSavedKeywordMetrics(
|
|||||||
metrics.map((metric) => [metric.keyword.toLowerCase(), metric]),
|
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(
|
await Promise.all(
|
||||||
groupRows.map((r) => {
|
chunk.map((r) => {
|
||||||
const metric = byKeyword.get(r.row.keyword.toLowerCase());
|
const metric = byKeyword.get(r.row.keyword.toLowerCase());
|
||||||
if (!metric) return Promise.resolve();
|
if (!metric) return Promise.resolve();
|
||||||
return KeywordResearchRepository.upsertKeywordMetric({
|
return KeywordResearchRepository.upsertKeywordMetric({
|
||||||
@ -59,6 +66,7 @@ export async function refreshSavedKeywordMetrics(
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updated += byKeyword.size;
|
updated += byKeyword.size;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user