Tech

우당탕탕~ 영상 서비스 개발기 1탄 : 영상 CMS

ENTER TECH 2023. 4. 4. 11:00

 

 

 

당신의 손에 소녀들의 운명이 달려있습니다.

 

 

카카오엔터테인먼트에서 새롭게 도전한 버추얼 걸그룹 데뷔 서바이벌 프로그램!

'소녀 리버스(RE:VERSE)'

 

소녀 리버스는 30인의 미모와 개성이 넘치는 아이돌들이

정체를 숨긴 채 가상현실에서 빛나는 무대를 선보이는 버추얼 서바이벌 오디션 프로그램입니다.

 

최종 데뷔조에 속한 5인은 누구!!!

 

 

소녀 리버스가 만들어지기까지 정말 다양한 곳에서 많은 분이 노력해 주셨는데요.

그중에서도 단연 돋보이는 것은 여러분의 눈을 사로잡는 '영상'이 아닐까 싶습니다.

 

예를 들어

영상의 제목, 출연자, 회차 등 관련 정보를 관리하거나

엄청난 대용량 파일을 사이즈는 줄이고 품질은 유지해서 사용자에게 제공하거나

어떤 기기로 접속을 하던지 사용자 환경에 맞게 영상을 인코딩하는 등

이렇게 '영상 서비스'를 제공하기 위해서

곳곳에 다양한 기술들이 숨어있는데요.

 

 

과연 저희 영상서비스개발팀에서는

어떤 기술을 사용하여 개발했는지, 그 기술이 왜 필요한지

어떻게 적용했고, 어떤 효과를 가져왔는지

2022년 7월부터 2023년 3월까지

약 9개월간의 여정을 소개합니다. 

 

◼️ 우당탕탕~ 영상 서비스 개발기 1탄 : 영상 CMS 🔗
◼️ 우당탕탕~ 영상 서비스 개발기 2탄 : 인코더와 라이브 서비스 🔗
◼️ 우당탕탕~ 영상 서비스 개발기 3탄 : 플레이어 백엔드 서버와 데이터 수집 🔗

 

 

화목한 영상서비스개발팀 크루들!:)

 

 

 


 

 

[Chapter 1] 기획 개발 다모여 이벤트 스토밍

DDD 이벤트 스토밍

 

 서비스에 대한 전체적인 기획 및 설계가 아직 문서화되지 않았던 시기에 좀 더 나은 기획과 설계를 위해 여러 가지 방법론을 찾아보다 DDD(Domain Driven Design)의 설계 방식 중 하나인 이벤트 스토밍에 대해 관심을 가지게 되었습니다. 이벤트 스토밍은 Alberto Brandolini가 제안한 워크숍으로 잡한 비즈니스 도메인을 빠르게 탐색하고 학습하는 방법인데요. 프로젝트 구성원들이 모여 이틀간 워크숍을 진행하면서 서브도메인, 제약사항(Policy), 소프트웨어 모델링 등 다양한 정보를 얻을 수 있었습니다.

 

 

🔍 DDD에 대한 경험이 궁금하신 분들은 아래 내용을 참고해 주세요!

 

ㄷㄷㄷ: Domain Driven Design과 적용 사례 공유

카카오페이지 앱에는 수많은 종류의 콘텐츠들이 있습니다. 웹툰, 웹소설, 일반교양, 오디오, 동영상 등등.. 이런 콘텐츠들은 카카오페이지에서 제공하는 파트너사이트라는 툴을 이용하여, 작품/

kakaoentertainment-tech.tistory.com

 

 

 소녀 리버스는 카카오페이지 앱 내 별도 UX형태로 개발되었고, 아래는 저희가 구축한 대략적인 영상 서비스 구성도입니다. 

 

영상서비스 아키텍처 다이어그램

 

 

 

 

[Chapter 2] VODKA 개발기

