[실전 미디어 01] 왜 미디어 파이프라인을 직접 만들었나

내가 직접 만든 커뮤니티 사이트에서 가장 오래 붙잡고 있던 영역이 미디어 처리였다. 글 하나에 사진 열 장, 세로로 긴 캡처, 아이폰으로 찍은 동영상이 뒤섞여 올라오는데, 이걸 그냥 원본 그대로 저장하고 보여주면 되는 줄 알았다. 그런데 실제로 운영을 시작하니 저장 용량, 로딩 속도, 브라우저별 호환성, 검색 노출까지 전부 미디어에서 발목이 잡혔다. 이 시리즈는 내가 업로드부터 변환, 저장, 서빙까지 하나씩 다시 설계하며 배운 것을 있는 그대로 정리한 기록이다. 화려한 정답을 늘어놓는 글이 아니라, 혼자 서버리스 스택 위에서 부딪히며 깨진 부분을 하나씩 메워 나간 과정을 담았다. 같은 제약 안에서 미디어를 다루는 사람이라면 내가 밟은 지뢰를 미리 피할 수 있길 바란다.


1. 원본을 그대로 서빙하면 벌어지는 일

처음 만든 버전은 단순했다. 사용자가 파일을 고르면 서버로 보내고, 스토리지에 그대로 넣고, 글 본문에는 그 주소를 박아 넣었다. 데모로 사진 몇 장 올릴 때는 완벽하게 동작했다. 문제는 진짜 사용자가 들어오면서 시작됐다. 요즘 스마트폰 사진 한 장이 4메가에서 8메가, 아이폰 동영상은 수십 메가를 우습게 넘겼다. 글 하나가 백 메가에 육박하기 시작했고, 스토리지 사용량 그래프가 예상보다 훨씬 가파르게 올라갔다.


용량만 문제가 아니었다. 목록 화면에서 썸네일 대신 원본 사진을 화면에서만 줄여서 보여주고 있었는데, 브라우저는 8메가짜리 원본을 그대로 내려받은 다음 작게 그렸다. 사용자 눈에는 작은 썸네일이지만 실제로는 매번 원본 전체가 회선을 타고 내려왔다. 모바일 사용자가 목록 한 번 넘길 때마다 수십 메가가 소모됐다. 데이터 요금과 로딩 시간을 동시에 낭비하는 최악의 구조였다.


결정적으로, 원본을 그대로 두면 포맷이 제각각이었다. 어떤 건 JPEG, 어떤 건 PNG, 아이폰에서 온 건 HEIC, 동영상은 HEVC 코덱이었다. 이 중 일부는 특정 브라우저에서 아예 열리지 않았다. 사용자는 자기 폰에서 잘 보이니 정상인 줄 알고 올렸는데, 다른 사람 화면에서는 깨진 이미지 아이콘만 떴다. 원본을 존중한다는 순진한 방침이 오히려 사용자 경험을 망치고 있었던 것이다.


이 세 가지 증상, 즉 용량 폭증과 대역폭 낭비와 포맷 파편화는 사실 하나의 뿌리에서 나온 문제였다. 업로드된 파일을 손대지 않고 그대로 저장한다는 결정 자체가 원인이었다. 나는 이 결정을 뒤집기로 했다. 사용자가 올린 파일은 입력일 뿐이고, 실제로 저장하고 서빙하는 것은 내가 통제하는 표준 산출물이어야 한다는 쪽으로 방향을 잡았다.


2. 업계는 원본을 서빙하지 않는다

방향을 잡고 나서 큰 서비스들이 어떻게 하는지 조사해봤다. 결론은 명확했다. 원본을 그대로 서빙하는 큰 사이트는 없었다. 거의 전부가 업로드 순간에 표준 포맷으로 변환하고, 표시 크기에 맞게 여러 벌을 만들어 두고, 원본은 폐기하거나 값싼 보관 계층으로 내려버렸다. 사용자가 올린 파일과 실제로 서빙되는 파일은 완전히 다른 물건이었다.


이미지는 대체로 최신 포맷으로 일괄 변환하고, 화면 크기별로 여러 해상도를 만들어 둔다. 동영상은 업로드 즉시 서버에서 재인코딩해 해상도와 비트레이트에 상한을 걸었다. 원본이 4K에 고비트레이트여도 표준 해상도의 적당한 비트레이트로 눌러버린다. 규모가 아주 큰 곳만 긴 영상에 적응형 스트리밍을 붙이고, 짧은 영상은 그냥 프로그레시브로 내려주는 식이었다.


작은 서비스일수록 아예 직접 호스팅을 포기하고 외부 영상 플랫폼 임베드로 저장과 대역폭을 통째로 떠넘기는 경우도 많았다. 나는 커뮤니티 특성상 사용자가 올린 짧은 짤과 사진이 대부분이라 임베드로 떠넘기긴 어려웠다. 대신 큰 서비스의 발상, 즉 업로드 순간에 표준화하고 표시용을 따로 만든다는 원칙만큼은 그대로 가져오기로 했다.


흥미로웠던 건 이 원칙이 규모와 무관하게 통한다는 점이었다. 하루 수백만 장을 처리하는 곳이든, 나처럼 하루 수백 장 규모든, 원본을 신뢰하지 않고 내가 만든 산출물만 서빙한다는 원칙은 똑같이 유효했다. 규모가 작다는 것은 오히려 변환 비용 부담이 적다는 뜻이라, 나는 더 과감하게 모든 업로드를 강제로 변환하는 쪽을 택할 수 있었다.


