backend: implement bulk deletion of recording metadata files in StorageProvider and S3StorageProvider

This commit is contained in:
Carlos Santos 2025-05-29 13:29:42 +02:00
parent 616222fb73
commit 2c03ecdd9a
4 changed files with 73 additions and 20 deletions

View File

@ -181,10 +181,25 @@ export class RecordingService {
async deleteRecording(recordingId: string): Promise<MeetRecordingInfo> {
try {
// Get the recording metada and recording info from the S3 bucket
const { filesToDelete, recordingInfo } = await this.getDeletableRecordingFiles(recordingId);
const { binaryFilesToDelete, metadataFilesToDelete, recordingInfo } =
await this.getDeletableRecordingFiles(recordingId);
const { roomId } = RecordingHelper.extractInfoFromRecordingId(recordingId);
const deleteRecordingTasks: Promise<unknown>[] = [];
if (binaryFilesToDelete.size > 0) {
// Delete video files from S3
deleteRecordingTasks.push(this.s3Service.deleteObjects(Array.from(binaryFilesToDelete)));
}
if (metadataFilesToDelete.size > 0) {
// Delete metadata files from storage provider
deleteRecordingTasks.push(
this.storageService.deleteRecordingMetadataByPaths(Array.from(metadataFilesToDelete))
);
}
await Promise.all(deleteRecordingTasks);
await this.s3Service.deleteObjects(Array.from(filesToDelete));
this.logger.info(`Successfully deleted ${recordingId}`);
const shouldDeleteRoomMetadata = await this.shouldDeleteRoomMetadata(roomId);
@ -211,7 +226,8 @@ export class RecordingService {
async bulkDeleteRecordingsAndAssociatedFiles(
recordingIds: string[]
): Promise<{ deleted: string[]; notDeleted: { recordingId: string; error: string }[] }> {
const allFilesToDelete: Set<string> = new Set<string>();
let allMetadataFilesToDelete: Set<string> = new Set<string>();
let allBinaryFilesToDelete: Set<string> = new Set<string>();
const deletedRecordings: Set<string> = new Set<string>();
const notDeletedRecordings: Set<{ recordingId: string; error: string }> = new Set();
const roomsToCheck: Set<string> = new Set();
@ -219,8 +235,12 @@ export class RecordingService {
// Check if the recording is in progress
for (const recordingId of recordingIds) {
try {
const { filesToDelete } = await this.getDeletableRecordingFiles(recordingId);
filesToDelete.forEach((file) => allFilesToDelete.add(file));
const { binaryFilesToDelete, metadataFilesToDelete } =
await this.getDeletableRecordingFiles(recordingId);
// Add files to the set of files to delete
allBinaryFilesToDelete = new Set([...allBinaryFilesToDelete, ...binaryFilesToDelete]);
allMetadataFilesToDelete = new Set([...allMetadataFilesToDelete, ...metadataFilesToDelete]);
deletedRecordings.add(recordingId);
// Track the roomId for checking if the room metadata file should be deleted
@ -232,15 +252,18 @@ export class RecordingService {
}
}
if (allFilesToDelete.size === 0) {
if (allBinaryFilesToDelete.size === 0) {
this.logger.warn(`BulkDelete: No eligible recordings found for deletion.`);
return { deleted: Array.from(deletedRecordings), notDeleted: Array.from(notDeletedRecordings) };
}
// Delete recordings and its metadata from S3
try {
await this.s3Service.deleteObjects(Array.from(allFilesToDelete));
this.logger.info(`BulkDelete: Successfully deleted ${allFilesToDelete.size} objects from S3.`);
await Promise.all([
this.s3Service.deleteObjects(Array.from(allBinaryFilesToDelete)),
this.storageService.deleteRecordingMetadataByPaths(Array.from(allMetadataFilesToDelete))
]);
this.logger.info(`BulkDelete: Successfully deleted ${allBinaryFilesToDelete.size} recordings.`);
} catch (error) {
this.logger.error(`BulkDelete: Error performing bulk deletion: ${error}`);
throw error;
@ -262,7 +285,7 @@ export class RecordingService {
try {
this.logger.verbose(`Deleting room_metadata.json for rooms: ${roomMetadataToDelete.join(', ')}`);
await Promise.all(deleteTasks);
this.logger.verbose(`BulkDelete: Successfully deleted ${allFilesToDelete.size} room metadata files.`);
this.logger.verbose(`BulkDelete: Successfully deleted ${roomMetadataToDelete.length} room metadata files.`);
} catch (error) {
this.logger.error(`BulkDelete: Error performing bulk deletion: ${error}`);
throw error;
@ -501,11 +524,14 @@ export class RecordingService {
*
* @param recordingId - The unique identifier of the recording egress.
*/
protected async getDeletableRecordingFiles(
recordingId: string
): Promise<{ filesToDelete: Set<string>; recordingInfo: MeetRecordingInfo }> {
protected async getDeletableRecordingFiles(recordingId: string): Promise<{
binaryFilesToDelete: Set<string>;
metadataFilesToDelete: Set<string>;
recordingInfo: MeetRecordingInfo;
}> {
const { metadataFilePath, recordingInfo } = await this.storageService.getRecordingMetadata(recordingId);
const filesToDelete: Set<string> = new Set();
const binaryFilesToDelete: Set<string> = new Set();
const metadataFilesToDelete: Set<string> = new Set();
// Validate the recording status
if (!RecordingHelper.canBeDeleted(recordingInfo)) throw errorRecordingNotStopped(recordingId);
@ -517,9 +543,10 @@ export class RecordingService {
}
const recordingPath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/${filename}`;
filesToDelete.add(recordingPath).add(metadataFilePath);
binaryFilesToDelete.add(recordingPath);
metadataFilesToDelete.add(metadataFilePath);
return { filesToDelete, recordingInfo };
return { binaryFilesToDelete, metadataFilesToDelete, recordingInfo };
}
// protected async getMeetRecordingInfoFromMetadata(

View File

@ -394,7 +394,22 @@ export class S3StorageProvider<
}
}
async deleteRecordingMetadata(recordingId: string): Promise<void> {}
/**
* Deletes multiple recording metadata files from S3 storage based on their file paths.
*
* @param metadataPaths - Array of file paths pointing to the metadata files to be deleted
* @returns A promise that resolves when all metadata files have been successfully deleted
* @throws May throw an error if any of the deletion operations fail
*/
async deleteRecordingMetadataByPaths(metadataPaths: string[]): Promise<void> {
try {
await this.s3Service.deleteObjects(metadataPaths);
this.logger.verbose(`Deleted multiple recording metadata files: ${metadataPaths.join(', ')}`);
} catch (error) {
this.handleError(error, `Error deleting multiple recording metadata files: ${metadataPaths.join(', ')}`);
throw error;
}
}
/**
* Retrieves an object of type U from Redis by the given key.

View File

@ -135,10 +135,10 @@ export interface StorageProvider<
getRecordingMetadata(recordingId: string): Promise<{ recordingInfo: MRec; metadataFilePath: string }>;
/**
* Deletes the recording metadata for a specific recording ID.
* Deletes multiple recording metadata files by their paths.
*
* @param recordingId - The unique identifier of the recording to delete.
* @returns A promise that resolves when the deletion is complete.
* @param metadataPaths - An array of metadata file paths to delete.
*/
deleteRecordingMetadata(recordingId: string): Promise<void>;
deleteRecordingMetadataByPaths(metadataPaths: string[]): Promise<void>;
}

View File

@ -210,6 +210,17 @@ export class MeetStorageService<
}>;
}
/**
* Deletes multiple recording metadata files by their paths.
*
* @param metadataPaths - Array of file paths to the recording metadata files to be deleted
* @returns A Promise that resolves when all metadata files have been successfully deleted
* @throws May throw an error if any of the deletion operations fail
*/
async deleteRecordingMetadataByPaths(metadataPaths: string[]): Promise<void> {
return this.storageProvider.deleteRecordingMetadataByPaths(metadataPaths);
}
/**
* Returns the default global preferences.
* @returns {GPrefs}