[실전 미디어 14] egress 0 스토리지 운영

지난 편에서 업로드 파이프라인을 설계하며 고아 파일이라는 부산물을 언급했다. 이번 편은 저장 그 자체의 운영, 즉 스토리지를 어떻게 고르고 어떻게 청소하는지를 다룬다. 미디어는 한 번 저장하고 끝이 아니라 계속 쌓이고, 짝을 잃은 파일이 생기고, 삭제되어야 할 것이 남는다. 이걸 방치하면 비용과 혼란이 눈덩이가 된다. 특히 미디어 서비스에서 가장 무서운 비용인 나가는 대역폭을 어떻게 0으로 만들었는지가 이번 편의 핵심이다.


1. 진짜 무서운 비용은 대역폭이다

미디어를 다루면서 비용 구조를 뜯어보니, 가장 무서운 건 저장 용량이 아니라 나가는 대역폭이었다. 저장은 파일을 보관하는 값이라 용량에 비례해 비교적 예측 가능했다. 그런데 나가는 대역폭은 파일이 사용자에게 전송될 때마다 발생하는 값이라, 인기 있는 콘텐츠일수록 폭발적으로 늘었다. 조회가 늘수록 비용이 느는 구조는 위험했다.


일반적인 스토리지와 CDN 조합에서는 이 나가는 대역폭이 비용의 대부분을 차지하기 쉬웠다. 이미지와 동영상은 텍스트와 비교가 안 되게 무거우니, 조회가 많은 미디어는 대역폭 청구서를 빠르게 키웠다. 미디어 중심 사이트가 성장하면서 비용에 짓눌리는 전형적 이유가 바로 이 대역폭이었다. 성공이 곧 비용 폭증이 되는 함정이었다.


그래서 나는 스토리지를 고를 때 나가는 대역폭 정책을 가장 중요하게 봤다. 저장 단가가 조금 비싸더라도, 나가는 대역폭이 무료이거나 아주 저렴한 쪽이 미디어 사이트에는 훨씬 유리했다. 조회가 늘어도 대역폭 비용이 안 붙으면, 성장이 비용 폭증으로 이어지지 않기 때문이다. 비용의 성격을 바꾸는 선택이었다.


결국 나는 나가는 대역폭이 무료인 오브젝트 스토리지를 택했다. 이건 비용을 없앤 게 아니라 성격을 바꾼 것이었다. 예측 불가능하게 폭증하는 대역폭 비용을, 예측 가능하고 완만한 저장 비용으로 치환한 것이다. 미디어가 아무리 많이 조회돼도 그만큼 청구서가 뛰지 않으니, 성장을 마음 편히 받아들일 수 있었다.


2. 대역폭 비용을 0으로

나가는 대역폭이 무료라는 건 미디어 사이트에서 생각보다 큰 무기였다. 인기 글의 이미지가 수만 번 조회돼도 그 전송에 추가 비용이 붙지 않았다. 앞 편들에서 다룬 온더플라이 변환의 캐시나 동영상 프레임의 저장도, 결국 이 무료 대역폭 위에서 돌아가니 비용 걱정이 덜했다. 기반의 비용 구조가 유리하면 그 위의 모든 결정이 편해졌다.


물론 이 선택에도 대가는 있었다. 나가는 대역폭이 무료인 대신 저장 단가나 다른 조건에서 트레이드오프가 있었고, 특정 인프라에 묶이는 종속도 감수해야 했다. 하지만 미디어가 핵심인 내 사이트에서는 대역폭을 잡는 이득이 이런 대가를 압도했다. 무엇이 내 서비스의 지배적 비용인지를 먼저 파악한 뒤 거기에 맞춰 고른 것이다.


이 대역폭 이점을 살리려면 앞선 최적화들이 함께 맞물려야 했다. 이미지를 webp로 작게 만들고, 표시 크기에 맞게 변환하고, 목록에는 가벼운 썸네일을 쓰는 모든 노력이 전송량 자체를 줄였다. 대역폭이 무료라도 전송량이 적으면 인프라 부하와 로딩 속도에서 이득이었다. 비용이 0이라고 낭비해도 된다는 뜻은 아니었다.


그래서 무료 대역폭은 안심의 근거였지 방심의 핑계가 아니었다. 비용이 안 붙는다고 원본을 그대로 서빙하면 로딩이 느려져 사용자가 떠났다. 비용과 별개로 사용자 경험을 위해 전송량을 줄이는 노력은 계속됐다. 다만 그 노력이 실패해 조회가 폭증하는 상황이 와도, 대역폭 비용으로 파산하지는 않는다는 안전판이 있었다. 그 안전판의 가치가 컸다. 미디어 사이트가 성장하다 대역폭 청구서에 무너지는 이야기를 여럿 봤기에, 그 위험을 구조적으로 없애 두었다는 사실만으로도 성장에 관한 마음가짐이 달라졌다. 겁내지 않고 콘텐츠를 늘릴 수 있다는 건 생각보다 큰 자유였다.


3. 주인 없는 파일을 정리하다

비용의 다른 축은 쌓이는 쓰레기였다. 앞 편에서 본 고아 파일, 즉 어느 글에도 속하지 않은 파일이 시간이 지나며 스토리지에 쌓였다. 하나하나는 작아도 계속 쌓이면 저장 비용과 관리 혼란이 됐다. 무엇보다 스토리지에 있는 파일과 실제 쓰이는 파일이 어긋나 있으면, 나중에 무엇이 필요한 파일인지 판단하기 어려웠다.


