backend: Refactor recording deletion logic to use Sets for improved performance

This commit is contained in:
Carlos Santos 2025-04-21 14:04:33 +02:00
parent 1ebc45a54f
commit d596e78a46

View File

@ -164,10 +164,11 @@ export class RecordingService {
// Get the recording metada and recording info from the S3 bucket // Get the recording metada and recording info from the S3 bucket
const { filesToDelete, recordingInfo } = await this.getDeletableRecordingData(recordingId); const { filesToDelete, recordingInfo } = await this.getDeletableRecordingData(recordingId);
const filesToDeleteArray = Array.from(filesToDelete);
this.logger.verbose( this.logger.verbose(
`Deleting recording from S3. Files: ${filesToDelete.join(', ')} for recordingId ${recordingId}` `Deleting recording from S3. Files: ${filesToDeleteArray.join(', ')} for recordingId ${recordingId}`
); );
await this.s3Service.deleteObjects(filesToDelete); await this.s3Service.deleteObjects(filesToDeleteArray);
this.logger.info(`Deletion successful for recording ${recordingId}`); this.logger.info(`Deletion successful for recording ${recordingId}`);
return recordingInfo; return recordingInfo;
@ -187,26 +188,26 @@ export class RecordingService {
async bulkDeleteRecordings( async bulkDeleteRecordings(
recordingIds: string[] recordingIds: string[]
): Promise<{ deleted: string[]; notDeleted: { recordingId: string; error: string }[] }> { ): Promise<{ deleted: string[]; notDeleted: { recordingId: string; error: string }[] }> {
const keysToDelete: string[] = []; let keysToDelete: Set<string> = new Set<string>();
const deletedRecordings: string[] = []; const deletedRecordings: Set<string> = new Set<string>();
const notDeletedRecordings: { recordingId: string; error: string }[] = []; const notDeletedRecordings: Set<{ recordingId: string; error: string }> = new Set();
for (const recordingId of recordingIds) { for (const recordingId of recordingIds) {
try { try {
const { filesToDelete } = await this.getDeletableRecordingData(recordingId, keysToDelete); const { filesToDelete } = await this.getDeletableRecordingData(recordingId, keysToDelete);
keysToDelete.push(...filesToDelete); keysToDelete = new Set([...keysToDelete, ...filesToDelete]);
deletedRecordings.push(recordingId); deletedRecordings.add(recordingId);
this.logger.verbose(`BulkDelete: Prepared recording ${recordingId} for deletion.`); this.logger.verbose(`BulkDelete: Prepared recording ${recordingId} for deletion.`);
} catch (error) { } catch (error) {
this.logger.error(`BulkDelete: Error processing recording ${recordingId}: ${error}`); this.logger.error(`BulkDelete: Error processing recording ${recordingId}: ${error}`);
notDeletedRecordings.push({ recordingId, error: (error as OpenViduMeetError).message }); notDeletedRecordings.add({ recordingId, error: (error as OpenViduMeetError).message });
} }
} }
if (keysToDelete.length > 0) { if (keysToDelete.size > 0) {
try { try {
await this.s3Service.deleteObjects(keysToDelete); await this.s3Service.deleteObjects(Array.from(keysToDelete));
this.logger.info(`BulkDelete: Successfully deleted ${keysToDelete.length} objects from S3.`); this.logger.info(`BulkDelete: Successfully deleted ${keysToDelete.size} objects from S3.`);
} catch (error) { } catch (error) {
this.logger.error(`BulkDelete: Error performing bulk deletion: ${error}`); this.logger.error(`BulkDelete: Error performing bulk deletion: ${error}`);
throw error; throw error;
@ -215,7 +216,7 @@ export class RecordingService {
this.logger.warn(`BulkDelete: No eligible recordings found for deletion.`); this.logger.warn(`BulkDelete: No eligible recordings found for deletion.`);
} }
return { deleted: deletedRecordings, notDeleted: notDeletedRecordings }; return { deleted: Array.from(deletedRecordings), notDeleted: Array.from(notDeletedRecordings) };
} }
/** /**
@ -382,11 +383,13 @@ export class RecordingService {
*/ */
protected async getDeletableRecordingData( protected async getDeletableRecordingData(
recordingId: string, recordingId: string,
filesToDeleteArray: string[] = [] filesAlreadyAddedForDeletion: Set<string> = new Set<string>()
): Promise<{ filesToDelete: string[]; recordingInfo: MeetRecordingInfo }> { ): Promise<{ filesToDelete: Set<string>; recordingInfo: MeetRecordingInfo }> {
const { metadataFilePath, recordingInfo } = await this.getMeetRecordingInfoFromMetadata(recordingId); const { metadataFilePath, recordingInfo } = await this.getMeetRecordingInfoFromMetadata(recordingId);
const filesToDelete: string[] = [metadataFilePath]; const newFilesToDelete: Set<string> = new Set();
newFilesToDelete.add(metadataFilePath);
// Validate the recording status
if ( if (
recordingInfo.status === MeetRecordingStatus.STARTING || recordingInfo.status === MeetRecordingStatus.STARTING ||
recordingInfo.status === MeetRecordingStatus.ACTIVE || recordingInfo.status === MeetRecordingStatus.ACTIVE ||
@ -395,27 +398,27 @@ export class RecordingService {
throw errorRecordingNotStopped(recordingId); throw errorRecordingNotStopped(recordingId);
} }
const recordingPath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/${RecordingHelper.extractFilename(recordingInfo)}`; const filename = RecordingHelper.extractFilename(recordingInfo);
if (!recordingPath) { if (!filename) {
throw internalError(`Error extracting path from recording ${recordingId}`); throw internalError(`Error extracting path from recording ${recordingId}`);
} }
filesToDelete.push(recordingPath); const recordingPath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/${RecordingHelper.extractFilename(recordingInfo)}`;
newFilesToDelete.add(recordingPath);
filesToDeleteArray.push(...filesToDelete);
// Get secrets.json file path if it is the only file remaining in the room's metadata directory
const secretsFilePath = await this.getSecretsFilePathIfOnlyRemaining( const secretsFilePath = await this.getSecretsFilePathIfOnlyRemaining(
recordingInfo.roomId, recordingInfo.roomId,
metadataFilePath, metadataFilePath,
filesToDeleteArray Array.from(new Set([...filesAlreadyAddedForDeletion, ...newFilesToDelete]))
); );
if (secretsFilePath) { if (secretsFilePath) {
filesToDelete.push(secretsFilePath); newFilesToDelete.add(secretsFilePath);
} }
return { filesToDelete, recordingInfo }; return { filesToDelete: newFilesToDelete, recordingInfo };
} }
protected async getMeetRecordingInfoFromMetadata( protected async getMeetRecordingInfoFromMetadata(