프로그래밍/회고

DASHI - 챕터와 속도 개선 업데이트

Lou Park 2024. 3. 31. 15:06

R2 도입

CloudFlare R2

얼마전, DASHI의 스트리밍 소스를 트위치에서 치지직으로 변경했다.

이미 다른 사람들이 치지직 API를 가지고 만들어 둔 소스를 참고해서 구현한 터라 크게 이슈는 없었지만 한가지 마음에 걸리는 부분이 생겼다. 바로...우리집 인터넷 속도! 

 

영상 서빙을 하면서 100mbps 인터넷을 쓰는 건 애초에 걱정했던 바였지만, 그래도 트위치때는 720p 30프레임이라 세그먼트당 파일 크기가 아주 못볼수준은 아니었다. 하지만 치지직은 60프레임이라 거의 1.5x~2.0x가량 용량이 뛰었고 시청에도 불편함을 줄 정도가 되었다. 이를 해결할 방법을 여러 생각해보았는데...

 

Plan1. 500mbps 인터넷을 가입한다

500메가 인터넷을 가입한다는 플랜을 떠올렸으나, 하루 이용자 7명 정도의 서비스 때문에 월 3만원을 지불하는건 홈서버로 돌릴 이유가 없다. 기-각

 

Plan2. 본가에 서버를 보낸다

500mbps 인터넷을 사용하는 본가에 서버를 설치해보려고 했다.

하지만 KT 공유기는 DHCP 설정이 불안하다는 썰이 인터넷에 떠돈다...

그리고 엄마가 청소하면서 서버 전원을 빼버릴 리스크... 기-각

 

Plan3. 영상 파일을 AWS S3에 저장한다

AWS S3에 영상 파일을 저장하는 방법도 생각해봤다.

하지만 찾아보니 저장에 대한 비용은 물론이고, Outbound 트래픽에대한 요금을 추가로 지불해야하는 구조라서 마음을 접었다. 기-각!

 

희망이 없어보였는데 불현듯 회사에서 잠깐 써본 CloudFlare R2가 떠올랐다.

R2를 찾아보니 빛같은 문구가 보인다. "송신료 없는 개체 스토리지(Zero Egress Fee Distributed Object Storage)" 데엠!

Data Egress Fee라는 용어는 생소했는데, Bandwidth 또는 Data Transfer Fee를 그렇게 부른다고한다. 

 

R2의 요금 정보

10GB 스토리지 무료(는 별 도움안되긴하지만)에 월 1백만건의 Mutation, 1천만건의 Read가 무료!

계산해보니 보관하는 최대 영상 수를 제한한다면 넉넉잡아 월 $2내에도 이용할 수 있을 것 같아서 바로 도입했다. R2의 도입으로 우리집 인터넷 기준 영상 세그먼트하나의 다운로드 속도는 대략 1초 좀 넘게 걸리고, 거의 버퍼링없이 영상을 시청할 수 있게되었다.

 

 

챕터 추가

영상 챕터가 추가되었다.

그리고 또 마음속으로 추가하고 싶었던 챕터 기능을 넣어봤다.

트위치 다시보기가 있었던 시절에는 챕터별로 영상이 끊겨있어서 참 편했는데, 그 맛을 잊을 수 없었다...

방송 녹화 중에는 방송 정보를 받아오지 않았지만, 챕터 기능을 위해 방송 정보를 Polling하고 새로운 챕터가 시작될 때마다 챕터정보를 넣어주었다. videojs에 VTT로 Chapter Track을 설정해주면 챕터를 지원하기에 큰 어려움은 없었다.

 

방송 중에 챕터가 바뀔때마다 VTT를 업데이트하는 "ChapterManager"라는 클래스에서 이를 처리했다.

 

클래스에 "Manager" 붙이기...어디선가 안 좋은 관습이라는 얘기를 들은 적있어서 늘 조심스럽기는 하다. (하지만 그만한 이름이 없읍ㅋㅋㅋ) 안좋은 관습이라는 이유는 이 "Manager"는 모든지 할 수 있기때문이다.

 

이번에 챕터 기능을 부랴부랴 도입한 이유는 사실 <플레이한 게임 목록> API가 필요해서다.

스트리머가 올해 플레이한 게임을 쭉 정리하고 있는데 인디게임이 아니라면 치지직 카테고리에 정보가 있으니 어느정도는 자동화가 가능해보였다. AppsScript의 Trigger를 이용하면 시트를 열때 일괄적으로 그동안 DASHI가 정리하고 있었던 플레이 목록을 쫙 업데이트 쳐줄 수 있다.

 

그래서 ChapterManager를 가지고 할 일이 하나 더 늘어난다.

1. 방송 챕터에 대한 VTT 파일을 만들어야하며.

2. 플레이 히스토리를 만들어야한다.

 

챕터와 플레이 히스토리는 다르다.

- 챕터는 그냥 이전 챕터와 다르면 저장,

- 플레이 히스토리는 게임이 아니면 저장하면 안되고, 이전에 중복값이 있다면 추가되서는 안된다.

 

Talk -> 마인크래프트 -> Talk -> 마인크래프트

 

방송중에 위와 같은 순서로 카테고리가 바뀌었다고하면 둘은 이렇게 저장된다.

- 챕터: Talk -> 마인크래프트 -> Talk -> 마인크래프트

- 플레이 히스토리: 마인크래프트

 

두 기능 모두 챕터가 변경될때마다 처리되어야하기에 ChapterManager에 추가하면 될 것같다는 달콤한 유혹이있지만 결론적으로 ChapterManager에다가 플레이 히스토리를 껴넣을려고 하는 순간부터 재앙이 시작될 것임이 분명하다...

 

 

https://howtodoinjava.com/design-patterns/behavioral/observer-design-pattern/

 

그래서 ChapterManager를 팡팡 부수고 Observer 패턴으로 변경했고,

뭉쳐있던 코드들은 ChapterObserver와 ChapterListener로 쪼개졌다.

각각의 Task들을 처리하는 클래스는 Listener를 상속받아 구현했다. 주 언어가 Kotlin이라 Python도 비슷하게 사용하게 되는데...@absctractmethod는 처음 써봤다.

class ChapterObserver:

    def __init__(self):
        self.listeners: list[ChapterListener] = []

    def add_listener(self, listener: ChapterListener):
        self.listeners.append(listener)
class ChapterListener:

    @abstractmethod
    def on_recv_chapter(self, chapter: Chapter, is_new: bool, delta: timedelta):
        pass
class PlayHistoryManager(ChapterListener):

    def on_recv_chapter(self, chapter: Chapter, is_new: bool, delta: timedelta)
    	# do somthing


class ChaptherTrackManager(ChapterListener):

    def on_recv_chapter(self, chapter: Chapter, is_new: bool, delta: timedelta)
    	# do somthing

 

 

덕분에 뭐든 더 넣을 수 있을 여유가 생겼다.

observer = ChapterObserver()
observer.add_listener(track_manager)
observer.add_listener(history_manager)

 

마무리

이렇게 또 주말 삭제식을 할 생각이 없었는데...ㅋㅋㅋ도파민이 돌아서 결국 또 해를 봤다.

단지 몇 명 뿐이라도 내가 만든걸 누군가 써준다는건 정말 고마운 일이다.

그리고 그 도구가 편해질때까지 할 수 있는 걸 해보는 것이 즐겁다.