VODKA는 영상 파일 및 메타 관리를 위한 CMS(Content Management System)입니다. VODKA라는 이름은 단순하게 VOD가 들어간 단어(아나그램)가 무엇이 있을까를 찾다가 마음에 드는 단어를 선택했는데, CMS를 운영하시는 팀에서 'VOD Kakaoent Admin'이라는 멋진 해석을 붙여주셨답니다.

 

CMS는 대게 아래와 같은 특성을 지녔습니다.

1️⃣ 사용자는 주로 회사 내부 직원
2️⃣ 디자인보다는 기능 동작에 초점
3️⃣ 테이블, 목록 등 흔히 정해진 UI를 다량 사용
4️⃣ BUT! 플랫폼/운영에 특화된 커스텀 UI 필수

 

많은 Admin 전용 템플릿/프레임워크/라이브러리 등은 운영툴에서 자주 쓰이는 UI 컴포넌트를 제공해서 적은 비용으로 빠르게 개발할 수 있도록 도와줍니다. VODKA는 개발 기간이 길지 않고, 프론트 개발 인원도 소수였기 때문에, 좋은 외부툴 선정이 성공적인 CMS 개발에 중요했습니다.

 

 

1.  리액트 프레임워크 선정

VODKA(CMS) 개발에 사용될 프론트 Admin 프레임워크를 선정하기 위해 아래와 같은 필수 조건과 우대 조건들을 먼저 세워 보았습니다.

 

필수 조건

1️⃣ React 기반일 것
 저희 팀은 아니지만 사내에 훌륭한 프론트엔드 개발자가 많습니다. 그분들이 대부분 React 기반으로 작업을 하시다 보니, 혹시 난관에 봉착했을 때 조언을 구하거나 도움을 요청할 수 있을 것 같아서 React 툴을 사용하기로 했습니다. (개인적으로는 Vue도 애정하는데... 점점 대세가 React로 되어 가고 있어서 아쉽습니다.)

2️⃣ 운영에 필요한 기본 제공 컴포넌트가 존재할 것
페이지네이션이 있는 테이블, 다양한 폼과 인풋들, 모달 등 운영에 반드시 쓰이는 UI 컴포넌트들을 내재하고 있고, prop을 다양하게 구현해서 여러 방면으로 사용할 수 있어야 했습니다. 내재된 컴포넌트가 쓰기 편할수록 UI를 그리는 시간을 아껴서 로직 구현에 쓸 수 있기 때문입니다.

3️⃣ data 연동 기능이 있을 것
운영툴 프론트는 데이터를 조회하고 생성하고 수정하고 삭제하는 CRUD 작업이 대부분입니다. 그러다 보니 비슷한 API 연동을 같은 모양의 UI 컴포넌트에 반복적으로 연결해 주는 코드가 들어가게 마련인데요, 이런 데이터 연동 부분을 under the hood로 해주면 개발 시간이 훨씬 단축될 수 있기 때문에 이 기능도 중점적으로 보았습니다.

 

 

우대 조건

1️⃣ 사용자가 많고 커뮤니티가 활발할 것
프론트는 특히나 기술의 발전 속도가 빠르다 보니, 외부 툴을 쓸 때 사용자 수가 많아야 힘을 받아서 오래 살아남고 리소스도 풍부한 것 같았습니다.

2️⃣ 문서 및 예제가 다양할 것
새로운 툴을 처음 쓸 때는 뭐든 할 수 있어 보이지만, 막상 구현을 시작하면 간단하지 않은 부분들이 많다는 것을 항상 느끼게 됩니다. 그럴 때, 문서나 예제가 잘 되어 있다면 헤매는 시간을 아끼고 지원 여부를 코드 레벨에서 확인할 수 있어서 유용합니다.

