Skip to content

Commit

Permalink
Merge pull request #299 from boostcampwm-2024/dev-be
Browse files Browse the repository at this point in the history
[BE] dev-be -> release
  • Loading branch information
NewCodes7 authored Dec 2, 2024
2 parents 73462ff + 51c0ce2 commit 01546ea
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 56 deletions.
55 changes: 53 additions & 2 deletions BE/src/game/redis/game-redis-memory.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis';
import { Cron, CronExpression } from '@nestjs/schedule';
import { REDIS_KEY } from '../../common/constants/redis-key.constant';
import { Cron, CronExpression } from '@nestjs/schedule';

@Injectable()
export class GameRedisMemoryService {
private readonly logger = new Logger(GameRedisMemoryService.name);
private readonly BATCH_SIZE = 100; // ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ๋ฐฐ์น˜ ํฌ๊ธฐ
private readonly INACTIVE_THRESHOLD = 30 * 60 * 1000; // 30๋ถ„ 30 * 60 * 1000;

private readonly TTL = {
ROOM: 3 * 60 * 60,
Expand All @@ -18,11 +19,61 @@ export class GameRedisMemoryService {

constructor(@InjectRedis() private readonly redis: Redis) {}

/**
* ๋น„ํ™œ์„ฑ ๋ฐฉ ์ฒดํฌ (์ฃผ๊ธฐ์ ์œผ๋กœ ์‹คํ–‰)
*/
/**
* ๋น„ํ™œ์„ฑ ๋ฐฉ์„ ์ฒดํฌํ•˜๊ณ  ์ •๋ฆฌํ•˜๋Š” ํฌ๋ก  ์ž‘์—…
* SCAN์„ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ๋ฐฉ ๋ชฉ๋ก๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
*/
@Cron(CronExpression.EVERY_10_MINUTES)
async checkInactiveRooms(): Promise<void> {
this.logger.verbose('๋น„ํ™œ์„ฑ ๋ฐฉ ์ฒดํฌ ์‹œ์ž‘');
try {
const now = Date.now();
let cursor = '0';
let processedCount = 0;

do {
// SCAN์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฐ์น˜ ๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌ
const [nextCursor, rooms] = await this.redis.sscan(
REDIS_KEY.ACTIVE_ROOMS,
cursor,
'COUNT',
this.BATCH_SIZE
);
cursor = nextCursor;

// ๋ณ‘๋ ฌ๋กœ ๋ฐฉ ์ƒํƒœ ์ฒดํฌ ๋ฐ ์ฒ˜๋ฆฌ
await Promise.all(
rooms.map(async (roomId) => {
try {
const lastActivity = await this.redis.hget(REDIS_KEY.ROOM(roomId), 'lastActivityAt');

if (lastActivity && now - parseInt(lastActivity) > this.INACTIVE_THRESHOLD) {
await this.redis.publish('room:cleanup', roomId);
this.logger.verbose(`๋น„ํ™œ์„ฑ์œผ๋กœ ์ธํ•ด ๋ฐฉ ${roomId} ์ •๋ฆฌ ์‹œ์ž‘`);
processedCount++;
}
} catch (error) {
this.logger.error(`๋ฐฉ ${roomId} ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`);
}
})
);
} while (cursor !== '0');

this.logger.verbose(`๋น„ํ™œ์„ฑ ๋ฐฉ ์ฒดํฌ ์™„๋ฃŒ: ${processedCount}๊ฐœ ๋ฐฉ ์ •๋ฆฌ๋จ`);
} catch (error) {
this.logger.error(`๋น„ํ™œ์„ฑ ๋ฐฉ ์ฒดํฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`);
}
}

/**
* TTL ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์Šค์ผ€์ค„๋Ÿฌ
* ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋กœ ๋ธ”๋กœํ‚น ์ตœ์†Œํ™”
*/
@Cron(CronExpression.EVERY_MINUTE)

// @Cron(CronExpression.EVERY_MINUTE)
async manageTTL(): Promise<void> {
try {
// SCAN์œผ๋กœ ํ™œ์„ฑ ๋ฐฉ ๋ชฉ๋ก์„ ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌ
Expand Down
9 changes: 9 additions & 0 deletions BE/src/game/redis/subscribers/room.cleanup.subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ export class RoomCleanupSubscriber extends RedisSubscriber {
try {
const pipeline = this.redis.pipeline();

// 1. ๋ฐฉ์— ์†ํ•œ ํ”Œ๋ ˆ์ด์–ด ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ, 200๋ช…๋ฏธ๋งŒ -> smembers ์‚ฌ์šฉ!
const players = await this.redis.smembers(REDIS_KEY.ROOM_PLAYERS(roomId));

// 2. ํ”Œ๋ ˆ์ด์–ด ๋ฐ์ดํ„ฐ ์‚ญ์ œ
for (const playerId of players) {
pipeline.del(REDIS_KEY.PLAYER(playerId)); // ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ
pipeline.del(`${REDIS_KEY.PLAYER(playerId)}:Changes`); // ํ”Œ๋ ˆ์ด์–ด Changes ๋ฐ์ดํ„ฐ
}

// 1. ๋ฐฉ ๊ด€๋ จ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ์‚ญ์ œ
pipeline.del(REDIS_KEY.ROOM(roomId));
pipeline.del(REDIS_KEY.ROOM_PLAYERS(roomId));
Expand Down
17 changes: 4 additions & 13 deletions BE/src/game/redis/subscribers/timer.subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class TimerSubscriber extends RedisSubscriber {
const selectAnswer = this.calculateAnswer(
player.positionX,
player.positionY,
quizList.length
parseInt(quiz.choiceCount)
);
// this.logger.verbose(selectAnswer);

Expand Down Expand Up @@ -167,8 +167,8 @@ export class TimerSubscriber extends RedisSubscriber {
}

private calculateAnswer(positionX: string, positionY: string, quizLen: number): number {
const x = parseFloat(positionX);
const y = parseFloat(positionY);
const x = parseFloat(positionY);
const y = parseFloat(positionX);

// ํ–‰์˜ ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ (2์—ด ๊ณ ์ •์ด๋ฏ€๋กœ ์ด ๊ฐœ์ˆ˜์˜ ์ ˆ๋ฐ˜์„ ์˜ฌ๋ฆผ)
const rows = Math.ceil(quizLen / 2);
Expand All @@ -177,21 +177,12 @@ export class TimerSubscriber extends RedisSubscriber {
const rowIndex = Math.floor(y * rows);

// X ์ขŒํ‘œ๋กœ ์™ผ์ชฝ/์˜ค๋ฅธ์ชฝ ๊ฒฐ์ •
const colIndex = x < 0.5 ? 0 : 1;
const colIndex = Math.round(x);

// ์ตœ์ข… ์„ ํƒ์ง€ ๋ฒˆํ˜ธ ๊ณ„์‚ฐ
const answer = rowIndex * 2 + colIndex + 1;

// ์‹ค์ œ ์„ ํƒ์ง€ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜์ง€ ์•Š๋„๋ก ๋ณด์ •
return Math.min(answer, quizLen);
// return (
// Math.round(
// parseFloat(positionX) + Math.floor(parseFloat(positionY) * Math.ceil(quizLen / 2))
// ) * 2
// );
// if (parseFloat(positionY) < 0.5) {
// return parseFloat(positionX) < 0.5 ? 1 : 2;
// }
// return parseFloat(positionX) < 0.5 ? 3 : 4;
}
}
61 changes: 20 additions & 41 deletions BE/src/game/service/game.room.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { generateUniquePin } from '../../common/utils/utils';
import SocketEvents from '../../common/constants/socket-events';
import { UpdateRoomOptionDto } from '../dto/update-room-option.dto';
import { UpdateRoomQuizsetDto } from '../dto/update-room-quizset.dto';
import { Cron, CronExpression } from '@nestjs/schedule';
import { Socket } from 'socket.io';
import { KickRoomDto } from '../dto/kick-room.dto';
import { TraceClass } from '../../common/interceptor/SocketEventLoggerInterceptor';
Expand All @@ -18,7 +17,6 @@ import { TraceClass } from '../../common/interceptor/SocketEventLoggerIntercepto
export class GameRoomService {
private readonly logger = new Logger(GameRoomService.name);
private readonly INACTIVE_THRESHOLD = 30 * 60 * 1000; // 30๋ถ„ 30 * 60 * 1000;
private readonly PLAYER_GRACE_PERIOD = 10; // 10์ดˆ

constructor(
@InjectRedis() private readonly redis: Redis,
Expand Down Expand Up @@ -215,7 +213,7 @@ export class GameRoomService {
disconnected: '1',
disconnectedAt: Date.now().toString()
});
pipeline.expire(REDIS_KEY.PLAYER(clientId), this.PLAYER_GRACE_PERIOD);
// pipeline.expire(REDIS_KEY.PLAYER(clientId), this.PLAYER_GRACE_PERIOD);

await pipeline.exec();

Expand All @@ -233,7 +231,7 @@ export class GameRoomService {
const remainingPlayers = await this.redis.scard(roomPlayersKey);

// 4. ํ”Œ๋ ˆ์ด์–ด ๊ด€๋ จ ๋ชจ๋“  ํ‚ค์— TTL ์„ค์ •
await this.setTTLForPlayerKeys(clientId);
// await this.setTTLForPlayerKeys(clientId);

if (remainingPlayers === 0) {
// ๋งˆ์ง€๋ง‰ ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๋‚˜๊ฐ„ ๊ฒฝ์šฐ
Expand All @@ -255,46 +253,27 @@ export class GameRoomService {
await pipeline.exec();
}

/**
* ๋น„ํ™œ์„ฑ ๋ฐฉ ์ฒดํฌ (์ฃผ๊ธฐ์ ์œผ๋กœ ์‹คํ–‰)
*/
@Cron(CronExpression.EVERY_MINUTE)
async checkInactiveRooms(): Promise<void> {
const now = Date.now();
const rooms = await this.redis.smembers(REDIS_KEY.ACTIVE_ROOMS);
this.logger.verbose(`๋น„ํ™œ์„ฑ ๋ฐฉ ์ฒดํฌ์‹œ์ž‘ / ํ™œ์„ฑ ๋ฐฉ ๋ชฉ๋ก: ${rooms}`);

for (const roomId of rooms) {
const lastActivity = await this.redis.hget(REDIS_KEY.ROOM(roomId), 'lastActivityAt');

if (lastActivity && now - parseInt(lastActivity) > this.INACTIVE_THRESHOLD) {
await this.redis.publish('room:cleanup', roomId);
this.logger.verbose(`๋น„ํ™œ์„ฑ์œผ๋กœ ์ธํ•ด ๋ฐฉ ${roomId} ์ •๋ฆฌ ์‹œ์ž‘`);
}
}
}

/**
* ํ”Œ๋ ˆ์ด์–ด ๊ด€๋ จ ๋ชจ๋“  ๋ฐ์ดํ„ฐ์— TTL ์„ค์ •
*/
private async setTTLForPlayerKeys(clientId: string): Promise<void> {
let cursor = '0';
const pattern = `Player:${clientId}:*`;
const pipeline = this.redis.pipeline();

do {
// SCAN์œผ๋กœ ํ”Œ๋ ˆ์ด์–ด ๊ด€๋ จ ํ‚ค๋“ค์„ ๋ฐฐ์น˜๋กœ ์ฐพ๊ธฐ
const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
cursor = nextCursor;

// ์ฐพ์€ ๋ชจ๋“  ํ‚ค์— TTL ์„ค์ •
for (const key of keys) {
pipeline.expire(key, this.PLAYER_GRACE_PERIOD);
}
} while (cursor !== '0');

await pipeline.exec();
}
// private async setTTLForPlayerKeys(clientId: string): Promise<void> {
// let cursor = '0';
// const pattern = `Player:${clientId}:*`;
// const pipeline = this.redis.pipeline();
//
// do {
// // SCAN์œผ๋กœ ํ”Œ๋ ˆ์ด์–ด ๊ด€๋ จ ํ‚ค๋“ค์„ ๋ฐฐ์น˜๋กœ ์ฐพ๊ธฐ
// const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
// cursor = nextCursor;
//
// // ์ฐพ์€ ๋ชจ๋“  ํ‚ค์— TTL ์„ค์ •
// for (const key of keys) {
// pipeline.expire(key, this.PLAYER_GRACE_PERIOD);
// }
// } while (cursor !== '0');
//
// await pipeline.exec();
// }

async kickRoom(kickRoomDto: KickRoomDto, clientId: string) {
const { gameId, kickPlayerId } = kickRoomDto;
Expand Down

0 comments on commit 01546ea

Please sign in to comment.