실시간 스트리밍 Web 경매 서비스
- 실시간 스트리밍으로 본인의 물건을 직접 홍보 및 판매
- 입찰가에 따른 경매 시간 변동으로 치열하고 재미있는 온라인 경매 경험
- 실시간 채팅으로 구매자 판매자가 소통할 수 있어요
- 실시간 방송으로 물건을 설명할 수 있어요
- 경매품의 상세 이미지와 동영상을 업로드 할 수 있어요
- 입찰 금액에 따라 시간이 변동되는 동적 경매시스템으로 경매 경쟁을 활발하게 만들어요
- mediasoup를 사용해 sfu방식 실시간 스트리밍 기능 구현
- passport로 소셜 로그인 전략패턴으로 구현
- Redis와 bull(MQ)를 이용한 입찰 시스템 및 시간 감소 로직 구현
- socket 통신 구현
- 로그인 Guard 및 경매 Guard 구현
- AWS File 업로드 및 영상 파일 HLS 트랜스 코딩 구현
- 경매 서비스에 필요한 기초 CRUD 구현
- pm2기반 모노레포 방식 도입, /src/app 디렉토리
- pm2와 git-action을 이용한 3개 인스턴스 CI/CD 파이프라인 구축
- ConfigModule를 이용한 환경변수 관리
- typeorm 적용
- class-validator, class-transformer을 이용한 유효성 검사
- test를 위한 모듈 및 기능 개발 ( 익명 로그인 기능 )
Email. [email protected]
Email. [email protected]
Email. [email protected]
- Nestjs
- typeorm
- postgresql
- JWT
- passport
- bull queue
- redis
- mediasoup
- aws ec2
- aws s3
- aws CloudFront
- aws MediaConvert
- Nginx
- 더 가벼운 러닝커브
- 더 적은 설정 파일과 의존성
- JavaScript 생태계의 풍부한 라이브러리 활용 가능
- 프론트엔드와 동일한 언어 사용으로 협업 용이
- 실시간 경매라는 도메인의 특징 상, 1s 이내의 delay의 영상 송수신이 필요
- 모바일 웹 환경에서 ffmpeg는 사용 불가
- HLS 방식은 encoding time으로 인한 낮은 실시간성
- 1s 이내의 실시간성과, 대규모의 동접자를 수용하기 위해 webRTC 중 SFU 방식을 채택하기로 결정
- 위의 목표를 달성하기 위해 ‘Mediasoup’ 도입 결정
- OpenVidu
- webRTC 실시간 영상통화 서비스를 쉽게 구축하게 해주는 platform
- 고수준의 api를 제공하여 쉽고 빠르게 media server 구축 가능
- 상대적으로 더 많은 리소스 사용
- Mediasoup
- sfu 방식의 webRTC media server 구축을 도와주는 node.js 기반 library
- 저수준의 api를 제공. video, audio 품질, bitrate 조정, 개별 스트림 레벨 제어 등 실시간 스트리밍 품질과 관련된 다양한 요소를 직접 조절 가능
- 상대적으로 적은 리소스 사용
- OpenVidu
- 위의 목표를 달성하기 위해 ‘Mediasoup’ 도입 결정
- 약 200대의 Headless Browser를 생성한 E2E Media Server 부하 테스트 진행
** Pupeteer를 사용해 Headless Browser를 만들며, webRTC 관련 통계치 정보를 제공하는 webrtcperf library 사용- stream packet 처리는 높은 cpu 집약적 작업임
- 하지만 현재 Media server의 ec2 instance의 2core로 시연 시 예상 서비스 참여자 100명에게 0.5s 이내의 안정적으로 실시간 영상이 제공가능할지 확신이 없었음
- 이를 검증하기 위해, 아래와 같이 2대의 c7i.8xlarge(32core, 64GB) instance에서 각 100대의 Headless Browser를 생성해 부하 테스트 진행
-
- 그 결과 아래와 같이 chrome에서 제공하는 webrtc stats tool(webrtc-internals)을 통해 jitterBufferDelay와 processingDelay의 합이 0.5s 미만임을 확인함
- media server의 CPU 사용량도 200대 기준 40% 대로 안정적이었음
- 반복 검증 결과 동시 접속자 100명 당 2core 기준 cpu 사용량 20% 정도를 차지. scale out or up을 적용하면 훨씬 많은 수의 동시접속자 수를 감당할 수 있을 것으로 추정
- 위 테스트 결과를 통해, 시연 시 참가자들을 서비스를 참여시키는 형식의 발표를 구성하기로 의사결정
- => 실제로 위 발표 영상에서와 같이, 시연 당일 60명의 동시 접속자에게 안정적인 스트리밍 영상 제공. 당일 발표한 8팀 중 유일하게 참가자가 시연 중 직접 사용 가능한 서비스였음
- 초기 기획은 다양한 소셜로그인 지원
- 서버에서 사용자의 데이터를 저장할 필요가 있었기에 OAuth2.0의 REST API 방식을 사용해야함.
- REST API ( 권한 코드 승인 방식 )은 Handshake 과정이 존재하며 이 과정은 소셜미디어 마다 차이가 존재할 수 있음.
- 해당 Handshake 과정을 전략패턴으로 구현하는 Passport 라이브러리 채택
- 높은 확장성: 일반 로그인 및 JWT와 연계가 용이함
- 소셜 로그인외에 JWT 토큰을 자체 발급해야함.
- JWT 토큰에 대한 처리(유효성) 또한 passport를 통해 간편히 다룰 수 있음.
- 효율적인 자원관리 및 동작의 일관성 보장 필요
- 단일 class에 로직을 집중하고 이 class를 비동기 익명생성하는 방식으로 경매 시스템을 구현. 경매가 종료된다면 GC에 의해 수집될 수 있음.
- 비동기 실행되는 경매에 접근할 방법이 필요함.
- Hash Map으로 진행중인 경매를 관리하여 O(1)의 접근 시간을 보장함.
- result → 사용자는 경매에 관한 모든 로직에서 경매 ID를 요구받게 됨.
- Hash Map으로 진행중인 경매를 관리하여 O(1)의 접근 시간을 보장함.
- 경매 State에 따른 사용자 접근 제한이 필요함
- Hash Map에 존재하면 경매는 진행 중이라 판단, Hash Map에 특정 경매가 존재하는지 여부를 판단하는 Guard ( nestjs)를 구현하여 진행중인 경매가 아니라면 JOIN 및 모든 요청 거부
- Lifecycle에 기능이 많아질 수록 DI 관련 로직이 증가해 효율성 감소
- 함수형 프로그래밍 방식 채택. 미리 선언된 필요한 함수를 Lifecycle에 등록하는 것으로 IOC를 구현. Lifecycle은 의존성관련 코드가 없어도 다양한 모듈에 접근이 가능함.
- 실시간 데이터 처리
- Redis의 인메모리 아키텍처는 밀리초 단위의 응답 속도를 보장하여, 실시간 입찰요청 처리를 빠르게 수행할 수 있음
- 확장성과 고가용성
- Redis는 클러스트링을 통해 수평 확장이 가능
- 사용자 수와 입찰 요청이 증가해도 성능 저하없이 확장 가능
- Bull Queue와의 호환성
- 같이 도입한 기술인 Bull Queue는 Redis를 기반으로 설계되어, 두 기술 간 통합이 간단하고 효율적임
- 입찰 기록 로깅
- 입찰 기록을 Redis에 로깅, 시간 변동 로직에서 조회하여 비교후 감소할 시간량을 산출
- 경매 입찰 요청의 동시성 문제 해결
- 경매가 진행될 때 다수의 입찰 요청을 순차적으로 처리하고, 데이터 무결성을 보장하기 위한 메세지 큐 필요
- Bull Queue는 메세지 큐 역할을 하며, 작업(job)을 Redis를 이용해 효율적으로 관리하고 작업 상태를 추적 가능
- 작업 처리의 안정성
- Bull queue는 작업을 실패하면 자동으로 재시도하거 실패 상태로 전환하여 관리할 수 있는 기능을 제공함
- 실시간 경매 중 발생할 수 있는 처리 오류를 안정적으로 관리 가능
- 직관적인 API, Nest.js에 적합
- Bull Queue 사용하기 쉬운 API를 제공하여 작업 큐 생성, 작업 추가, 작업 처리등을 수월하게 구현 가능
- Node.js 환경에서 사용하기 적합하도록 설계되어, Nest.js에서 쉽게 도입 가능한 기술
-
webRTC Test 불완전성
- delay 측정의 오차
- 실제 delay는 다음과 같은 값을 포함함.
- delay = 송신 측 encoding time + packet send to server network time + server processing time + packet send to 수신 network time + 수신 측 buffer time + processing time(decoding + rendering)
- delay에서 가장 큰 영향은 사실 network time임. 아무리 서비스를 잘 만들어도 network 품질이 떨어진다면 delay는 늘어날 수 밖에 없음
- 그리하여 delay의 요소 중 network time은 배제하고, 그 다음 크게 영향을 끼칠 수 있는 요소인 buffer time과 processing time만으로 delay를 추정함
- 실제 delay는 다음과 같은 값을 포함함.
- 다양한 테스트 시나리오의 부재
- 순간 부하, 여러 경매 방 진행, 채팅 및 입찰과의 통합 시나리오 등
- Headless Browser와 Media server의 연결 불안정성
- delay 측정의 오차
-
시간 줄이기 로직에서 논리적 충돌(Lock)
- 현재 시간을 줄이는 로직과 시간이 흐르는 로직에 대해 별다른 lock을 구현하지 않고 있음.
- Node가 Single Thread 기반이라 별 문제없이 작동하는 듯 여겨지지만 정확한 이유 파악및 lock 도입 필요
- 현재 시간을 줄이는 로직과 시간이 흐르는 로직에 대해 별다른 lock을 구현하지 않고 있음.
-
단일 Queue로 모든 입찰 요청을 관리
- 모든 경매에서 동일 Queue 사용
- 병렬적으로 진행되는 각 경매 세션에 대해서 경매 입찰 메세지 큐를 분리하지 않고 있음
- 하나의 Queue로 모든 입찰 요청을 처리하기 때문에 Queue 생성 및 관리가 단순하다는 장점이 있지만, 병목현상 발생 가능
- 경매방 ID를 기준으로 개별 Queue 생성 필요
- 병렬 처리가 향상되어, 특정 경매방에서 대량의 입찰 요청이 발생하더라도 다른 경매방에 영향을 주지 않음
- 각 Queue가 특정 경매방의 작업만 처리하여, 작업 상태 추적 및 관리가 용이질 것으로 기대
- 모든 경매에서 동일 Queue 사용
-
EDA(Event Driven Architecture) 구현
- MSA에서 서비스 간 통신 미흡
- Media Server와 Bid Server의 통신 방법으로 Bull과 Redis를 이용한 Message Subscribe 방식을 도입하려 했으나 시간적인 이유로 구현까지는 이르지 못했음
- 경매 진행서버와 입찰 서버의 분리 필요
- 단기간에 입찰이 엄청많이 발생하면 Bid Server가 이 응답을 다 처리하는 동안 시간을 감소시키지 못해 경매 시간이 줄지 않는 현상
- 물리적으로 서버를 분리해야했으나 Message Subscribe 방식이 선행되어야 했기에 마찬가지로 구현에는 이르지 못했음.
- 단기간에 입찰이 엄청많이 발생하면 Bid Server가 이 응답을 다 처리하는 동안 시간을 감소시키지 못해 경매 시간이 줄지 않는 현상
- MSA에서 서비스 간 통신 미흡
-
Scale out 고려 미흡
- 현재 경매에 대한 정보는 In-memory의 Context객체를 통해 관리되고 있음
- 만약 Context객체가 저장하고 있는 정보를 Redis로 옮겨도 지연시간이 크지 않다면 (측정 필요) 기존 Context 객체는 Redis의 값을 참조하도록 변경한 이후 인스턴스 자체를 Scailing하는 방식으로 높은 부하에 대응할 수 있을 것이라 예상
- 현재 경매에 대한 정보는 In-memory의 Context객체를 통해 관리되고 있음




