LINE 트랜스코딩 서버 아키텍처 개선기 – 2

들어가며

안녕하세요. LINE에서 트랜스코딩(Transcoding) 서버 개발과 운영 업무를 담당하고 있는 백승훈입니다. 1편에서 LINE의 트랜스코딩 서버인 ‘리코더(Licoder, LIne TransCODER)’를 소개하고 그 기능과 인터페이스에 대해 말씀드렸는데요. 이번 글에서는 리코더의 초기 아키텍처를 설명하고 이 아키텍처가 LINE의 급격한 성장에 따라 어떻게 변화했는지 공유하겠습니다.

 

초기 리코더의 아키텍처

초기 리코더는 크게 3가지 모듈로 분류했습니다. 사용자의 요청을 받아주는 API 서버와 트랜스코딩을 수행하고 사용자에게 알림(notify)를 전달하는 워커(worker) 서버, 그리고 각 서버 간 메시지 전달과 메시지 큐 역할을 담당하는 RabbitMQ 서버입니다. 아래는 초기 리코더의 아키텍처를 나타낸 그림입니다. 어드민(admin) 서버는 리코더에서 처리한 내용을 일정 기간동안 보관하고, 실패한 요청을 분석할 때 주로 사용하고 있습니다.

사용자는 동영상 파일이 저장된 경로와 원하는 품질 정보를 담아 리코더로 HTTP 요청을 보냅니다. 요청을 받은 API 서버는 검증 과정을 거쳐 RabbitMQ의 요청 큐에 메시지를 넣습니다. 워커 서버는 요청 큐에서 메시지를 가져와 필요한 작업을 수행하는데요. 메시지에 입력된 커맨드와 리코더의 커맨드 파이프라인을 사용하여 사용자가 원하는 파일을 생성합니다. 파일 생성이 완료되면 사용자가 지정한 경로에 파일을 저장하고, 작업 완료 메시지를 알림 큐에 넣습니다. 워커 서버는 알림 큐에서 메시지를 읽어 사용자에게 작업이 완료되었음을 알려줍니다. 만약 사용자가 특별한 이유로 완료 메시지를 받을 수 없는 상황이라면 알림을 보장하기 위해 알림 재처리 과정을 진행합니다. 알림 재처리 과정은, 전달에 실패한 메시지를 DLQ(Dead-Letter Queue)에 보관하였다가 일정 시간(TTL, Time-To-Live) 이후 꺼내어 사용자에게 알려주고, 만약 또 전달에 실패하더라도 다시 동일한 과정을 진행해 사용자가 메시지를 받을 수 있도록 보장합니다.

리코더는 사용자의 요청을 처리할 때 동영상의 품질을 최적으로 유지하면서 트랜스코딩 프로세스를 효율적으로 진행하기 위해 다음과 같이 처리합니다.

 

인코딩 타입 분류

일반적으로 동영상 파일은 데이터의 양을 줄이기 위해 손실 압축 방식으로 인코딩됩니다. 그런데 원본 데이터를 손실 압축 방식으로 압축한 후 복원하면, 처음의 원본 데이터와 완전히 동일하지 않고 일부 데이터가 손실되는 특성이 있어서 손실 압축 방식으로 생성된 동영상 파일을 다시 인코딩하면 동영상의 화질이 점점 열화하는 현상(Generation Loss)이 발생합니다. 그래서 리코더는 재인코딩으로 발생하는 화질 열화 현상을 방지하고, 불필요한 트랜스코딩 작업을 최소화하기 위해 사용자의 요청을 인코딩 타입으로 분류하여 처리하고 있습니다.

리코더에서 사용자의 요청을 인코딩 타입으로 분류하는 과정을 살펴보면, 앞서 소개된 워커 서버의 트랜스코더에서 Pre-Transcoding 과정을 통해 미디어를 분석하여 적절한 인코딩 타입을 알아내고 있습니다. 아래는 워커 서버에서 수행하는 Pre-Transcoding 과정을 자세히 설명한 그림입니다.

