Skip to content

Commit f792f25

Browse files
authored
fix: reduce memory footprint of deleteFiles by utilizing getFilesStream and using smaller queue of promises (#2147)
1 parent 6851cd2 commit f792f25

File tree

2 files changed

+149
-21
lines changed

2 files changed

+149
-21
lines changed

src/bucket.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,6 +2028,7 @@ class Bucket extends ServiceObject {
20282028
}
20292029

20302030
const MAX_PARALLEL_LIMIT = 10;
2031+
const MAX_QUEUE_SIZE = 1000;
20312032
const errors = [] as Error[];
20322033

20332034
const deleteFile = (file: File) => {
@@ -2039,15 +2040,32 @@ class Bucket extends ServiceObject {
20392040
});
20402041
};
20412042

2042-
this.getFiles(query)
2043-
.then(([files]) => {
2043+
(async () => {
2044+
try {
2045+
let promises = [];
20442046
const limit = pLimit(MAX_PARALLEL_LIMIT);
2045-
const promises = files!.map(file => {
2046-
return limit(() => deleteFile(file));
2047-
});
2048-
return Promise.all(promises);
2049-
})
2050-
.then(() => callback!(errors.length > 0 ? errors : null), callback!);
2047+
const filesStream = this.getFilesStream(query);
2048+
2049+
for await (const curFile of filesStream) {
2050+
if (promises.length >= MAX_QUEUE_SIZE) {
2051+
await Promise.all(promises);
2052+
promises = [];
2053+
}
2054+
promises.push(
2055+
limit(() => deleteFile(curFile)).catch(e => {
2056+
filesStream.destroy();
2057+
throw e;
2058+
})
2059+
);
2060+
}
2061+
2062+
await Promise.all(promises);
2063+
callback!(errors.length > 0 ? errors : null);
2064+
} catch (e) {
2065+
callback!(e as Error);
2066+
return;
2067+
}
2068+
})();
20512069
}
20522070

20532071
deleteLabels(labels?: string | string[]): Promise<DeleteLabelsResponse>;

test/bucket.ts

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class FakeFile {
6060
options: FileOptions;
6161
metadata: {};
6262
createWriteStream: Function;
63+
delete: Function;
6364
isSameFile = () => false;
6465
constructor(bucket: Bucket, name: string, options?: FileOptions) {
6566
// eslint-disable-next-line prefer-rest-params
@@ -79,6 +80,10 @@ class FakeFile {
7980
};
8081
return ws;
8182
};
83+
84+
this.delete = () => {
85+
return Promise.resolve();
86+
};
8287
}
8388
}
8489

@@ -1226,10 +1231,35 @@ describe('Bucket', () => {
12261231
});
12271232

