YuGiOhDeck X queueSystem

a·2025년 6월 22일
0

사이드 프로젝트

목록 보기
1/3

프로젝트 개요

프로젝트명: YuGiOhDeck (유희왕 덱 빌더)

  • 기간: 2024년 7월 20일 ~ 2024년 7월 24일
  • 팀 규모: 개인 프로젝트
  • 역할: 풀스택 개발 (Backend & Frontend)

YuGiOhDeck은 유희왕 카드를 검색·선택하여 나만의 덱을 구성하고 공유할 수 있는 웹 애플리케이션입니다. URL에 덱 구성을 인코딩해 친구에게 간편하게 덱을 전달할 수 있도록 설계되었습니다.


사용 기술 스택

  • Backend:

    • Spring Boot (Java)
    • MySQL
    • Redis
    • WebSocket (Spring WebSocket)
  • Frontend:

    • React
    • TypeScript
    • Styled-Components
    • react-three-fiber (카드 3D 효과)

주요 기능 및 구현 상세

1. WebSocket을 이용한 대기열 시스템

  • 목적: 동시에 많은 사용자가 카드 검색/덱 구성 페이지를 사용 중일 때, 과도한 로드를 방지하고 안정적인 실시간 알림(broadcast)을 제공하기 위함.

  • 구현:

    1. Spring WebSocket을 기반으로 /queue 엔드포인트 설정
    2. 사용자가 페이지에 접속하면 enter 메시지를 서버로 전송하고, 서버는 Redis의 ZSet에 사용자 세션 ID와 타임스탬프를 저장
    3. 최대 입장 가능 인원(MAX_RUNNING) 체크 후, 초과 시 대기열로 이동
    4. 대기열 순서나 입장 가능 여부를 WebSocket broadcast() 호출로 클라이언트에 실시간 전송
// Redis에 사용자 추가 예시
String runKey = RUNNING_PREFIX + qid;
if (redis.opsForZSet().score(runKey, user) == null && totalRunningSize() < MAX_RUNNING) {
    redis.opsForZSet().add(runKey, user, Instant.now().toEpochMilli());
}
  • 우선순위 대기열 관리:

    • VIP 사용자와 일반 사용자를 구분하여 각각 다른 ZSet(vip, main)에 저장
    • 사용자 score를 통해 입장 우선순위 계산 (VIP 우선 처리)
// 대기열 우선순위 계산 및 다음 사용자 프로모션
TypedTuple<String> vipTuple  = firstWithScore(vipKey);
TypedTuple<String> mainTuple = firstWithScore(mainKey);

// VIP 우선 처리
if (vipScore <= mainScore) {
    uid = vipTuple.getValue();
} else {
    uid = mainTuple.getValue();
}
  • 대기열 Vacancy 발생 시 자동 프로모션:

    • 세션 만료로 인한 vacancy 발생 시, 대기열에서 자동으로 사용자를 프로모션하여 RUNNING ZSet으로 이동
    • WebSocket을 통해 ENTER 메시지 전송
promoteVip.forEach(uid -> notifier.sendToUser(uid, "{\"type\":\"ENTER\"}"));
promoteMain.forEach(uid -> notifier.sendToUser(uid, "{\"type\":\"ENTER\"}"));

2. TTL과 장기간 미사용 사용자 처리

  • TTL 설정: Redis Sorted Set의 각 사용자 엔트리마다 score로 타임스탬프를 저장하고, EXPIRE를 걸어 세션 만료 시 자동 삭제

  • 비활성 사용자 제어 로직:

    1. @Scheduled 어노테이션을 사용하여 10초 주기로 모든 RUNNING ZSet 점검
    2. 현재 시각 기준으로 TTL (예: 5분) 초과 사용자를 검색 후 제거
    3. 세션 만료 사용자에게 WebSocket으로 TIMEOUT 메시지 전송 후 RUNNING ZSet에서 제거