3️⃣ 커스텀 컴포넌트 작성이 쉬울 것
CMS나 Admin 운영툴은 소수의 내부 직원이 고객입니다. 그러다 보니 그들만을 위한 특수 UI 컴포넌트를 만들어야 하는 상황이 발생하는데, 이런 커스텀 UI 작업이 어렵지 않아야 했습니다. (소수의 고객인 만큼 최대한 니즈를 맞춰 주고 싶은 개발자의 따뜻한 마음...😊)

4️⃣ 개별 지원이 가능할 것
오픈소스가 아닌 유료 서비스는 고객 지원을 해주는 경우가 많습니다. 처음 쓰는 툴인만큼 개별적인 지원을 해준다면 도움이 될 수 있습니다.

5️⃣ graphQL 사용이 용이할 것
소녀 리버스 프로젝트는 소수 인원이 단기간에 개발할 모듈이 많았기 때문에, 서버-프론트 간의 작업을 최소화하기 위해 graphQL을 도입하려고 고민했습니다. 따라서 이를 지원하는 기능이 포함되기를 원했습니다. (결론적으로는 graphQL은 쓰지 않았지만...)

 

필수 조건을 바탕으로 React Admin, Refine, Retool, AdminJS 이렇게 4가지 후보군으로 추려보았는데, 각각의 프레임워크의 특징을 요약하고 장단점을 분석해 보겠습니다.

❗️아래 검토사항은 2022년도 6월에 진행된 것으로, 그 이후 각 프레임워크 별 변경사항이 있는 점 참고 바랍니다.

 

React Admin Framework 비교 분석

 

브랜딩과 특색이 필수인 유저향 서비스와 달리 CMS 같은 내부 운영툴은 대부분 비슷하게 구현이 됩니다. 그러다 보니 Low-code 또는 No-code 개발이 가장 용이한 분야라고 생각했고, Retool과 같은 프레임워크는 마치 예전의 나모 웹에디터가 브라우저에서 동작하는 듯한 느낌을 받았습니다. 실제로 연동이 필요한 부분만 개발자가 설정하고 그림을 그리는 분야는 마우스로 끌어다가(Drag and Drop) 그리면 깔끔하게 화면을 만들어주는 등 사용성이 용이하다는 판단에 Retool을 가장 먼저 검토하게 되었습니다.

 

Retool은 개발을 하지 않아도 된다는 큰 장점이 있지만, 직접 개발을 할 수 없다는 아주 제한적이라는 단점도 있었습니다. 한 가지 니즈를 해결하기 위한 개별 화면을 빨리 만드는 데는 특출 나게 효과적이었지만 복합적인 기능이 들어가는 하나의 애플리케이션으로 동작하기에는 부족한 점이 많았습니다. 또, 이때는 엔터프라이즈 버전에서만 호스팅을 지원했기 때문에 무료 버전이 아닌 엔터프라이즈 버전을 free trial로 테스트해야 했는데, 이 때문에 충분한 시간을 들여서 검토하기 어려웠고, 확실하게 검토가 되지 않은 상태에서 구매하기에는 엔터프라이즈 버전의 가격이 매우 높아 결국 포기하게 되었습니다. 지금 글을 작성하는 시점에는 셀프 호스팅이 무료 버전에도 가능해졌다고 하니 다음 프로젝트 때는 꼭 써보고 싶습니다.

 

그다음으로는 서버를 Node.js로 사용하지 않기 때문에 React Admin과 Refine을 비교했습니다. 사실 이 고민은 별로 어렵지 않았습니다. React Admin은 사용법이 엄격하고 opinionated 한데 반해, Refine은 자유도가 높다는 차이점이 있었는데요, 이 때문에 Refine을 선택하게 되었습니다. 운영툴을 개발하다 보면 커스텀 UI에 대한 니즈는 반드시 생기게 마련입니다. 따라서 커스텀 UI 컴포넌트 작성이 자유롭고 여기에 연동 로직을 조합해서 사용할 수 있는 Refine이 장기적으로 봤을 때 더 낫다는 판단을 하게 되었습니다.

 

 