먼저 원격 저장소의 동영상 파일을 로컬 시스템에 저장한 뒤 동영상 파일을 분석합니다. 동영상 파일에서 비디오의 비트 레이트, 초당 프레임 수, 해상도, 코덱의 종류와 같은 정보를 추출하고 오디오의 비트 레이트와 코덱, 샘플 레이트, 채널 수 등 품질과 관련된 항목들을 추출합니다. 이후 파일에서 추출한 품질 정보와 사용자가 원하는 품질 요구 사항을 비교하여 적절한 처리 방법을 결정합니다. 아래는 위 과정을 거쳐 분류된 인코딩 타입의 종류입니다.

  • Reformatting: 동영상 파일의 컨테이너 부분만 수정이 필요한 경우 컨테이너 부분만 수정
  • Audio Transcoding: 오디오 스트림의 트랜스코딩이 필요한 경우 오디오 스트림만 트랜스코딩
  • Video Transcoding: 비디오 스트림의 트랜스코딩이 필요한 경우 비디오 스트림만 트랜스코딩
  • Transcoding: 오디오와 비디오 둘 다 트랜스코딩이 필요한 경우 오디오, 비디오 스트림을 트랜스코딩

이렇게 조건에 따라 트랜스코딩 방식을 나누면, 불필요한 작업을 줄일 수 있으므로 사용자의 요청을 보다 신속히 처리할 수 있으며, 사용자가 업로드한 동영상을 최대한 그대로 사용하기 때문에 재인코딩으로 인한 동영상 품질 저하를 최소화할 수 있습니다.

 

커맨드 최적화

비디오 해상도를 업 스케일링(up-scaling)하면 원본 동영상의 품질에 비해 좋아지지 않는 현상이 있습니다. 일반적인 업 스케일링 과정을 살펴보면, 기존 비디오 프레임에 있던 픽셀 사이에 빈 픽셀을 추가하고, 기존에 존재하던 픽셀의 색상을 참고하여 빈 픽셀의 색상을 채워 넣습니다. 결과적으로 기존의 픽셀을 쭉 늘여서 해상도를 키우는 결과가 되기 때문에 업 스케일링된 동영상의 품질은 원본 동영상의 품질에 비해 좋아지지 않습니다. 특히 저해상도 동영상을 고해상도 동영상으로 트랜스코딩하는 경우에 빈번히 발생하는 현상이며, 리코더에서는 불필요한 업 스케일링 현상을 방지하기 위해 커맨드 최적화 옵션을 제공하고 있습니다.

리코더가 제공하는 커맨드 최적화 옵션은, 사용자가 하나의 입력 동영상을 여러 화질로 인코딩하는 경우 유용하게 사용할 수 있습니다. 예를 들어 사용자가 자신이 업로드한 동영상을 720p나 480p, 혹은 360p로 인코딩하길 원한다고 가정해 보겠습니다. 이때 원본 동영상의 해상도가 360p라면 720p나 480p로 트랜스코딩하는 것은 화질의 변화 없이 동영상 파일의 크기만 크게 할 뿐이므로 불필요한 작업이라고 생각할 수 있습니다. 이럴 때 커맨드 최적화 옵션을 사용하면 720p나 480p에 대한 트랜스코딩 작업을 수행하지 않고 360p의 트랜스코딩만 수행함으로써, 처리 시간을 절약하고 저장소의 저장 공간을 아낄 수 있습니다. 리코더는 사용자에게 작업 완료 메시지를 보낼 때 720p나 480p로 트랜스코딩하는 작업은 360p로 트랜스코딩하는 작업으로 대체되었다는 내용을 전달합니다. 사용자는 이 정보를 참고하여 대체된 동영상에 접근할 수 있습니다. 사용자가 업로드한 동영상의 재생 길이가 길고, 파일 크기가 크지만 저화질인 경우 이 옵션을 유용하게 사용할 수 있습니다. 아래 그림은 커맨드 최적화 옵션을 적용했을 때의 작업 흐름입니다.

 

