![[실전 미디어 16] 미디어 생명주기 정합성](https://img.thenullpage.com/posts/5789/5789_1_ce203d.webp)
이 시리즈의 마지막 편이다. 그동안 이미지와 동영상을 어떻게 변환하고 저장하고 서빙하는지를 하나씩 다뤘다. 그런데 이 모든 처리를 관통하는 마지막 주제가 남았다. 정합성이다. 글은 한 번 발행되고 끝나는 게 아니라 수정되고 삭제된다. 그 과정에서 딸린 미디어가 어긋나면, 앞서 아무리 잘 만든 파이프라인도 결국 쓰레기와 깨진 이미지를 남긴다. 이번 편은 글의 전 생애에 걸쳐 미디어를 어긋나지 않게 유지하는 이야기로 시리즈를 맺는다.
1. 글의 생애 전체를 봐야 한다
미디어 처리를 업로드 순간만 놓고 보면 절반만 보는 것이었다. 글에는 생애가 있었다. 만들어지고, 여러 번 수정되고, 언젠가 삭제된다. 각 단계마다 딸린 미디어에 무슨 일이 일어나는지를 전부 챙겨야 정합성이 지켜졌다. 발행 순간만 완벽하게 처리하고 그 뒤를 방치하면, 시간이 지나며 어긋남이 쌓였다.
정합성의 핵심은 두 저장소가 항상 서로를 정확히 반영해야 한다는 것이었다. 파일이 실제로 있는 스토리지와, 어느 글이 어느 파일을 쓰는지 아는 데이터베이스, 이 둘이 어긋나면 문제가 생겼다. 데이터베이스는 있다는데 파일이 없으면 깨진 이미지가 뜨고, 파일은 있는데 아무도 안 쓰면 고아 쓰레기가 됐다. 양쪽의 일치가 정합성의 정의였다.
그런데 이 두 저장소에 걸친 작업은 원자적이지 않았다. 한쪽은 성공하고 다른 쪽은 실패할 수 있었다. 그래서 정합성은 한 번에 완벽히 맞추려 하기보다, 어긋날 수 있음을 전제로 어긋나면 복구하는 구조로 접근해야 했다. 앞선 편들의 재시도와 전수검사가 바로 이 복구 장치였다. 완벽 대신 회복을 지향한 것이다.
그래서 이번 편은 앞선 모든 편의 결론이 모이는 자리이기도 했다. 실패를 숨기지 않고 표시로 남긴다, 브라우저가 못 하는 건 서버로 넘긴다, 흔한 것은 단순하게 드문 것은 특별하게 다룬다, 그리고 완벽보다 회복을 지향한다. 이 원칙들이 글의 생애 관리에서 종합적으로 작동했다. 정합성은 개별 기법이 아니라 태도의 문제였다.
2. 수정할 때 미디어를 챙기다
글 수정은 정합성이 가장 자주 깨지는 지점이었다. 사용자가 글을 고치면서 이미지를 새로 추가하거나, 있던 이미지를 지우거나, 다른 이미지로 교체했다. 이때 본문의 변화와 실제 파일의 상태를 맞춰야 했다. 본문에서 이미지를 지웠는데 파일은 스토리지에 남으면 그게 곧 고아였다.
그래서 수정을 저장할 때 본문에 실제로 남아 있는 미디어가 무엇인지 다시 확정했다. 수정 전에 딸려 있던 파일 목록과, 수정 후 본문에 등장하는 파일 목록을 비교하는 것이다. 수정 후에 사라진 파일은 정리 대상으로, 새로 추가된 파일은 이 글의 것으로 갱신했다. 수정이 파일 소속을 다시 정하는 순간이 되게 한 것이다.
대표 썸네일도 수정 때 다시 봐야 했다. 첫 미디어가 바뀌면 대표 이미지도 바뀌어야 했기 때문이다. 사용자가 맨 앞 이미지를 다른 것으로 교체했는데 대표 썸네일이 옛것으로 남으면, 목록과 본문이 어긋났다. 그래서 수정으로 첫 미디어가 달라지면 대표 썸네일도 다시 뽑도록 연결했다. 얼굴이 내용을 따라가게 한 것이다.
수정에는 잠금 규칙도 얽혔다. 어떤 상태의 글은 미디어 교체를 제한해야 정합성이나 맥락이 지켜졌다. 무엇을 언제 바꿀 수 있게 할지를 정해 두지 않으면, 수정이 예상 못 한 방식으로 정합성을 흔들었다. 그래서 수정 시 무엇이 허용되고 무엇이 잠기는지를 명확히 규정했다. 자유로운 수정과 지켜야 할 불변 사이에 선을 그은 것이다.
3. 삭제할 때 딸린 것을 함께
글 삭제는 정합성의 또 다른 시험대였다. 글을 지우면 그 글에 딸린 모든 것을 함께 지워야 했다. 본문 이미지와 동영상은 물론, 그 미디어에서 파생된 썸네일과 변환 캐시, 부수적인 파일까지 전부였다. 이 중 하나라도 빠뜨리면 주인을 잃은 파생물이 고아로 남았다.
그래서 삭제는 단일 파일이 아니라 묶음을 대상으로 설계했다. 글 하나에 얽힌 파일들의 전체 목록을 알고, 그걸 통째로 정리 대상으로 넘기는 것이다. 본문 미디어만 지우고 썸네일을 잊으면, 목록에는 안 뜨는데 스토리지에는 남는 파생물이 쌓였다. 얽힌 것을 빠짐없이 세는 완결성이 삭제의 핵심이었다.
삭제도 한 번에 다 성공한다고 가정하지 않았다. 파일 삭제가 일시적으로 실패하면 재시도 대기열로 넘겨 나중에 다시 지웠다. 앞선 스토리지 운영 편에서 다룬 그 재시도 구조가 여기서 정합성을 지켰다. 글은 지워졌는데 파일이 안 지워지는 어긋남도, 결국 재시도로 수렴해 사라졌다. 실패를 전제한 설계가 삭제에서도 통했다.
부수 데이터도 함께 챙겼다. 미디어에는 파일만이 아니라 그와 얽힌 여러 기록이 딸려 있었다. 글을 지울 때 이런 부수 기록까지 정리하지 않으면, 파일은 지워졌는데 기록만 남는 반대 방향의 어긋남이 생겼다. 삭제는 파일과 기록 양쪽을 다 훑어야 완결됐다. 한 방향만 지우는 것은 정합성을 절반만 지키는 것이었다.
4. 캐시를 무효화하다
정합성에는 캐시라는 변수도 있었다. 앞선 편들에서 성능을 위해 여러 곳에 캐시를 뒀다. 목록 캐시, 변환 결과 캐시, 공유 카드 캐시 같은 것들이다. 그런데 원본이 바뀌었는데 캐시가 옛 상태로 남으면, 데이터는 맞는데 사용자에게는 옛것이 보이는 어긋남이 생겼다. 캐시는 성능의 친구이자 정합성의 적이었다.
그래서 미디어가 바뀌거나 글이 지워질 때 관련 캐시를 무효화해야 했다. 이미지를 교체하면 그 이미지의 변환 캐시를, 글을 수정하면 목록 캐시를, 함께 갱신하거나 비우는 것이다. 무효화를 빠뜨리면 사용자는 한참 동안 옛 이미지나 옛 목록을 봤다. 변경과 캐시 무효화를 짝으로 묶어 처리하는 게 중요했다.
캐시 무효화는 범위를 정확히 잡아야 했다. 너무 좁게 잡으면 옛것이 남고, 너무 넓게 잡으면 멀쩡한 캐시까지 날려 성능이 떨어졌다. 그래서 무엇이 바뀌면 어떤 캐시가 영향받는지를 파악해, 딱 그만큼만 무효화하려 했다. 변경의 파급 범위를 아는 것이 효율적 무효화의 조건이었다. 정합성과 성능의 균형이 여기서도 문제였다.
공유 카드 캐시처럼 내가 통제 못 하는 캐시도 있었다. 소셜 플랫폼이 쥔 캐시는 내가 직접 비울 수 없으니, 앞 편에서 본 것처럼 주소에 버전을 붙이거나 갱신 도구를 쓰는 우회가 필요했다. 내가 통제하는 캐시는 직접 무효화하고, 통제 못 하는 캐시는 우회로 갱신을 유도하는 이원 전략이었다. 캐시의 소유권에 따라 방법이 갈렸다.
5. 시리즈를 맺으며
돌아보면 이 시리즈는 하나의 결정에서 출발했다. 원본을 신뢰하지 않고, 업로드된 파일은 입력일 뿐이며 서빙되는 것은 내가 통제하는 산출물이라는 원칙이다. 이 첫 결정에서 포맷 통일, 클라이언트 압축, 서버 변환, 썸네일 자동화, 동영상 트랜스코딩, 스토리지 운영, 그리고 지금의 정합성까지 모든 편이 가지를 뻗었다.
또 하나 관통한 것은 브라우저와 서버의 역할 분담이었다. 가벼운 건 클라이언트에서, 무거운 건 서버나 온더플라이에서, 브라우저가 구조적으로 못 하는 건 아예 서버로 넘겼다. 하나의 도구로 다 하려 하지 않고 입력의 성격에 따라 경로를 나눈 이 사고가, 서버리스 제약 안에서 품질과 비용을 동시에 잡는 열쇠였다.
실패를 다루는 태도도 시리즈 전체를 흘렀다. 조용한 실패를 드러내 검증하고, 실패를 표시로 남겨 재시도하고, 완벽한 한 번보다 스스로 회복하는 구조를 택했다. 혼자 운영하는 사이트에서 내가 자는 동안에도 시스템이 스스로 구멍을 메우게 하는 것, 그것이 지속 가능한 운영의 조건이었다. 정합성은 그 태도의 종착점이었다. 잘 저장하는 기술보다, 어긋났을 때 스스로 알아채고 되돌리는 구조가 오래 가는 시스템을 만들었다. 화려한 처리보다 조용한 복구가 결국 신뢰를 쌓았다.
이 기록은 완성된 정답이 아니라 부딪히고 고친 흔적이다. 지금도 개선할 지점이 남아 있고, 반자동으로 굴리는 부분은 언젠가 완전 자동화로 넘어갈 것이다. 그래도 이 여정에서 얻은 원칙들은 어떤 스택에서 미디어를 다루든 통하리라 믿는다. 같은 제약 안에서 씨름하는 누군가에게 이 열여섯 편이 지뢰를 미리 피하는 지도가 되었다면, 이 긴 기록의 목적은 다한 셈이다. 여기서 실전 미디어 시리즈를 맺는다.