그래서 주기적으로 고아를 찾아 정리하는 루틴이 필요했다. 원리는 대조였다. 스토리지에 실제로 존재하는 파일 목록과, 데이터베이스가 어느 글에 속한다고 아는 파일 목록을 맞춰본다. 스토리지에는 있는데 어느 글도 자기 것이라 하지 않는 파일이 고아 후보였다. 두 목록의 차집합을 구하는 것이 정리의 출발이었다.


다만 대조에는 신중함이 필요했다. 방금 올렸지만 아직 글에 연결되기 전인 파일을 성급히 고아로 판단해 지우면, 멀쩡한 파일을 날리는 사고가 났다. 그래서 올라온 지 충분한 시간이 지났는데도 여전히 짝이 없는 파일만 고아로 확정했다. 유예 기간을 두어 정상적인 과도기 파일과 진짜 고아를 구분한 것이다. 성급한 청소가 데이터 손실보다 나빴다.


이 정리는 자동으로 매번 돌리기보다 신중하게 다뤘다. 삭제는 되돌릴 수 없으니, 자동 삭제 루틴이 오판하면 피해가 컸다. 그래서 정기적으로 고아 후보를 뽑아 실제 파일과 대조해 확인한 뒤 정리하는, 사람이 한 번 검토하는 절차를 뒀다. 완전 자동화의 편함보다 데이터 안전을 우선한 것이다. 지우는 일에는 늘 한 겹의 확인을 더 뒀다.


4. 삭제의 정합성

파일을 지우는 일 자체도 정합성 문제를 안고 있었다. 글이 삭제되면 거기 딸린 파일도 스토리지에서 지워야 하는데, 이 두 삭제가 항상 함께 성공한다는 보장이 없었다. 데이터베이스에서 글은 지워졌는데 스토리지의 파일 삭제가 실패하면, 또 다른 고아가 생겼다. 두 저장소에 걸친 작업의 원자성이 깨지는 지점이었다.


그래서 삭제가 실패할 가능성을 전제로 설계했다. 파일 삭제가 한 번에 실패하면 그걸로 끝내지 않고, 재시도 대기열에 넣어 나중에 다시 시도하게 했다. 일시적인 오류로 삭제가 실패해도, 대기열에 남아 있으니 결국은 지워졌다. 한 번의 실패가 영구적인 쓰레기로 굳지 않도록 재시도의 여지를 남긴 것이다.


이 재시도 구조는 앞선 편들에서 반복된 패턴과 같았다. 실패를 숨기지 않고 표시로 남겨 다시 시도한다는 원칙 말이다. 썸네일 생성이든 동영상 인코딩이든 파일 삭제든, 실패할 수 있는 작업은 전부 재시도 가능한 대기열로 감쌌다. 한 번에 완벽히 성공하는 대신, 실패해도 결국 수렴하는 구조가 신뢰의 바탕이었다.


삭제할 때는 딸린 것들을 빠짐없이 챙기는 것도 중요했다. 글 하나에는 본문 이미지만이 아니라 썸네일, 변환 캐시, 부수적인 파생물이 딸려 있었다. 글을 지우면서 이것들을 함께 정리하지 않으면 파생물만 고아로 남았다. 삭제는 하나의 파일이 아니라 그 파일에 얽힌 전체 묶음을 대상으로 해야 정합성이 지켜졌다. 이 챙김의 완결성이 다음 편의 주제로 이어졌다.


5. 월말 전수검사라는 안전망

평상시의 정리와 재시도가 잘 돌아도, 어딘가에서 어긋남은 생기기 마련이었다. 그래서 주기적인 전수검사를 안전망으로 뒀다. 한 달에 한 번쯤, 스토리지의 실제 파일과 데이터베이스의 기록을 통째로 대조해, 양쪽이 어긋난 지점을 찾아내는 것이다. 평소의 부분적 정리가 놓친 구멍을 정기 전수검사가 훑었다.


전수검사는 양방향으로 봤다. 스토리지에는 있는데 기록에 없는 고아 파일도 찾고, 반대로 기록에는 있는데 스토리지에서 사라진 파일도 찾았다. 후자는 어떤 이유로 파일이 유실된 경우라 오히려 더 심각했다. 양쪽을 다 대조해야 어느 방향의 어긋남이든 드러났다. 한 방향만 보면 절반의 문제를 놓쳤다.


이 검사도 즉시 자동 삭제로 이어지진 않았다. 검사는 어긋난 목록을 뽑아 보고하는 데까지 하고, 실제 조치는 내가 목록을 확인한 뒤 결정했다. 자동 감지와 수동 조치를 나눈 것이다. 무엇이 잘못됐는지 파악하는 일은 자동으로 자주 하되, 되돌릴 수 없는 삭제는 사람의 확인을 거치게 했다. 이 분리가 안전과 효율을 동시에 줬다.


이렇게 스토리지 운영은 무료 대역폭이라는 비용 구조 위에, 주기적 정리와 재시도와 전수검사라는 정합성 장치를 얹은 형태로 자리 잡았다. 파일을 잘 저장하는 것만큼 잘 정리하고 잘 지우는 것이 중요했다. 다음 편에서는 이렇게 관리한 미디어가 사이트 바깥, 즉 검색 결과와 소셜 공유 카드에서 어떻게 얼굴 역할을 하는지를 다룬다.