리코더 아키텍처의 변화

리코더는 LINE에서 동영상이 필요한 모든 서비스의 트래픽을 처리하고 있는 플랫폼입니다. LINE 메신저가 다양한 국가에서 급성장하면서 리코더에 변화가 필요한 시기가 찾아왔습니다. 많은 사람들이 자신의 스마트폰에 있는 동영상을 LINE 메신저를 통해 공유했는데요. 당시에는 LINE 메신저에 동영상을 업로드하면 몇몇 서버를 거쳐 리코더로 트랜스코딩 요청이 이어졌습니다. 사용자가 동영상을 업로드하기 전 클라이언트 쪽의 트랜스코딩 기능이 없던 시절이었기에 많은 양의 트래픽을 감당했어야 했고, 그만큼 많은 서버가 필요한 시기였습니다.

 

인코딩 타입 추가 – COPY

스마트폰으로 촬영한 동영상을 업로드하는 경우엔 LINE에서 허용하는 품질보다 고화질인 경우가 대다수였고, 같은 장면을 촬영하더라도 스마트폰 제조사마다 동영상 파일의 구조나 특성이 조금씩 달랐습니다. 그래서 리코더는 모든 동영상 파일을 LINE에서 정의한 품질에 맞게 일괄적으로 트랜스코딩하고 있었습니다. 시간이 흘러 모바일 기기가 점차 고도화되어 모바일에서 트랜스코딩 기능을 사용할 수 있게 되었고, LINE 모바일에서도 모바일 트랜스코딩을 할 수 있게 되었습니다. 사용자의 동영상은 모바일 트랜스코더를 거쳐 리코더로 업로드됐고, 이렇게 업로드된 동영상의 품질은 LINE에서 허용하는 품질에 적합한 경우가 많아졌습니다. 앞서 설명한 Pre-Transcoding 과정에서 동영상 파일의 품질을 비교해 변환이 필요 없다고 판단되면 사용자가 업로드한 동영상 파일을 그대로 사용하는데요. 이것을 리코더에서는 ‘COPY 방식’이라고 부르고 있습니다. 

아래 그래프는 최근 리코더에서 처리한 트래픽을 인코딩 타입으로 분류한 결과입니다.

위 그림처럼 COPY로 처리된 것이 가장 많은 것을 볼 수 있습니다. 그 외 나머지 인코딩 타입은 모바일 트랜스코더를 사용했음에도 LINE의 품질 조건을 만족하지 못했거나, 클라이언트 인코딩이 지원되지 않는 LINE의 여러 패밀리 서비스들의 요청을 처리하는 경우에 해당합니다. COPY가 아닌 인코딩 타입은 COPY에 비해 그 수는 적지만, 고품질 영상을 처리하거나 재생 시간이 긴 동영상을 처리하며 리코더가 제공하는 기능을 다양하게 사용하고 있기 때문에 리소스를 많이 사용하고 있습니다. COPY 방식을 통해 절약한 리소스는 새로 시작하는 비디오 중심의 서비스를 위해 사용하고 있습니다.

 

워커 서버 세분화

리코더의 워커 서버는 RabbitMQ의 요청 큐에서 메시지를 읽어 필요한 작업을 수행합니다. 그런데 요청 큐에서 메시지를 읽어 온 워커 서버는 커맨드에 어떤 작업이 입력되어 있는지, 그 작업이 얼마나 많은 시스템 리소스를 필요로 하는지 알 수 없는 문제가 있었습니다. 그래서 워커 서버는 요청 큐에서 한 번에 몇 개의 메시지를 가져오는 게 시스템의 리소스를 잘 사용하는 건지 알기 힘든 문제가 있었습니다. 메시지를 너무 적게 들고 오면 사용자의 요청이 빠르게 처리되지 않아 지연되는 문제가 발생하고, 반대로 너무 많이 들고 오면 시스템 리소스가 부족해 처리 속도가 오히려 느려지는 문제가 발생합니다. 여러 운영 경험을 바탕으로 일정 개수를 정했지만, 시스템의 리소스를 십분 사용하지는 못했습니다. 아래 그림은 시스템의 리소스를 제대로 활용하지 못한 예시입니다. 

