Skip to content

Commit caba6a9

Browse files
authored
chore: smaller limit on rm stale (#375)
1 parent f38ada6 commit caba6a9

2 files changed

Lines changed: 52 additions & 29 deletions

File tree

packages/app/server/routes/rm/stale.post.ts

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import type { H3Event } from "h3";
22

33
export default eventHandler(async (event) => {
4-
setResponseHeader(event, "Cache-Control", "no-cache");
5-
setResponseHeader(event, "Content-Type", "application/json");
6-
74
const rmStaleKeyHeader = getHeader(event, "sb-rm-stale-key");
85
const signal = toWebRequest(event).signal;
96
const { rmStaleKey } = useRuntimeConfig(event);
@@ -18,40 +15,65 @@ export default eventHandler(async (event) => {
1815

1916
const result = await iterateAndDelete(event, signal, {
2017
prefix: bucket === 'packages' ? usePackagesBucket.base : useTemplatesBucket.base,
21-
limit: 100,
18+
limit: 1000,
2219
cursor: cursor || undefined,
2320
}, remove);
2421

22+
setResponseHeader(event, "Content-Type", "application/json");
23+
2524
return {
2625
result,
2726
};
2827
});
2928

29+
// Helper for concurrency limiting
30+
async function mapWithConcurrency<T, R>(items: T[], concurrency: number, fn: (item: T) => Promise<R>): Promise<R[]> {
31+
const results: R[] = [];
32+
let i = 0;
33+
const executing: Promise<void>[] = [];
34+
35+
async function enqueue(item: T) {
36+
const result = await fn(item);
37+
results.push(result);
38+
}
39+
40+
while (i < items.length) {
41+
const item = items[i++];
42+
const p = enqueue(item);
43+
executing.push(p.then(() => {
44+
executing.splice(executing.indexOf(p), 1);
45+
}));
46+
if (executing.length >= concurrency) {
47+
await Promise.race(executing);
48+
}
49+
}
50+
await Promise.all(executing);
51+
return results;
52+
}
53+
3054
async function iterateAndDelete(event: H3Event, signal: AbortSignal, opts: R2ListOptions, remove: boolean) {
3155
const binding = useBinding(event);
3256
let truncated = true;
33-
let cursor: string | undefined;
57+
let cursor: string | undefined = opts.cursor;
58+
let processed = 0;
3459
const removedItems: Array<{ key: string; uploaded: Date; downloadedAt?: Date }> = [];
3560
const downloadedAtBucket = useDownloadedAtBucket(event);
3661
const today = Date.parse(new Date().toString());
62+
const CONCURRENCY = 10;
3763

3864
while (truncated && !signal.aborted) {
39-
if (removedItems.length >= 1000) {
40-
break
65+
if (removedItems.length >= 100 || processed >= 10000) {
66+
break;
4167
}
4268
const next = await binding.list({
4369
...opts,
4470
cursor,
4571
});
46-
for (const object of next.objects) {
47-
if (signal.aborted) {
48-
break;
49-
}
72+
processed += next.objects.length;
73+
74+
await mapWithConcurrency(next.objects, CONCURRENCY, async (object) => {
75+
if (signal.aborted) return;
5076
const uploaded = Date.parse(object.uploaded.toString());
51-
// removedItems.push({
52-
// key: object.key,
53-
// uploaded: new Date(object.uploaded),
54-
// });
5577
const uploadedDate = new Date(uploaded);
5678
const sixMonthsAgo = new Date(today);
5779
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
@@ -64,12 +86,11 @@ async function iterateAndDelete(event: H3Event, signal: AbortSignal, opts: R2Lis
6486
await binding.delete(object.key);
6587
await downloadedAtBucket.removeItem(object.key);
6688
}
67-
continue;
89+
return;
6890
}
6991
const downloadedAt = await downloadedAtBucket.getItem(object.key);
70-
7192
if (!downloadedAt) {
72-
continue;
93+
return;
7394
}
7495
const downloadedAtDate = new Date(downloadedAt);
7596
const oneMonthAgo = new Date(today);
@@ -88,9 +109,9 @@ async function iterateAndDelete(event: H3Event, signal: AbortSignal, opts: R2Lis
88109
await binding.delete(object.key);
89110
await downloadedAtBucket.removeItem(object.key);
90111
}
91-
92112
}
93-
}
113+
});
114+
94115
truncated = next.truncated;
95116
if (next.truncated) {
96117
cursor = next.cursor;
@@ -103,3 +124,4 @@ async function iterateAndDelete(event: H3Event, signal: AbortSignal, opts: R2Lis
103124
};
104125
}
105126

127+

script/remove-stale.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ async function processBucket(bucket) {
1818
remove,
1919
};
2020
try {
21+
console.log(`[${bucket}] Batch ${batch} - Fetching...`);
2122
const res = await fetch(endpoint, {
2223
method: 'POST',
2324
headers: {
@@ -26,26 +27,24 @@ async function processBucket(bucket) {
2627
},
2728
body: JSON.stringify(body),
2829
});
29-
const text = await res.text();
30-
let json;
31-
try {
32-
json = JSON.parse(text);
33-
} catch (e) {
34-
console.error(`[${bucket}] Batch ${batch} - Failed to parse response:`, text);
35-
process.exit(1);
36-
}
3730
if (!res.ok) {
3831
console.error(`[${bucket}] Batch ${batch} - Request failed:`, json);
3932
process.exit(1);
4033
}
34+
35+
const json = await res.json();
36+
4137
console.log(`[${bucket}] Batch ${batch} - Removed items:`, json.result.removedItems.length);
4238
if (json.result.removedItems.length > 0) {
4339
for (const item of json.result.removedItems) {
4440
console.log(` -`, item);
4541
}
4642
}
43+
console.log('just fetched', json.result.removedItems.length)
4744
cursor = json.result.cursor;
4845
truncated = json.result.truncated;
46+
console.log('cursor', cursor)
47+
console.log('truncated', truncated)
4948
batch++;
5049
if (!truncated) {
5150
console.log(`[${bucket}] Completed. Total batches: ${batch}`);
@@ -58,8 +57,10 @@ async function processBucket(bucket) {
5857
}
5958

6059
(async () => {
61-
for (const bucket of ['packages', 'templates']) {
60+
// for (const bucket of ['packages', 'templates']) {
61+
for (const bucket of ['packages']) {
6262
console.log(`Processing bucket: ${bucket}`);
6363
await processBucket(bucket);
6464
}
65+
process.exit(0);
6566
})();

0 commit comments

Comments
 (0)