
ICPC MT 실시간 진행 시스템
2026.06
문제
진행자가 발표 노트북 앞에 묶이고, 점수와 슬라이드가 기기마다 따로 놀았습니다.
내 역할
- 발표 화면·폰 리모컨·보기 전용 화면 분리 설계
- Flask-SocketIO로 슬라이드·음악·점수 상태 broadcast와 구독 구현
- Oracle Cloud nginx + gunicorn(eventlet) 단일 워커 배포
확인 결과
- 발표 화면과 폰 리모컨을 분리해 진행자가 노트북에서 자유로워짐
- 점수·슬라이드·음악 상태를 WebSocket으로 다기기 실시간 동기화
- Oracle Cloud에 nginx + gunicorn(eventlet) 단일 워커로 배포
제가 기여한 부분
- 발표 화면·리모컨·보기 전용 화면의 상태 흐름 분리 설계
- Flask-SocketIO broadcast/구독과 scores.json 영속 구현
- 단일 워커(eventlet) 제약과 autoplay 정책 트러블슈팅
데모 안내
폰 리모컨: https://juyoung-icpc-mt.duckdns.org/remote — 발표 화면을 원격으로 넘기고 배경음악·효과음·점수판을 제어합니다. 점수판은 https://juyoung-icpc-mt.duckdns.org/view 에서 보기 전용으로 공유됩니다.
contribution
전체 기능 / 제 기여
contribution
전체 기능 / 제 기여
전체 프로젝트 기능
- 55+개 슬라이드 MT 진행 덱과 인터랙티브 게임
- 폰 리모컨 원격 슬라이드·음악·효과음 제어
- 실시간 점수판과 보기 전용 공유 화면
- Oracle Cloud 배포와 HTTPS 운영
제가 맡아 정리한 부분
- 발표 화면·리모컨·보기 전용 화면의 상태 흐름 분리 설계
- Flask-SocketIO broadcast/구독과 scores.json 영속 구현
- 단일 워커(eventlet) 제약과 autoplay 정책 트러블슈팅
- nginx + gunicorn + systemd + certbot 배포 구성
role
내 역할 · 규모
role
내 역할 · 규모
- 발표 화면·폰 리모컨·보기 전용 화면 분리 설계
- Flask-SocketIO로 슬라이드·음악·점수 상태 broadcast와 구독 구현
- Oracle Cloud nginx + gunicorn(eventlet) 단일 워커 배포
기술 스택
case flow
문제 · 해결 · 결과
case flow
문제 · 해결 · 결과
핵심 성과
- 발표 화면과 폰 리모컨을 분리해 진행자가 노트북에서 자유로워짐
- 점수·슬라이드·음악 상태를 WebSocket으로 다기기 실시간 동기화
- Oracle Cloud에 nginx + gunicorn(eventlet) 단일 워커로 배포
문제
진행자가 발표 노트북 앞에 묶이고, 점수와 슬라이드가 기기마다 따로 놀았습니다.
해결
발표 화면이 슬라이드·음악 상태를 서버로 broadcast → 서버가 상태를 캐시 → 폰 리모컨(/remote)이 구독해 원격 제어하고, 점수판은 POST→scores.json 저장→전 기기 WebSocket 동기화로 구성했습니다.
결과
폰 하나로 슬라이드 넘김·배경음악·실시간 점수를 제어하고, 여러 기기가 동시에 같은 상태로 동기화되며 진행할 수 있었습니다.
architecture
설계 / 문서
architecture
설계 / 문서
아키텍처
- 인터넷 → nginx(80/443) → gunicorn+eventlet(127.0.0.1:8099) → Flask-SocketIO(app.py)
- 점수는 scores.json에 디스크 영속, 슬라이드·음악 상태는 서버 메모리에 캐시 후 WebSocket broadcast
ERD / DB
- 관계형 DB 없이 scores.json에 조 이름·조장·점수를 영속 저장
- 서버 메모리에 현재 슬라이드 인덱스와 음악 재생 상태를 캐시해 신규 접속자에게 동기화
API 명세
- 발표 화면 서빙(GET /), 점수 조회·갱신(GET·POST /api/scores), 조 이름·조장 수정(POST /api/teams), 음악 상태(GET /api/music)
- 폰 리모컨(GET /remote)·보기 전용(GET /view), WebSocket 이벤트: score_update·slide_state·music_state·slide_cmd·music_cmd·sfx_cmd
debugging
트러블슈팅
debugging
트러블슈팅
WebSocket broadcast가 워커 간에 전달되도록 gunicorn은 redis 같은 메시지 큐 없이 단일 워커(-w 1, eventlet)로 운영해야 했습니다. 브라우저 autoplay 정책 때문에 배경음악 첫 재생은 사용자가 한 번 클릭하도록 처리했습니다.
verification
검증 / 운영 기록
verification
검증 / 운영 기록
모바일 확인
폰 리모컨(/remote)을 모바일 화면 기준으로 구성해 슬라이드 제어·효과음·점수 버튼을 한 손으로 누를 수 있게 했습니다.
보안 체크
리모컨과 발표 화면은 제어 권한을 갖고, /view는 점수를 실시간으로 받기만 하는 보기 전용으로 분리해 외부 공유 시 편집·동기화 명령을 받지 않게 했습니다.
운영 경험
Oracle Cloud Ubuntu에 systemd(mt.service)로 gunicorn+eventlet을 단일 워커로 띄우고 nginx 리버스 프록시(WebSocket upgrade 포함)와 certbot HTTPS로 운영했습니다.
features
주요 기능
features
주요 기능
problem solving
문제 해결 과정
problem solving
문제 해결 과정
진행자가 발표 노트북 앞에 묶여 있었고, 슬라이드와 점수가 기기마다 따로 놀아 진행이 끊겼습니다.
발표 화면이 슬라이드·음악 상태를 서버로 broadcast하면 서버가 상태를 캐시하고, 폰 리모컨(/remote)이 이를 구독해 원격 제어하도록 분리했습니다. 점수는 POST로 받아 scores.json에 저장한 뒤 전 기기에 WebSocket으로 동기화했습니다.
실시간 다기기 진행은 화면을 늘리는 것보다 상태를 어디에 두고 누가 broadcast/구독하는지를 먼저 정해야 했습니다.
takeaways
프로젝트에서 배운 것
takeaways
프로젝트에서 배운 것
실시간 동기화는 상태의 소유 위치와 broadcast/구독 흐름을 먼저 정해야 함
WebSocket broadcast는 메시지 큐 없이 다중 워커를 쓰면 워커 간 전달이 끊김
브라우저 autoplay 정책 때문에 음악 첫 재생은 사용자 클릭이 필요함
점수처럼 보존이 필요한 상태는 파일에 영속하고 화면 상태는 메모리 캐시로 충분함
interview prep
면접 질문 3개
interview prep
면접 질문 3개
Q01
발표 화면과 리모컨을 왜 분리했나요?
진행자가 노트북 앞에 묶이지 않게 하기 위해서입니다. 발표 화면이 슬라이드·음악 상태를 서버로 broadcast하면 폰 리모컨이 구독해 원격으로 넘기고 제어하도록 역할을 나눴습니다.
Q02
여러 기기의 점수와 슬라이드는 어떻게 동기화했나요?
상태를 서버에 두고 WebSocket으로 broadcast했습니다. 점수는 POST로 받아 scores.json에 저장한 뒤 전 기기에 score_update를 보내고, 슬라이드·음악 상태는 서버 메모리에 캐시해 새로 접속한 기기에도 현재 상태를 내려줬습니다.
Q03
배포에서 가장 신경 쓴 점은 무엇인가요?
WebSocket broadcast가 워커 간에 끊기지 않도록 gunicorn을 메시지 큐 없이 단일 워커(eventlet)로 운영한 점입니다. nginx에 WebSocket upgrade 설정을 넣고 certbot으로 HTTPS를 붙여 폰에서도 wss로 연결되게 했습니다.