위 그림은 워커 서버가 요청 큐에서 6개의 메시지를 읽어 분석했을 때 각 메시지가 어떤 방식으로 처리되어야 하는지를 보여주고 있습니다. 해당 작업들을 모두 처리하기 위해 필요한 시스템 리소스를 예시로 표현했습니다. 동일한 수의 메시지를 읽어 왔지만 메시지에 어떤 작업이 필요한지 알 수 없어 작업 로드가 분산되지 못한 것을 볼 수 있습니다. 또한 앞서 말씀드린 ‘대표 장면 추출’ 기능이 필요한 요청은 다른 요청보다 빠르게 처리할 필요가 있었는데요. 리코더를 사용하는 LINE 미디어 서버와 여러 서버의 구조 때문에 리코더에서 장면 추출이 지연되면 다른 서버들의 부하가 급증하는 현상이 발생하여 리코더의 장면 추출이 지연되지 않도록 리코더 구조를 개선할 필요가 있었습니다.

이제 서버의 리소스를 잘 활용하고, 장면 추출을 빠르게 처리하기 위해 내부 구조를 어떻게 변경하였는지 아래 그림과 함께 설명하겠습니다.

기존의 워커 서버는 총 4개의 클러스터로 분리했습니다. 장면 추출만을 담당하는 ‘Scene 클러스터’, 시스템 리소스가 많이 필요한 요청(인코딩 타입이 비디오 트랜스코딩 혹은 트랜스코딩)을 처리하는 ‘Encoding 클러스터’, 사용자에게 작업 완료를 알려주는 ‘Notify 클러스터’, 그리고 ‘Probe 클러스터’입니다. Probe 클러스터는 사용자가 요청한 커맨드를 분석해 시스템 리소스가 많이 필요한 요청들은 Encoding 클러스터에서 처리할 수 있도록 커맨드를 핸드오버(handover) 큐로 전달하고, 시스템 리소스가 적게 필요한 요청은 직접 처리하는 클러스터입니다. 기존의 API 서버는 단순히 사용자의 요청을 받아 요청 큐에 입력하는 역할을 했는데요. 변경된 서버 구조에서는 사용자의 요청 중 장면 추출 기능이 필요한 커맨드를 따로 분리하여 Scene 클러스터로 전달하도록 변경했습니다.

기존의 요청 큐가 Scene 큐와 핸드오버 큐로 세분화되면서, 큐에 저장된 메시지를 처리하기 위한 부하가 균일해졌습니다. 각 클러스터에서 동시에 처리 가능한 메시지의 수를 조정하는 방식으로 로드 밸런싱이 가능해졌습니다. 시스템 리소스를 최대한 활용할 수 있게 되었고, 장면 추출을 전담하는 클러스터가 있어서 처리가 지연될 가능성이 낮아져 서버 운영 리스크가 줄어든 효과가 있습니다. 이렇게 워커 서버를 클러스터 단위로 나누면서, 클러스터를 어떻게 관리하면 좋을지 고민하게 되었습니다. 예를 들어 장면 추출 요청이 갑작스레 증가해 현재 운영 중인 Scene 클러스터의 서버 증설이 필요한 상황이라면, Encoding 클러스터의 일부 서버를 Scene 클러스터로 투입할 수 있어야 했습니다.

