backend: Refactored room GC using bulk delete rooom method for gracefully delete the expired rooms

This commit is contained in:
Carlos Santos 2025-04-09 12:22:27 +02:00
parent 7ca385968d
commit 913aa44278

View File

@ -159,11 +159,13 @@ export class RoomService {
} }
/** /**
* Deletes a room by its ID. * Deletes multiple rooms in bulk, with the option to force delete or gracefully handle rooms with active participants.
* For rooms with participants, when `forceDelete` is false, the method performs a "graceful deletion"
* by marking the room as deleted without disrupting active sessions.
* *
* @param roomId - The unique identifier of the room to delete. * @param roomIds - Array of room identifiers to be deleted
* @param forceDelete - Whether to force delete the room even if it has participants. * @param forceDelete - If true, deletes rooms even if they have active participants.
* @returns A promise that resolves to an object containing the deleted and marked rooms. * If false, rooms with participants will be marked for deletion instead of being deleted immediately.
*/ */
async bulkDeleteRooms( async bulkDeleteRooms(
roomIds: string[], roomIds: string[],
@ -187,10 +189,7 @@ export class RoomService {
} }
this.logger.verbose(`Room ${roomId} has participants. Marking as deleted (graceful deletion).`); this.logger.verbose(`Room ${roomId} has participants. Marking as deleted (graceful deletion).`);
// Mark room as deleted await this.markRoomAsDeleted(roomId);
const room = await this.storageService.getMeetRoom(roomId);
room.markedForDeletion = true;
await this.storageService.saveMeetRoom(room);
return { roomId, status: 'marked' } as const; return { roomId, status: 'marked' } as const;
}) })
); );
@ -222,6 +221,19 @@ export class RoomService {
} }
} }
/**
* Marks a room as deleted in the storage system.
*
* @param roomId - The unique identifier of the room to mark for deletion
* @returns A promise that resolves when the room has been successfully marked as deleted
* @throws May throw an error if the room cannot be found or if saving fails
*/
protected async markRoomAsDeleted(roomId: string): Promise<void> {
const room = await this.storageService.getMeetRoom(roomId);
room.markedForDeletion = true;
await this.storageService.saveMeetRoom(room);
}
/** /**
* Validates a secret against a room's moderator and publisher secrets and returns the corresponding role. * Validates a secret against a room's moderator and publisher secrets and returns the corresponding role.
* *
@ -290,69 +302,44 @@ export class RoomService {
} }
/** /**
* Deletes OpenVidu expired rooms and consequently LiveKit rooms. * Gracefully deletes expired rooms.
* *
* This method delete the rooms that have an expiration date earlier than the current time. * This method checks for rooms that have an auto-deletion date in the past and deletes them.
* * It also marks rooms as deleted if they have participants.
* @returns {Promise<void>} A promise that resolves when the deletion process is complete. */
**/
protected async deleteExpiredRooms(): Promise<void> { protected async deleteExpiredRooms(): Promise<void> {
let nextPageToken: string | undefined;
const deletedRooms: string[] = [];
const markedAsDeletedRooms: string[] = [];
this.logger.verbose(`Checking expired rooms at ${new Date(Date.now()).toISOString()}`);
try { try {
const ovExpiredRooms = await this.deleteExpiredMeetRooms(); do {
const now = Date.now();
if (ovExpiredRooms.length === 0) return; const { rooms, nextPageToken: token } = await this.getAllMeetRooms({ maxItems: 100, nextPageToken });
nextPageToken = token;
const livekitResults = await Promise.allSettled( const expiredRoomIds = rooms
ovExpiredRooms.map((roomId) => this.livekitService.deleteRoom(roomId)) .filter((room) => room.autoDeletionDate && room.autoDeletionDate < now)
); .map((room) => room.roomId);
const successfulRooms: string[] = []; if (expiredRoomIds.length > 0) {
this.logger.verbose(
`Trying to delete ${expiredRoomIds.length} expired Meet rooms: ${expiredRoomIds.join(', ')}`
);
livekitResults.forEach((result, index) => { const { deleted, markedAsDeleted } = await this.bulkDeleteRooms(expiredRoomIds, false);
if (result.status === 'fulfilled') {
successfulRooms.push(ovExpiredRooms[index]); deletedRooms.push(...deleted);
} else { markedAsDeletedRooms.push(...markedAsDeleted);
this.logger.error(`Failed to delete OpenVidu room "${ovExpiredRooms[index]}": ${result.reason}`);
} }
}); } while (nextPageToken);
this.logger.verbose( this.logger.verbose(`Successfully deleted ${deletedRooms.length} expired rooms}`);
`Successfully deleted ${successfulRooms.length} expired rooms: ${successfulRooms.join(', ')}` this.logger.verbose(`Marked as deleted ${markedAsDeletedRooms.length} expired rooms}`);
);
} catch (error) { } catch (error) {
this.logger.error('Error deleting expired rooms:', error); this.logger.error('Error deleting expired rooms:', error);
} }
} }
/**
* Deletes expired Meet rooms by iterating through all paged results.
*
* @returns A promise that resolves with an array of room IDs that were successfully deleted.
*/
protected async deleteExpiredMeetRooms(): Promise<string[]> {
const now = Date.now();
this.logger.verbose(`Checking Meet expired rooms at ${new Date(now).toISOString()}`);
let nextPageToken: string | undefined;
const deletedRooms: string[] = [];
do {
const { rooms, nextPageToken: token } = await this.getAllMeetRooms({ maxItems: 100, nextPageToken });
nextPageToken = token;
const expiredRoomIds = rooms
.filter((room) => room.autoDeletionDate && room.autoDeletionDate < now)
.map((room) => room.roomId);
if (expiredRoomIds.length > 0) {
this.logger.verbose(
`Deleting ${expiredRoomIds.length} expired Meet rooms: ${expiredRoomIds.join(', ')}`
);
// const deletedOnPage = await this.deleteMeetRooms(expiredRooms);
await this.storageService.deleteMeetRooms(expiredRoomIds);
deletedRooms.push(...expiredRoomIds);
}
} while (nextPageToken);
return deletedRooms;
}
} }