VODK 프로젝트의 다계층 캐싱 아키텍처 실습
비디오 수집 사이트 tv.odkkk.com이 불안정한 여러 데이터 소스를 원활한 사이트로 전환한 방법 - 그 뒤에 숨겨진 다중 계층 캐싱 아이디어에 대해 이야기해 보겠습니다.
이 웹사이트의 기능은 무엇인가요?
tv.odkkk.com은 제가 만든 작은 사이트입니다. 쉽게 말하면 동영상 집계 포털입니다. 영화 소스 자체를 저장하지 않고 대신 인터넷의 여러 리소스 사이트에서 콘텐츠를 수집하므로 사용자는 더 이상 영화를 찾기 위해 앞뒤로 이동할 필요가 없습니다.
주로 다음 세 가지 작업을 수행합니다.
- 전체 네트워크를 한 번에 검색: 영화 제목을 입력하고 백그라운드에서 여러 리소스 사이트로 이동하여 다시 질문하고, 결과를 병합하고, 중복 항목을 제거하고, 정렬하고 돌아옵니다. 사용자는 5~6개 사이트 사이에 제목을 붙여넣는 대신 하나의 깔끔한 목록을 보게 됩니다.
- 전체 정보: 리소스 스테이션에서 반환한 데이터는 일반적으로 매우 대략적이며 제목과 재생 주소만 있는 경우가 많습니다. 이것을 Douban에 가져가서 평점, 소개, 포스터를 확인하고 Bangumi에 대한 정보를 보완하여 모든 결과가 괜찮게 되도록 하겠습니다.
- 본 곳을 기억하세요: 재생 진행 상황, 검색 기록, 즐겨찾기는 모두 계정을 따르며 페이지를 변경할 때 손실되지 않습니다.
복잡해 보이지는 않지만 실제로 해보면 문제는 함수 자체가 아니라 데이터 소스가 너무 불안정하다는 사실을 알게 됩니다. 리소스 사이트는 자주 시간 초과되어 차단됩니다. Douban은 너무 많이 탐색하면 403을 얻지만 홈페이지 추천에 항상 지난주 콘텐츠가 표시되는 것은 아닙니다. 클릭할 때마다 소스로 직접 가서 데이터를 가져오면 경험이 매우 나쁠 것입니다. 때로는 몇 초 만에 열리기도 하고, 때로는 회전하는 데 오랜 시간이 걸리기도 하며, 때로는 직접 오류를 보고하기도 합니다.
따라서 이 웹 사이트를 사용할 수 있는지 여부의 핵심은 프런트 엔드가 어떻게 보이는지가 아니라 업스트림 “바람”이 데이터 레이어에 숨겨져 사용자가 항상 원활하다고 느낄 수 있는지 여부입니다. 이는 다음 캐시 아키텍처가 해결할 문제입니다.
핵심 아이디어: 데이터가 사용자에게 가까울수록 더 좋습니다.
전체 데이터 계층은 가장 빠른 것부터 가장 느린 것까지, 계층별로 CPU 캐시의 아이디어를 차용합니다.
请求 → 边缘缓存 → 热数据缓存 → 持久存储 → 上游源站
각 레벨은 놓쳤을 때만 질문하고, 맞으면 바로 돌아옵니다. 이는 반응을 최대화할 뿐만 아니라 업스트림에 가해지는 압력도 최소화합니다. 레이어별로 이야기해보겠습니다.
첫 번째 레이어: 엣지 캐시
가장 바깥쪽은 CDN 에지 노드입니다. 동일한 요청에 대해 최근에 누군가가 요청한 경우라면 엣지 노드는 내 서버에 전혀 도달하지 않고 마지막 응답을 그대로 사용자에게 직접 반환합니다. 이는 매번 메인 창고에 갈 필요 없이 아래층 매점에 베스트셀러 제품을 비축해 두는 것과 같습니다.
여기에 약간의 트릭이 있습니다. 만료된 응답이 먼저 반환되고 백그라운드에서 조용히 업데이트되도록 하세요. 사용자가 얻는 것은 “이전 초의 이전 데이터”이지만 다음 초에 새로 고쳐집니다. 항상 초 단위로 시작되므로 소스가 반환될 때까지 기다릴 필요가 없습니다.
두 번째 레이어: 핫 데이터 캐시
엣지 캐시가 누락되고 요청이 애플리케이션 계층에 도달합니다. 여기에는 최근 자주 액세스되는 데이터 전용으로 메모리 수준의 빠른 캐시가 있습니다.
가장 현명한 점은 자체 인기도를 판단한다는 것입니다. 즉, 더 많은 데이터에 액세스할수록 해당 데이터가 더 오래 유지됩니다. 방금 들어오는 콜드 데이터는 1분 동안만 보관되는데, 반복해서 클릭하기 시작하면 몇 분, 10분, 30분까지 자동으로 연장됩니다. 결과적으로, 아무도 읽지 않는 콘텐츠는 빠르게 만료되어 공간을 차지하게 됩니다.
그 결과, 인기 있는 콘텐츠가 더욱 영구적이 될수록 인기가 없는 콘텐츠는 자연스럽게 제거됩니다. 사람들이 “어떤 것을 보관해야 하는지” 추측할 필요가 없으며 방문자가 스스로 투표합니다.
또 다른 세부 사항이 있습니다. 데이터를 쓸 때 “백업 복사본”을 저장하고 유효 기간은 원본의 5배입니다. 원본이 만료되어 원본으로 반환되는 경우 사용자는 로딩 원을 쳐다보는 대신 최소한 이전 데이터 전체를 얻을 수 있습니다.
세 번째 계층: 영구 저장소
핫 캐시 데이터는 만료된 후에도 실제로 사라지지 않습니다. 영구 스토리지 계층에서 “워밍업”됩니다. 이 레이어는 오랫동안 저장되며 이는 오프라인 스냅샷과 동일합니다. 오늘 업스트림 사이트가 완전히 다운되더라도 지난번에 캡처한 데이터가 여전히 남아 있습니다. 기껏해야 사용자에게는 오류 페이지 대신 약간 오래된 콘텐츠가 표시됩니다.
여기서 나는 무의미한 던지기를 피하기 위해 두 가지 작은 일을 했습니다.
첫째, 내용이 실제로 변경된 경우에만 덮어쓰게 됩니다. 각 쓰기 전에 “지문”이 계산되고 비교됩니다. 내용이 변경되지 않는 경우에는 데이터 자체를 다시 쓰지 않고 접속 시간과 횟수만 업데이트됩니다. 이런 식으로 반복해서 접속했지만 내용이 변하지 않은 인기 영화는 매일 데이터베이스에 기록되어 쓸모가 없을 것입니다.
둘째, 새로운 데이터를 업데이트해야 합니다. 지속성 레이어는 영구적으로 유지되지만 일부 항목에는 항상 오래된 데이터를 제공할 수 없습니다. 예를 들어 홈페이지 추천과 같이 일주일 전의 목록을 항상 표시하는 것은 지루할 것입니다. 따라서 각 데이터 유형마다 유효 기간이 설정됩니다. 예를 들어 홈페이지 추천은 하루에 한 번 새로 고쳐야 하며 검색 결과는 더 오래 저장될 수 있습니다. 유효 기간이 지나면 영구 레이어가 있더라도 다른 레이어를 가져오기 위해 강제로 소스로 이동해야 합니다.
네 번째 레이어: 상류 원점 역
실제로 이 지점에 도달하면 처음 세 레벨이 모두 누락되었음을 의미합니다. 이제 정말로 자원 스테이션의 문을 두드려야 할 때입니다. 다시 가져온 새 데이터는 핫 캐시와 지속성 레이어에 동시에 채워지며 쓰기가 완료되기 전에 사용자에게 응답합니다. 다음에 동일한 요청이 이루어지면 캐시에서 직접 적중됩니다.
방해가 되지 않는 곳에 라이브러리 작성
지속성 계층에 데이터를 쓰면 응답 속도가 느려질 가능성이 가장 높습니다. 그래서 저는 “포스트 비동기식”으로 작성했습니다. 먼저 데이터를 사용자에게 반환한 다음 천천히 백그라운드의 데이터베이스에 놓습니다. 데이터베이스 쓰기에 실패하더라도 이 액세스는 영향을 받지 않습니다. 기껏해야 다음 번에 소스가 한 번 더 반환됩니다.
“사용자가 빠르다고 느끼는지 여부”와 “데이터가 안전하게 저장되어 있는지 여부”를 분리하면 이러한 절충안은 가치가 있습니다. 데이터베이스가 느리면 사용자는 무관심하지만 응답이 0.5초 느리면 사용자는 즉시 짜증을 냅니다.
효과
이 아키텍처를 실행한 후 가장 직관적인 두 가지 사항은 다음과 같습니다.
하나는 빠릅니다. 홈페이지는 기본적으로 몇 초 만에 열리며, 검색에는 기본적으로 100밀리초 미만이 소요됩니다. 대부분의 요청은 업스트림에 도달하지 않습니다.
두 번째는 안정성이다. 현재 특정 리소스 사이트에 문제가 있는데 사용자는 이를 전혀 모릅니다. 이는 핫 캐시나 지속성 레이어에 있지만 데이터가 약간 오래되었기 때문입니다. 복구되면 다음에 크롤링할 때 자연스럽게 업데이트됩니다.
마지막에 쓰세요
이 프로젝트를 완료한 후 “캐싱”에 대한 나의 견해가 많이 바뀌었습니다. 예전에는 캐싱이 단지 “다음번에 결과를 더 빨리 저장하는 것”이라고 생각했지만 이제는 사용자의 업스트림 불확실성을 소화하는 것과 비슷하다고 생각합니다. 속도는 부산물일 뿐이고 안정성은 해결해야 할 실제 문제입니다.
좋은 캐싱 전략은 모든 데이터를 영구적으로 저장하는 것이 아니라 핫 데이터는 영구적으로 유지하고 콜드 데이터는 복원하며 만료된 데이터는 새로 고칠 수 있도록 하는 것입니다.