long cutoff = System.currentTimeMillis() - sessionTtlMillis();
Set<String> expired = redis.opsForZSet().rangeByScore(runKey, 0, cutoff);
expired.forEach(uid -> notifier.sendToUser(uid, "{\"type\":\"TIMEOUT\"}"));
  • Client 측 heartbeat 처리:

    • React에서 useCallback으로 ping 메시지를 주기적으로 서버에 전송하여 세션 유지
const sendPing = useCallback(() => {
    if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
      wsRef.current.send('PING');
    }
}, []);

3. 검색 성능 개선 (Ngram Full-Text 도입)

  • 기존:

    • Query DSL 기반 직접 LIKE 검색 사용
    • 공백 제거 후 prefix 방식으로 korName, name 검색
@Query("""
SELECT c FROM CardModel c
WHERE (:frameType = '' OR c.frameType = :frameType)
  AND (LOWER(REPLACE(c.korName,' ','')) LIKE CONCAT(:norm,'%')
    OR LOWER(REPLACE(c.name,' ','')) LIKE CONCAT(:norm,'%'))
""")
Page<CardModel> searchByNameContaining(...);
  • 문제점: 속도 저하, 인덱스 미활용, 중간 검색 불가
  • 개선: Ngram 기반 Full-Text 인덱스 적용 (중간 검색 가능)
  • 성과:
지표기존 검색 (Fuzzy)Fulltext 검색변화량
샘플 수10 00010 000
평균 응답 시간1 635 ms1 112 ms–523 ms (–32 %)
중앙값 (Median)1 743 ms1 083 ms–660 ms (–38 %)
최소 응답 시간53 ms26 ms–27 ms
최대 응답 시간10 365 ms3 857 ms–6 508 ms
표준 편차 (Std.Dev)939 ms420 ms–519 ms (–55 %)
처리량 (Throughput)44 req/sec63 req/sec+19 req/sec (+43 %)
에러율0 %0 %

검색 응답 속도가 평균 32% 개선되었으며, 처리량도 43% 향상됨.

4. 대기열 인원별 서버 성능 테스트

워커 수Throughput (RPS)평균 (ms)중앙값 P50 (ms)P90 (ms)P95 (ms)P99 (ms)Max (ms)Std.Dev (ms)
5051.11 4851 5482 0492 2143 0545 607531
4059.61 1081 1341 7251 8932 1043 634476
3063.79891 1071 4101 4751 6132 927398

최종적으로 **2초 SLA (P95 ≤ 2 000 ms)**를 만족하면서도 평균 응답속도 1초 미만(989ms), 높은 처리량(63.7 RPS)을 기록한 30명 제한이 최적임을 확인.


프로젝트 성과 및 배운 점

  • 성능 최적화: Redis TTL을 통한 세션 관리로 메모리 누수를 방지하고, WebSocket을 이용한 실시간 알림으로 서버-클라이언트 지연 최소화
  • 대기열 시스템 경험: 우선순위 기반 대기열과 자동 프로모션 로직 구현으로 대규모 사용자 처리 및 서버 부하 제어 능력 강화
  • 검색 개선 경험: Ngram 기반 Full-Text 검색 도입으로 검색 속도 32% 개선, 처리량 43% 향상
  • UX 강화: 3D 그래픽과 애니메이션을 통한 직관적 인터페이스 제공으로 사용자 만족도 향상
  • 풀스택 경험: Spring Boot부터 React 3D 렌더링까지 전 영역을 담당하며, 빠르게 프로토타이핑하고 배포하는 과정을 경험

사용된 핵심 라이브러리 및 도구

역할라이브러리/도구
WebSocket 서버Spring WebSocket
세션 관리Redis (TTL, ZSet)
3D 렌더링react-three-fiber, drei
스타일링styled-components
CI/CDGitHub Actions, Maven
검색 엔진MySQL Full-Text (Ngram)

결론: YuGiOhDeck 프로젝트를 통해 실시간 대기열 시스템 설계, Redis 세션 및 우선순위 관리, MySQL Full-Text 기반 검색 개선 등 고급 백엔드 아키텍처 역량을 강화하였으며, 프론트엔드 3D 인터랙션과 서버 부하 제어를 모두 만족시키는 풀스택 서비스를 완성하였습니다.

0개의 댓글