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 forceDelete - Whether to force delete the room even if it has participants.
* @returns A promise that resolves to an object containing the deleted and marked rooms.
* @param roomIds - Array of room identifiers to be deleted
* @param forceDelete - If true, deletes rooms even if they have active participants.
* If false, rooms with participants will be marked for deletion instead of being deleted immediately.
*/
async bulkDeleteRooms(
roomIds: string[],
@ -187,10 +189,7 @@ export class RoomService {
}
this.logger.verbose(`Room ${roomId} has participants. Marking as deleted (graceful deletion).`);
// Mark room as deleted
const room = await this.storageService.getMeetRoom(roomId);
room.markedForDeletion = true;
await this.storageService.saveMeetRoom(room);
await this.markRoomAsDeleted(roomId);
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.
*
@ -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.
*
* @returns {Promise<void>} A promise that resolves when the deletion process is complete.
**/
* 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.
*/
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 {
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(
ovExpiredRooms.map((roomId) => this.livekitService.deleteRoom(roomId))
);
const expiredRoomIds = rooms
.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) => {
if (result.status === 'fulfilled') {
successfulRooms.push(ovExpiredRooms[index]);
} else {
this.logger.error(`Failed to delete OpenVidu room "${ovExpiredRooms[index]}": ${result.reason}`);
const { deleted, markedAsDeleted } = await this.bulkDeleteRooms(expiredRoomIds, false);
deletedRooms.push(...deleted);
markedAsDeletedRooms.push(...markedAsDeleted);
}
});
} while (nextPageToken);
this.logger.verbose(
`Successfully deleted ${successfulRooms.length} expired rooms: ${successfulRooms.join(', ')}`
);
this.logger.verbose(`Successfully deleted ${deletedRooms.length} expired rooms}`);
this.logger.verbose(`Marked as deleted ${markedAsDeletedRooms.length} expired rooms}`);
} catch (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;
}
}