Skip to content

Commit 6e09672

Browse files
Cap the number of errors and warnings for bulk KV put (#9776)
Co-authored-by: Dario Piotrowicz <[email protected]>
1 parent dacfc35 commit 6e09672

File tree

4 files changed

+103
-8
lines changed

4 files changed

+103
-8
lines changed

.changeset/shaggy-frogs-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Cap the number of errors and warnings for bulk KV put to avoid consuming too much memory

packages/wrangler/src/__tests__/kv.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { writeFileSync } from "node:fs";
22
import { http, HttpResponse } from "msw";
3+
import { BATCH_MAX_ERRORS_WARNINGS } from "../kv/helpers";
34
import { endEventLoop } from "./helpers/end-event-loop";
45
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
56
import { mockConsoleMethods } from "./helpers/mock-console";
@@ -1609,6 +1610,79 @@ describe("wrangler", () => {
16091610
"
16101611
`);
16111612
});
1613+
1614+
it("should cap the number of errors", async () => {
1615+
const keyValues = [...Array(BATCH_MAX_ERRORS_WARNINGS + 5).keys()];
1616+
1617+
writeFileSync("./keys.json", JSON.stringify(keyValues));
1618+
await expect(
1619+
runWrangler(`kv bulk put --namespace-id some-namespace-id keys.json`)
1620+
).rejects.toThrowErrorMatchingInlineSnapshot(`
1621+
[Error: Unexpected JSON input from "keys.json".
1622+
Each item in the array should be an object that matches:
1623+
1624+
interface KeyValue {
1625+
key: string;
1626+
value: string;
1627+
expiration?: number;
1628+
expiration_ttl?: number;
1629+
metadata?: object;
1630+
base64?: boolean;
1631+
}
1632+
1633+
The item at index 0 is 0
1634+
The item at index 1 is 1
1635+
The item at index 2 is 2
1636+
The item at index 3 is 3
1637+
The item at index 4 is 4
1638+
The item at index 5 is 5
1639+
The item at index 6 is 6
1640+
The item at index 7 is 7
1641+
The item at index 8 is 8
1642+
The item at index 9 is 9
1643+
The item at index 10 is 10
1644+
The item at index 11 is 11
1645+
...]
1646+
`);
1647+
1648+
expect(std.warn).toMatchInlineSnapshot(`""`);
1649+
});
1650+
1651+
it("should cap the number of warnings", async () => {
1652+
const keyValues: KeyValue[] = new Array(
1653+
BATCH_MAX_ERRORS_WARNINGS + 5
1654+
).fill({
1655+
key: "k",
1656+
value: "v",
1657+
invalid: true,
1658+
});
1659+
writeFileSync("./keys.json", JSON.stringify(keyValues));
1660+
const requests = mockPutRequest("some-namespace-id", keyValues);
1661+
await runWrangler(
1662+
`kv bulk put --namespace-id some-namespace-id keys.json`
1663+
);
1664+
expect(requests.count).toEqual(1);
1665+
1666+
expect(std.warn).toMatchInlineSnapshot(`
1667+
"▲ [WARNING] Unexpected key-value properties in \\"keys.json\\".
1668+
1669+
The item at index 0 contains unexpected properties: [\\"invalid\\"].
1670+
The item at index 1 contains unexpected properties: [\\"invalid\\"].
1671+
The item at index 2 contains unexpected properties: [\\"invalid\\"].
1672+
The item at index 3 contains unexpected properties: [\\"invalid\\"].
1673+
The item at index 4 contains unexpected properties: [\\"invalid\\"].
1674+
The item at index 5 contains unexpected properties: [\\"invalid\\"].
1675+
The item at index 6 contains unexpected properties: [\\"invalid\\"].
1676+
The item at index 7 contains unexpected properties: [\\"invalid\\"].
1677+
The item at index 8 contains unexpected properties: [\\"invalid\\"].
1678+
The item at index 9 contains unexpected properties: [\\"invalid\\"].
1679+
The item at index 10 contains unexpected properties: [\\"invalid\\"].
1680+
The item at index 11 contains unexpected properties: [\\"invalid\\"].
1681+
...
1682+
1683+
"
1684+
`);
1685+
});
16121686
});
16131687

16141688
describe("delete", () => {

packages/wrangler/src/kv/helpers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const API_MAX = 10000;
1616
// The const below are lowered from the API's true capacity to help avoid
1717
// hammering it with large requests.
1818
export const BATCH_KEY_MAX = API_MAX / 10;
19+
// Limit the number of errors or warnings to logs during a bulk put.
20+
// They might end up filling memory for invalid inputs.
21+
export const BATCH_MAX_ERRORS_WARNINGS = 12;
1922

2023
type KvArgs = {
2124
binding?: string;

packages/wrangler/src/kv/index.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { parseJSON, readFileSync, readFileSyncToBuffer } from "../parse";
1616
import { requireAuth } from "../user";
1717
import { getValidBindingName } from "../utils/getValidBindingName";
1818
import {
19+
BATCH_MAX_ERRORS_WARNINGS,
1920
createKVNamespace,
2021
deleteKVBulkKeyValue,
2122
deleteKVKeyValue,
@@ -687,20 +688,32 @@ export const kvBulkPutCommand = createCommand({
687688
);
688689
}
689690

691+
let maxNumberOfErrorsReached = false;
690692
const errors: string[] = [];
693+
let maxNumberOfWarningsReached = false;
691694
const warnings: string[] = [];
692695
for (let i = 0; i < content.length; i++) {
693696
const keyValue = content[i];
694-
if (!isKVKeyValue(keyValue)) {
695-
errors.push(`The item at index ${i} is ${JSON.stringify(keyValue)}`);
697+
if (!isKVKeyValue(keyValue) && !maxNumberOfErrorsReached) {
698+
if (errors.length === BATCH_MAX_ERRORS_WARNINGS) {
699+
maxNumberOfErrorsReached = true;
700+
errors.push("...");
701+
} else {
702+
errors.push(`The item at index ${i} is ${JSON.stringify(keyValue)}`);
703+
}
696704
} else {
697705
const props = unexpectedKVKeyValueProps(keyValue);
698-
if (props.length > 0) {
699-
warnings.push(
700-
`The item at index ${i} contains unexpected properties: ${JSON.stringify(
701-
props
702-
)}.`
703-
);
706+
if (props.length > 0 && !maxNumberOfWarningsReached) {
707+
if (warnings.length === BATCH_MAX_ERRORS_WARNINGS) {
708+
maxNumberOfWarningsReached = true;
709+
warnings.push("...");
710+
} else {
711+
warnings.push(
712+
`The item at index ${i} contains unexpected properties: ${JSON.stringify(
713+
props
714+
)}.`
715+
);
716+
}
704717
}
705718
}
706719
}

0 commit comments

Comments
 (0)