3. 내 환경의 제약, 서버리스 스택

내가 쓰는 스택은 엣지에서 도는 함수와 오브젝트 스토리지, 그리고 관계형 데이터베이스를 조합한 서버리스 구성이다. 상시 켜진 서버가 없으니 저렴하고 확장에 강하지만, 미디어 처리에는 뼈아픈 제약이 있었다. 엣지 함수 안에서는 무거운 네이티브 인코더를 돌릴 수 없다. 실행 시간과 메모리에 한도가 있고, 네이티브 바이너리를 붙일 수 없기 때문이다.


업로드 경로 자체에도 제약이 있었다. 브라우저가 보낸 파일이 엣지 함수의 본문을 통과해 스토리지로 들어가는 구조라, 함수의 요청 본문 한도가 곧 업로드 크기의 상한이 됐다. 큰 동영상 하나가 이 한도에 부딪히면 그대로 거부됐다. 이 제약은 뒤에 나올 동영상 편에서 두고두고 나를 괴롭히게 되는데, 근본적으로는 서버를 얇게 유지하는 서버리스 철학과 대용량 파일이 충돌하는 지점이었다.


그래서 전략은 두 갈래로 갈렸다. 가벼운 이미지는 브라우저에서 미리 변환해 이미 작아진 상태로 받고, 서버는 검증만 한다. 무거운 작업이나 브라우저가 못 하는 작업은 스토리지에 원본을 둔 채 예약 작업이나 온더플라이 변환으로 나중에 처리한다. 이 두 갈래를 어떻게 나누느냐가 파이프라인 설계의 핵심 축이 됐다.


이 제약들은 답답했지만 동시에 좋은 가드레일이기도 했다. 서버에서 뭐든 다 할 수 있었다면 나는 아마 무거운 인코더를 함수 안에 욱여넣고 타임아웃과 씨름했을 것이다. 못 하게 막혀 있으니 처음부터 클라이언트 처리와 온더플라이 변환, 예약 처리로 역할을 나눌 수밖에 없었고, 결과적으로 더 깔끔하고 확장 가능한 구조가 나왔다.


4. 이 시리즈에서 다룰 것들

앞으로 다룰 주제를 미리 지도처럼 펼쳐두면 이렇다. 먼저 이미지 쪽은 왜 특정 포맷 하나로 통일했는지, 브라우저에서 압축할 때 어떤 함정에 빠졌는지, 움직이는 이미지를 어떻게 정적화하지 않고 보존했는지, 세로로 아주 긴 이미지가 왜 뭉개졌고 어떻게 살렸는지를 순서대로 풀어간다.


그다음은 서버 쪽 변환과 썸네일이다. 브라우저가 못 하는 변환을 온더플라이로 넘기는 방법, 목록에 쓸 대표 썸네일을 자동으로 만드는 방법, 그리고 동영상 썸네일이 특정 환경에서 구조적으로 실패하던 문제를 서버사이드 프레임 추출로 어떻게 우회했는지를 다룬다. 이어서 동영상 트랜스코딩과 아이폰 영상 용량 문제로 넘어간다.


마지막 축은 운영이다. 업로드 파이프라인을 즉시 저장과 지연 처리로 어떻게 쪼갰는지, 나가는 대역폭 비용을 어떻게 0으로 만들었고 버려진 파일은 어떻게 청소하는지, 대표 이미지가 검색과 공유 카드에 어떻게 쓰이는지, 그리고 글을 수정하거나 삭제할 때 미디어 정합성을 어떻게 지키는지를 정리한다.


5. 이 기록을 관통하는 원칙

편이 나뉘어 있어도 관통하는 원칙은 몇 개로 압축된다. 첫째, 원본을 신뢰하지 않는다. 사용자가 올린 파일은 입력일 뿐이고 서빙되는 것은 내가 만든 산출물이다. 둘째, 브라우저가 잘하는 일과 서버가 잘하는 일을 억지로 한쪽에 몰지 않는다. 가벼운 변환은 클라이언트가, 무거운 변환은 서버나 온더플라이가 맡는다.


셋째, 증상을 땜질하지 않고 근본 원인까지 내려간다. 이 시리즈에 나오는 거의 모든 해결책은 처음엔 급한 대로 우회를 시도했다가, 같은 문제가 반복되는 걸 보고 나서야 진짜 원인을 파고들어 다시 설계한 결과물이다. 넷째, 비용 구조를 항상 의식한다. 저장 용량과 나가는 대역폭, 변환 횟수는 그대로 청구서에 찍히기 때문이다.


다섯째, 정합성을 마지막까지 챙긴다. 파일을 잘 저장하는 것만큼이나 글이 수정되고 삭제될 때 스토리지와 데이터베이스가 어긋나지 않게 유지하는 일이 중요하다. 버려진 파일 하나가 쌓이면 결국 비용과 혼란이 된다. 이 다섯 가지 원칙을 머릿속에 두고 각 편을 읽으면 개별 기술 결정들이 왜 그렇게 내려졌는지 자연스럽게 이어질 것이다.


이 모든 것은 완성된 설계가 아니라 부딪히고 고친 흔적이다. 각 편은 그때그때 마주한 증상에서 출발해 근본 원인을 파고, 정석 해결책으로 수렴하는 과정을 그대로 담았다. 다음 편에서는 첫 번째 결정, 왜 여러 이미지 포맷 중에서 하나만 남기고 전부 그 포맷으로 강제 변환하기로 했는지를 다룬다.