Refine 애플리케이션 구성

App.tsx(App.jsx)의 가장 외부에 Refine 컴포넌트로 지정하면 Refine의 연동 로직 사용 준비는 끝이 납니다.

 

App.tsx

 

Refine이 제공하는 연동 로직들은 Provider의 형태로 제공되는데, 일반적으로 인증이나 API 연동은 기본 Provider를 확장하는 커스텀 Provider를 작성해서 쓰게 됩니다. 커스텀은 기본 Provider의 함수들을 overwrite 하는 형태이기 때문에 크게 복잡하지는 않았습니다.

 

커스텀 인증 Provider의 예시

 

 각 메뉴가 담당하는 개념을 Resource라 하고, 이 Resource를 바탕으로 라우팅 구성 및 화면별 연동할 API(목록/생성/수정 등)를 자동으로 연결해 줍니다. CRUD(Create/Read/Update/Delete) 형태가 아닌 화면은 직접 정의한 routes를 추가하는 것으로 처리할 수 있습니다.

 

Resource는 CMS가 처리하는 대상으로, 프로그램, 에피소드 등을 생성하고 수정하는 단위로 잡게 됩니다. 예를 들어 데이터베이스에서 한 테이블에 관리하는 모델과 동일할 것 같습니다. 이 Resource를 아래와 같이 지정하면 Refine은 좌측 사이드바에 메뉴를 생성하고, 메뉴를 클릭했을 때 주소창의 route을 변경해 주고 지정한 UI 컴포넌트를 노출합니다.

 

Resource의 예시

 

이렇게 Single Page Application을 구성하기 위해 필요한 굵직한 부분들이Provider를 수정하는 것으로 쉽게 처리가 가능하기 때문에, 직접 구성하는 것에 비해 간편하게 개발할 수 있었습니다.

 

 

Refine 화면 컴포넌트

각 화면은 List, Create, Edit, Show 등의 기본 제공 컴포넌트를 이용해서 개발하게 됩니다. 저희는 Refine에서 제작한 Ant Design 라이브러리를 사용해서 기본 제공 Hook과 UI를 손쉽게 연동할 수 있었습니다.

 

Refine에서 제공하는 useTable Hook

 

List 컴포넌트 하위에 UI 컴포넌트 작성 예시

 

여기서는 refine-antd의 Table 컴포넌트를 사용했지만, Hook을 이용하지 않거나 직접 연동을 하는 경우는 Ant Design의 기본 Table 컴포넌트를 사용하는 것도 가능합니다. 이렇게 화면을 구성하고 그 하위 컴포넌트부터는 그냥 평범한 리액트 컴포넌트처럼 개발하면 됩니다. Ant Design 컴포넌트든, 다른 UI 컴포넌트든, 커스텀 컴포넌트든 자유롭게 활용할 수 있습니다. 파일 업로드를 위한 컴포넌트는 기존의 컴포넌트를 활용하기 어려워서 직접 개발해야 했는데, 이때 이런 자유도의 장점을 체감할 수 있었습니다.

 

 

Refine 사용 후기

 과거 프론트 SPA(Single Page Application)를 처음 셋업할 때는 구성할 것도 많고 설정할 것도 많아서 막상 화면 구현에 들어가기까지 시간이 걸리는 경우가 많았습니다. Refine은 이런 초반 구성을 빠르게 작업할 수 있도록 도와주었고, 복잡도도 그리 높지 않아 처음 Refine을 접하는 동료들이 이해하는데도 오래 걸리지 않았습니다.

 

다만, 이렇게 내부 프레임워크에서 많은 작업을 해주는 기술은 간편한 대신에 블랙박스 안의 로직을 다르게 쓰고 싶을 때는 아쉬운 부분 발생할 수 밖에 없었습니다. CRUD 형태가 아닌 API를 호출하는 경우에는 Refine의 Hook이 제한적으로 느껴질 때가 있었는데, 충분한 개발 기간이 주어진 상황에서 CRUD가 아닌 API 호출이 잦다면, 연동 함수를 직접 만들어서 쓰는 게 더 나은 방법일 수도 있습니다.

 