이를 위해 리코더는 서버의 설정값이 저장되어 있는 ‘롤(role)’이라는 것을 정의해 사용하고 있습니다. 이 롤의 설정값을 변경하는 방식으로 클러스터와 서버 장비를 관리합니다. 서버의 롤 정보는 Apache Zookeeper의 노드에 저장하고, 리코더의 관리 API를 사용하여 서버의 롤 정보를 업데이트합니다. 롤은 어떤 클러스터로 동작할지와 트랜스코딩과 관련된 세부 설정을 저장하는데요. 롤 설정과 리코더의 관리 API를 좀 더 쉽게 사용할 수 있도록 아래와 같이 어드민 서버에 대시보드 페이지를 개발했습니다.

대시보드 페이지에선 모든 서버의 현재 상태를 실시간으로 확인할 수 있고, 서버의 롤을 변경할 수 있는 UI가 제공되며, 필요하다면 새로운 롤을 정의해 적용할 수 있는 기능을 제공합니다. 대시보드 페이지를 통해 리코더로 유입되는 트래픽에 빠르게 대응할 수 있고, 긴급 상황에서는 모바일로 서버의 상태를 쉽게 변경할 수 있어서 운영할 때 큰 도움이 됩니다.

 

하드웨어 트랜스코더 도입

워커 서버를 세분화하여 장비의 효율을 높였지만, Encoding 클러스터에 상당히 많은 수의 서버가 있는 상태였습니다. 다수의 서버를 운영하기 위해 적지 않은 시간이 필요했고, 리코더를 사용하는 LINE 서비스와 고품질 영상의 트랜스코딩 요청이 조금씩 증가하는 상황이었습니다. 관리하는 서버 수가 많아지면 그만큼 운영에 소비되는 시간이 증가하기 때문에 서버를 효율적으로 운영할 필요가 있었습니다. 이에 많은 논의를 거치며 하드웨어 트랜스코더 도입을 검토했고, GPU에 탑재된 하드웨어 코덱으로 트랜스코딩 가속화 기능을 사용하게 되었습니다.

리코더는 동영상 트랜스코딩 외에 앞서 설명한 여러 기능을 제공하는데요. 그중 일부 기능을 GPU에서 지원하는 2D 엔진으로 대체할 수 있었고, H/W 코덱과 함께 트랜스코딩 가속을 할 수 있었습니다. 아직 GPU를 사용한 트랜스코딩 사례가 많지 않았기 때문에 리코더에 적용할 때 서버가 다운되거나 인코딩에 실패, 혹은 비디오와 오디오의 싱크가 맞지 않는 등 여러 시행 착오를 거쳤습니다. 결론적으로 GPU가 탑재된 장비는 일반 서버 장비에 비해 대략 7배 정도 더 좋은 성능을 보여 주었고, 기존의 서버 수를 80% 이상 절감할 수 있었습니다. 또한 IDC 내 상면 공간 또한 크게 절약할 수 있어서 비용을 크게 절감할 수 있었습니다.

 

GPU 리소스 공유

리코더는 연말에 발생하는 트래픽 급증과 여러 국가에서 열리는 행사 시기에 발생하는 트래픽에 대비하기 위해 평일 트래픽의 3배 정도를 처리할 수 있는 서버를 준비하고, 운영하고 있습니다. 평소에는 대부분의 서버가 여유로운 상태인데요. GPU가 탑재된 서버는 일반 서버에 비해 상당히 고가이기 때문에 장비를 좀 더 효율적으로 운영할 필요가 있었습니다. 저희는 트래픽 급증에 대비하기 위해 운영하고 있는 GPU 서버들을 좀 더 유연하게 사용하고 싶었고, Antman 프로젝트에서는 GPU가 탑재된 서버가 필요한 시점이었습니다(참고). 그래서 리코더에 유입되는 트래픽이 적을 때는 Antman 프로젝트에서 GPU 서버를 사용하도록 하고, 반대 상황에서는 리코더가 GPU 서버를 사용하면 좋겠다고 생각했습니다.

