Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/mongodb-log-writer/src/mongo-log-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,32 @@ describe('MongoLogManager', function () {
expect(await getFilesState(paths)).to.equal('0000111111');
});

it('skips checking file storage if retentionGB is set to Infinity', async function () {
const statStub = sinon.stub(fs, 'stat');

const manager = new MongoLogManager({
directory,
retentionDays,
maxLogFileCount: 1000,
retentionGB: Infinity,
onwarn,
onerror,
});

const offset = Math.floor(Date.now() / 1000);
for (let i = 0; i < 10; i++) {
const filename = path.join(
directory,
ObjectId.createFromTime(offset - i).toHexString() + '_log'
);
await fs.writeFile(filename, '');
}

await manager.cleanupOldLogFiles();

expect(statStub).not.called;
});

describe('with a random file order', function () {
let paths: string[] = [];
const times = [92, 90, 1, 2, 3, 91];
Expand Down
37 changes: 25 additions & 12 deletions packages/mongodb-log-writer/src/mongo-log-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ export class MongoLogManager {
}

/** Clean up log files older than `retentionDays`. */
async cleanupOldLogFiles(maxDurationMs = 5_000, remainingRetries = 1): Promise<void> {
async cleanupOldLogFiles(
maxDurationMs = 5_000,
remainingRetries = 1
): Promise<void> {
const deletionStartTimestamp = Date.now();
// Delete files older than N days
const deletionCutoffTimestamp =
Expand All @@ -84,7 +87,13 @@ export class MongoLogManager {
fileSize: number | undefined;
}>((a, b) => a.fileTimestamp - b.fileTimestamp);

let usedStorageSize = this._options.retentionGB ? 0 : -Infinity;
const hasRetentionGB =
!!this._options.retentionGB && isFinite(this._options.retentionGB);
const hasMaxLogFileCount =
!!this._options.maxLogFileCount &&
isFinite(this._options.maxLogFileCount);

let usedStorageSize = hasRetentionGB ? 0 : -Infinity;

try {
for await (const dirent of dirHandle) {
Expand All @@ -93,28 +102,28 @@ export class MongoLogManager {
// where lots and lots of log files end up and filesystem operations happen
// with network latency.
if (Date.now() - deletionStartTimestamp > maxDurationMs) break;

if (!dirent.isFile()) continue;
const logRegExp = new RegExp(
`^${this.prefix}(?<id>[a-f0-9]{24})_log(\\.gz)?$`,
'i'
);
const { id } = logRegExp.exec(dirent.name)?.groups ?? {};
if (!id) continue;

const fileTimestamp = +new ObjectId(id).getTimestamp();
const fullPath = path.join(dir, dirent.name);

// If the file is older than expected, delete it. If the file is recent,
// add it to the list of seen files, and if that list is too large, remove
// the least recent file we've seen so far.
if (fileTimestamp < deletionCutoffTimestamp) {
await this.deleteFile(fullPath);
continue;
}

let fileSize: number | undefined;
if (this._options.retentionGB) {
if (hasRetentionGB) {
try {
fileSize = (await fs.stat(fullPath)).size;
usedStorageSize += fileSize;
Expand All @@ -123,12 +132,13 @@ export class MongoLogManager {
continue;
}
}
if (this._options.maxLogFileCount || this._options.retentionGB) {

if (hasMaxLogFileCount || hasRetentionGB) {
leastRecentFileHeap.push({ fullPath, fileTimestamp, fileSize });
}

if (
hasMaxLogFileCount &&
this._options.maxLogFileCount &&
leastRecentFileHeap.size() > this._options.maxLogFileCount
) {
Expand All @@ -145,11 +155,14 @@ export class MongoLogManager {
// To handle such scenarios, we will catch lstat errors and retry cleaning up
// to let different processes reach out to different log files.
if (statErr.code === 'ENOENT' && remainingRetries > 0) {
await this.cleanupOldLogFiles(maxDurationMs - (Date.now() - deletionStartTimestamp), remainingRetries - 1);
await this.cleanupOldLogFiles(
maxDurationMs - (Date.now() - deletionStartTimestamp),
remainingRetries - 1
);
}
}

if (this._options.retentionGB) {
if (hasRetentionGB && this._options.retentionGB) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is redundant but TypeScript isn't smart enough to do type promotion otherwise

const storageSizeLimit = this._options.retentionGB * 1024 * 1024 * 1024;

for (const file of leastRecentFileHeap) {
Expand Down