또 한 가지 아쉬운 점은 Refine이 빠르게 버전업이 되고 있다 보니 배포 버전과 문서가 일치하지 않는 경우가 있다는 것입니다. Refine의 문서는 따로 버저닝을 하고 있지 않기 때문에, 문서에서 제공한다고 되어 있는 기능이 저희가 쓰는 버전에 없는 경우가 종종 있었습니다. 그래서 Refine의 github의 Release 탭에서 하나하나 변경 사항을 확인해서 업데이트해야 했습니다.

 

처음에는 신생 프레임워크인데다가 업데이트도 잦다보니 프로덕션에서 사용하기 불안정하지 않을까 하는 우려가 있었지만, 생각보다 안정적이고 완성도가 높았던 것 같습니다. UI 컴포넌트의 일부는 직접 개발해야 직성이 풀리는 개발자의 입장에서, Refine은 Admin 프레임워크의 편리함을 부분적으로 쏙 빼먹으면서 자유도를 지나치게 뺏기지 않는 좋은 툴이라고 생각합니다.

 

 


 

2.  Gee Gee Gee! Apigee

 CMS를 개발하면서 고민됐던 점은 CMS 백엔드 API 호출 시 인증, 권한 관련된 처리를 한 곳에서 하고 싶었습니다. 그래서 GCP에서 사용할 수 있는 APIGateway 서비스를 찾아보게 되었고, 여러 서비스 중에서 Apigee를 선택하게 되었습니다. 저희는 Apigee에서 제공하는 Shared Flows라는 기능 이용하여 VODKA API 요청에 대한 OKTA 기반 인증 처리를 손쉽게 할 수 있었습니다.

 

vodka api Apigee 설정 화면

 

Apigee는 Google에서 제공하는 API Gateway 서비스입니다. Apigee를 사용하면 백엔드 시스템과 클라이언트 애플리케이션 사이에서 API 요청과 응답을 관리할 수 있습니다. Apigee는 다양한 프로토콜과 포맷을 지원하며, 강력한 보안 기능과 모니터링 및 분석 기능을 제공합니다.

 

Apigee 아키텍처

 

이미지에 나와 있는 것처럼 Apigee는 다음과 같은 주요 구성요소로 구성됩니다.

1️⃣ Apigee 서비스 : API 프록시를 생성, 관리, 배포하는 데 사용하는 API입니다.
2️⃣ Apigee 런타임 : Google이 Kubernetes 클러스터에서 유지관리하는 컨테이너화된 런타임 서비스 집합입니다.
모든 API 트래픽이 통과되어 이러한 서비스에 의해 처리됩니다.

 

또한, Apigee는 다음과 같은 다른 구성요소를 사용합니다.

1️⃣ GCP 서비스 : ID 관리, 로깅, 분석, 측정항목, 프로젝트 관리 기능을 제공합니다.
2️⃣ 백엔드 서비스 : 앱에서 API 프록시의 데이터에 대한 런타임 액세스를 제공하기 위해 사용합니다.

 

자세한 설명은 Apigee 구성요소를 참고하시기 바랍니다.

 

 


 

3.  파일은 Conveyor에 싣고

 영상 CMS는 인코딩하기 전의 영상 컨텐츠와 같이 용량이 매우 큰 파일이 있을 수 있기 때문에 대용량 업로드를 고려해서 설계해야 합니다. 대용량 파일을 업로드하는 방법에는 여러가지가 있지만 Tus(the upload server) 프로토콜을 사용하여 업로드 서버를 개발하고 개선하게 된 과정을 소개합니다.

 

Tus(the upload server)?