GPU 서버를 사용하는 프로젝트의 밸런스를 조절하려고 할 때, 어드민 서버에서 서버 밸런싱을 담당하는 모듈이 떠올랐습니다. 아래 그림에서 ‘Instance Changer’라는 부분이 서버 밸런싱을 담당하고 있는데요. 앞서 설명한 것처럼 하드웨어 트랜스코더를 도입했기 때문에 트랜스코딩 가속을 할 수 있었습니다. 

리코더로 유입되는 트래픽의 양을 확인하기 위해 Collector에서 RabbitMQ의 Ready와 Publish Rate 값을 수집했습니다. Predictor에서는 최근 수집된 2~3건의 평균 값을 계산하여 리코더를 활성화(=Antman 비활성화)하고, 최근 수집된 10~20건 정도의 평균 값을 계산하여 리코더를 비활성화(=Antman 활성화)하고 있습니다. 수집된 큐의 정보를 상황에 따라 다른 크기로 확인하는 이유는, 트랜스코딩 요청이 많이 유입되는 상황에선 GPU 서버를 재빨리 리코더용으로 사용할 수 있게 하고, 리코더의 트래픽 유입이 적은 상황에서는 시간을 두고 천천히 GPU 서버를 Antman으로 넘기기 위해서입니다. 만약 리코더의 트래픽 유입이 적은 상황에서 한꺼번에 GPU 서버를 Antman이 사용하도록 변경한다면, 역할을 변경한 후 얼마 지나지 않아 다시 리코더로 전환해야 하는 상황이 빈번하게 발생하게 됩니다. 이에 따라 GPU 서버의 전환 방식을 상황에 따라 다르게 처리했습니다.

아래 그래프는 전체 GPU 서버를 100%로 보았을 때, GPU 서버의 역할이 시간별로 어떻게 변화하는지 나타낸 그래프입니다. 파란색 부분이 리코더가 동작한 것을 의미합니다. 

LINE 사용자들이 동영상을 업로드하는 패턴을 살펴보면, 사용자들이 가장 활동을 많이 하는 오후 시간대부터 동영상 업로드 양이 점차 증가하다가 오후 10시에 정점에 이르고, 이후 새벽이 지나 아침이 올 때까지 소강상태를 보입니다. 이 그래프를 보며 값비싼 GPU 장비를 낭비하지 않고 잘 사용하고 있다는 것을 알게 되어 뿌듯했습니다.  🙂

 

마치며

이번 글에서는 리코더가 동영상 트랜스코딩 요청을 효율적으로 처리하기 위해 내부적으로 어떤 변화를 거쳤는지 이야기해 보았습니다. 처음에는 단순한 모델로 시작했지만, 부족한 부분을 개선하고 여러 테스트를 거쳐 현재 구조에 이르렀고, 현재도 LINE의 여러 서비스에 다양한 동영상 기술을 제공하기 위해 꾸준히 노력하고 있습니다.

LINE의 모든 동영상 트래픽을 처리하고 있다고 생각하면 마음이 뿌듯하지만, 트랜스코딩은 잘못되었을 경우에 그 영향력이 크기 때문에 한편으론 배포와 소스 코드 변경에 막중한 책임감을 느끼고 있습니다. 앞으로 모든 LINE 사용자가 문제없이 동영상을 즐기며 여러 기술을 통해 다양한 경험을 할 수 있도록 프로젝트를 발전시켜 나가고 싶습니다.

마지막으로 저희는 전 세계 사용자들의 동영상을 처리하고 기술 개발에 함께하고 싶은 분들을 찾고 있습니다. 저희와 함께 하시면 글로벌 규모의 서버사이드 개발과 운영, 동영상 외의 미디어 관련 기술 개발을 경험할 수 있습니다. 관심 있는 분들은 적극적으로 지원해 주세요!

Related Post