12281233
describe('deleteFiles', () => {
1234+
let readCount: number;
1235+
1236+
beforeEach(() => {
1237+
readCount = 0;
1238+
});
1239+
12291240
it('should accept only a callback', done => {
1230-
bucket.getFiles = (query: {}) => {
1241+
const files = [bucket.file('1'), bucket.file('2')].map(file => {
1242+
file.delete = () => {
1243+
return Promise.resolve();
1244+
};
1245+
return file;
1246+
});
1247+
1248+
const readable = new stream.Readable({
1249+
objectMode: true,
1250+
read() {
1251+
if (readCount < 1) {
1252+
this.push(files[readCount]);
1253+
readCount++;
1254+
} else {
1255+
this.push(null);
1256+
}
1257+
},
1258+
});
1259+
1260+
bucket.getFilesStream = (query: {}) => {
12311261
assert.deepStrictEqual(query, {});
1232-
return Promise.all([[]]);
1262+
return readable;
12331263
};
12341264

12351265
bucket.deleteFiles(done);
@@ -1238,9 +1268,28 @@ describe('Bucket', () => {
12381268
it('should get files from the bucket', done => {
12391269
const query = {a: 'b', c: 'd'};
12401270

1241-
bucket.getFiles = (query_: {}) => {
1271+
const files = [bucket.file('1'), bucket.file('2')].map(file => {
1272+
file.delete = () => {
1273+
return Promise.resolve();
1274+
};
1275+
return file;
1276+
});
1277+
1278+
const readable = new stream.Readable({
1279+
objectMode: true,
1280+
read() {
1281+
if (readCount < 1) {
1282+
this.push(files[readCount]);
1283+
readCount++;
1284+
} else {
1285+
this.push(null);
1286+
}
1287+
},
1288+
});
1289+
1290+
bucket.getFilesStream = (query_: {}) => {
12421291
assert.deepStrictEqual(query_, query);
1243-
return Promise.resolve([[]]);
1292+
return readable;
12441293
};
12451294

12461295
bucket.deleteFiles(query, done);
@@ -1253,7 +1302,26 @@ describe('Bucket', () => {
12531302
return () => {};
12541303
};
12551304

1256-
bucket.getFiles = () => Promise.resolve([[]]);
1305+
const files = [bucket.file('1'), bucket.file('2')].map(file => {
1306+
file.delete = () => {
1307+
return Promise.resolve();
1308+
};
1309+
return file;
1310+
});
1311+
1312+
const readable = new stream.Readable({
1313+
objectMode: true,
1314+
read() {
1315+
if (readCount < 1) {
1316+
this.push(files[readCount]);
1317+
readCount++;
1318+
} else {
1319+
this.push(null);
1320+
}
1321+
},
1322+
});
1323+
1324+
bucket.getFilesStream = () => readable;
12571325
bucket.deleteFiles({}, assert.ifError);
12581326
});
12591327

@@ -1270,9 +1338,21 @@ describe('Bucket', () => {
12701338
return file;
12711339
});
12721340

1273-
bucket.getFiles = (query_: {}) => {
1341+
const readable = new stream.Readable({
1342+
objectMode: true,
1343+
read() {
1344+
if (readCount < files.length) {
1345+
this.push(files[readCount]);
1346+
readCount++;
1347+
} else {
1348+
this.push(null);
1349+
}
1350+
},
1351+
});
1352+
1353+
bucket.getFilesStream = (query_: {}) => {
12741354
assert.strictEqual(query_, query);
1275-
return Promise.resolve([files]);
1355+
return readable;
12761356
};
12771357

12781358
bucket.deleteFiles(query, (err: Error) => {
@@ -1284,9 +1364,15 @@ describe('Bucket', () => {
12841364

12851365
it('should execute callback with error from getting files', done => {
12861366
const error = new Error('Error.');
1367+
const readable = new stream.Readable({
1368+
objectMode: true,
1369+
read() {
1370+
this.destroy(error);
1371+
},
1372+
});
12871373

1288-
bucket.getFiles = () => {
1289-
return Promise.reject(error);
1374+
bucket.getFilesStream = () => {
1375+
return readable;
12901376
};
12911377

12921378
bucket.deleteFiles({}, (err: Error) => {
@@ -1303,8 +1389,20 @@ describe('Bucket', () => {
13031389
return file;
13041390
});
13051391

1306-
bucket.getFiles = () => {
1307-
return Promise.resolve([files]);
1392+
const readable = new stream.Readable({
1393+
objectMode: true,
1394+
read() {
1395+
if (readCount < files.length) {
1396+
this.push(files[readCount]);
1397+
readCount++;
1398+
} else {
1399+
this.push(null);
1400+
}
1401+
},
1402+
});
1403+
1404+
bucket.getFilesStream = () => {
1405+
return readable;
13081406
};
13091407

13101408
bucket.deleteFiles({}, (err: Error) => {
@@ -1321,8 +1419,20 @@ describe('Bucket', () => {
13211419
return file;
13221420
});
13231421

1324-
bucket.getFiles = () => {
1325-
return Promise.resolve([files]);
1422+
const readable = new stream.Readable({
1423+
objectMode: true,
1424+
read() {
1425+
if (readCount < files.length) {
1426+
this.push(files[readCount]);
1427+
readCount++;
1428+
} else {
1429+
this.push(null);
1430+
}
1431+
},
1432+
});
1433+
1434+
bucket.getFilesStream = () => {
1435+
return readable;
13261436
};
13271437

13281438
bucket.deleteFiles({force: true}, (errs: Array<{}>) => {

0 commit comments

Comments
 (0)