Tus는 대용량 파일 업로드를 위한 비표준 프로토콜이며, HTTP 기반으로 작동하므로 브라우저를 포함한 다양한 클라이언트에서 쉽게 적용 할 수 있고 확장성이 뛰어납니다. Tus는 청크 기반 전송으로 이어올리기를 지원하므로 대용량 업로드에 적합합니다. 스토리지 벤더마다 제공되는 클라이언트가 있지만, 이러한 클라이언트는 플랫폼 종속적인 부분이 있으며 클라이언트 설치가 필요한 경우가 많아서 오픈소스 프로토콜인 Tus를 사용하기로 결정했습니다. 또한, GCS(Google Cloud Storage)나 NFS(Network File System) 외에 다른 스토리지가 필요할 때도 단일 진입점으로 Tus를 사용할 수 있습니다.

 
 
tus.io

 

위와 같이 Restful API 형태를 띠고 고유한 Signed URL과 offset 관리를 통해서 안정적인 업로드를 구현합니다. 요약하면 큰 파일을 작게 쪼개서 업로드하고, 업로드 진행상황을 관리하는 규약이라고 보면 됩니다. 여기에 추가로 병렬 업로드, 체크섬, 중단 등의 선택적 확장 정의가 있습니다. 이를 구현한 구현체는 여러 클라이언트와 언어로 구현되어 있는데 목록은 여기서 확인 가능합니다.

 

 

서버간 업로드 방식에서 SignedUrl 방식으로

서비스 백엔드에서 업로드를 수신하여 Tus 서버로 전송하게 되면 그만큼 시간이 걸리게 되고, 요청 버퍼를 사용하게 되면 메모리 문제도 발생하게 됩니다. 그래서 서비스 백엔드에서 필요한 비즈니스 로직이 있는 경우, 전처리 후에 Tus에 업로드 할 수 있는 url을 요청하고 이후에는 서비스 프론트엔드와 Tus가 직접 통신하는 SignedUrl 방식으로 변경하였습니다. SignedUrl 은 이미 인증이 완료된 url 이기 때문에 ttl을 필수로 적용하고, 유출에 주의할 필요가 있습니다.

 

초기 작성했던 요청 흐름

 

서버간의 인증 이후에 업로드 경로가 고정된 SignedURL을 발급받고, 이후로는 SignedURL로 Tus와 바로 통신하기 때문에 위와 같은 구조가 되었습니다. Tus는 전송에 대한 규약이라서 일반적으로는 디스크에 쓰는 걸 기준으로 하고 있습니다. 디스크에 쓰고 GCS와 같은 오브젝트 스토리지로 옮기게 되면 저장소가 추가로 필요하고 업로드 시간이 두 배가 걸리기 때문에 GCS와 같은 오브젝트 스토리지로 바로 저장하는 방향으로 개선하였습니다.

 

 

로컬 파일 lock 방식에서 redis lock으로

Tus는 이어올리기와 동시 업로드를 지원하기 때문에 업로드 충돌을 막는 lock 구현은 필수입니다. 가장 간단한 구현으로는 업로드 서버 파일 시스템에 SignedUrl(UUID)로 파일을 생성해서 lock 상태를 나타내고, 파일이 없으면 lock이 해제된 것으로 처리하는 방식이 있습니다. 하지만 이 방식은 로컬 lock이기 때문에 여러 서버를 운영하는 분산 환경에서는 사용할 수 없어서 redis를 이용한 분산lock을 구현했습니다. 구현 흐름은 파일시스템 락과 동일합니다. 

1️⃣ SETNX 명령어를 이용해서 lock을 획득합니다. redis에 'lock:{uuid}' 라는 키가 존재하지 않을때(NX- not exist)만 키를 생성(SET)하고, 생성에 실패하면 이미 다른 곳에서 lock을 획득한 것으로 판단합니다.
2️⃣ 작업이 끝나면 lock을 위해 생성한 키를 삭제합니다.

 

위의 단순한 흐름에 잠금 해제가 실패한 경우나 교착상태(deadlock)를 해제하기 위해 일정시간이 지나면 자동으로 키를 삭제(ttl)하는 부분이 추가되었습니다. 추후 redis clustering 방식으로 구성을 변경하게 되면 분산 방식인 redlock으로 개선할 예정입니다. 

 

 

자바에서 golang 으로

 처음부터 모든 확장을 지원할 예정은 아니어서 필수 기능만 자바를 사용하여 구현한 상태였습니다. 그리고 인하우스 kubernetes에서 GCP로 플랫폼을 이전하게 되면서 컨테이너를 올릴 기술을 결정해야 했습니다. 파일 업로드라는 기능이 연속적인 작업이기보다는 특정 시간대에 일어나는 작업이어서 GCP Cloud run을 쓰게 되었고 가볍고 cold start가 짧은 언어로 golang을 채택하였습니다. 다른 서비스 백엔드를 golang으로 작업하기 시작한 이유도 크게 작용했습니다.

 

Cloudrun 에서 다시 kubernetes 로...

 

Cloud run에서는 http/1 요청 크기가 32mb로 제한이 되어 있습니다. 청크를 업로드 할 때마다 lock의 잠금 및 해제가 발생하고 http 자체의 요청, 응답에 대한 지연이 발생하기 때문에 너무 작은 크기는 그만큼 업로드 시간이 길어지는 결과를 주었습니다. 청크 단위로 업로드 하는 특성상 스토리지에도 청크 파일 단위로 업로드 되고 마지막에 단일 파일로 병합하고 청크 파일을 삭제하는 작업이 발생하게 됩니다. 원본 영상과 같이 용량이 큰 경우는 수백개의 청크 파일이 발생하기 때문에 파일 병합과 같은 후처리가 상당히 부담되는 상황이 발생합니다.

 

Cloud run spec상 http/2에서는 요청 크기에 대한 제한이 없지만 서비스단에서 h2c를 지원해줘야 합니다. 또, 속도를 위해서 golang fiber를 썼지만, fiber의 core인 fasthttp 모듈에서 아직 http/2를 지원하지 않아 http/1 환경에서 요청 크기를 늘릴 수 있는 GCP GKE(kubernestes)로 옮기는 방법으로 결정하였습니다.

 

 

버퍼에서 스트리밍으로

 파일 전송 시 전체 요청 body를 버퍼에 저장한 후 한 번에 파일에 기록하는 방식은 대용량 파일은 비효율적입니다. 따라서 파일을 작은 청크로 나누어 전송하고, 서버에서 이를 연속적으로 기록하는 스트리밍 업로드 방식을 사용합니다. 이를 위해 golang의 Fiber 프레임워크에서는 SteramRequestBody: true 옵션을 설정해야 하며, Kubernetes Ingress (Nginx)에서는 nginx.ingress.kubernetes.io/proxy-request-buffering: "off" 어노테이션을 사용하여 버퍼링을 꺼주어야 합니다. 이러한 방식을 통해 대용량 파일 전송 시 효율성을 높일 수 있습니다.

 

 

internal LB에서 external LB로

 초기에는 업로드 서비스를 GKE의 internal ingress를 통해 사내 VPN으로만 접근이 가능한 형태로 노출했습니다. 하지만 대용량 파일을 업로드할 때 속도가 너무 느리다는 문제가 발생했습니다. 자세한 기록이 남아있진 않지만, 속도 차이는 대략적으로 아래와 같습니다.

◼️ internal ingress w/ ssl : 1 (기준)
◼️ internal ingress w/o ssl : 4
◼️ external ingress w/ ssl: 10

 

위처럼 초기 internal ingress를 통해서 업로드를 했을때는 이미지와 같은 작은 파일은 차이를 못 느꼈지만 큰 파일을 올릴 때는 사용하기 어려운 수준이었습니다. 개선이 반드시 필요한 사항이어서 테스트를 진행하게 되었습니다. 여러번 테스트를 통해서 internal LB, external LB 차이 때문에 속도 차이가 난다는 걸 알았고 가장 큰 차이는 VPN을 통한 접근 유/무라고 판단했습니다. 즉, internal LB는 일반적으로 GCP 내부 서비스의 부하 분산 용도로 사용되는데, VPN 터널링으로 서비스에 접근하는 경우 VPN에 의한 지연 때문에 성능 저하가 발생하는 것으로 내부적으로 판단하게 되었습니다.

현재는 external ingress로 변경하고, cloud armor를 통한 접근 제어를 적용하여 쾌적한 업로드 환경을 제공하고 있습니다.

 

 

gcs api 에서 redis 캐시로

Tus는 청크 단위로 업로드 파일이 생성되는 구조여서 마지막에 청크 파일들을 하나의 단일 파일로 만들어주는 과정이 필요합니다. 스토리지 벤더마다 다르겠지만 GCS에는 compose 명령어로 여러 파일을 합쳐서 단일파일로 만들어줍니다. compose는 한 번의 명령에 32개의 청크를 넣을 수 있는 제약이 있어서 수백 개의 청크가 발생한 경우는 각 청크의 정보를 GCS에서 읽어오고 합치는 과정에서 수 분 이상이 걸리는 경우가 발생합니다. Connection timeout을 늘리면 되지만 응답이 길어질수록 클라이언트와 통신하는데 문제가 발생할 가능성이 높고, compose 과정에서 문제가 발생한 경우는 처리하기가 까다롭기 때문에 최대한 빨리 완료될 수 있도록 개선할 필요가 있었습니다. 또한, 공개된 코드에서는 업로드할때마다 GCS를 통해서 object 정보를 읽어와서 청크 개수가 많아질수록 점점 느려지는 현상도 발생합니다.

 

우선은 청크 파일을 합치는 과정을 병렬 진행하는 걸로 개선하고, 각 청크 파일의 정보를 redis 에 캐싱하여 매번 GCS에서 object를 읽어오지 않도록 수정했습니다. 이를 통해 청크가 많아질수록 점점 느려지는 현상과 단일 파일로 병합하는 과정의 속도를 대폭 개선할 수 있었습니다.

 

 

Conveyor

마지막으로 서비스 이름은 Conveyor로 결정했습니다. 파일 업로드 과정이 큰 컨테이너에서 작은 박스를 나눠 벨트에 올리는 것과 유사한 느낌을 주기 때문입니다. Tus의 규약 중에 일부 필수 기능만을 구현한 것이어서 아직 구현할 부분이 남아 있습니다. 파일 업로드 도중에 관리자가 바로 끊을 수 있는 기능이나, 업로드 현황을 보여주는 대시보드, 구조상 클라이언트와 Tus 서버만 알 수 있는 진행사항을 VODKA(CMS) 백엔드(혹은 서드파티 서비스)에 이벤트로 알려주는 것 등을 앞으로 개선할 예정입니다.

 

 


 

 

여기까지 영상서비스 어드민, VODKA에 대해 설명 드렸는데요.

다음 편에서는 영상서비스 인코더 및 라이브 서비스에 대해 소개하도록 하겠습니다.

 

감사합니다. 

 

 

 

▶️ 우당탕탕~ 영상 서비스 개발기 2탄 : 인코더와 라이브 서비스 

 

우당탕탕~ 영상 서비스 개발기 2탄 : 인코더와 라이브 서비스

앞서 영상 서비스 어드민, VODKA에 관한 내용을 다뤘는데요. 이번 편에서는 영상 서비스 인코더와 라이브 서비스에 대해 다뤄보고자 합니다. 아직 '우당탕탕~ 영상 서비스 개발기 1탄 : 어드민, VODK

kakaoentertainment-tech.tistory.com

 

📷 photo by. Selian