들어가는 말
아마존은 로딩속도가 1초 빨라지면, 매출이 68억 달러가 증가한다고 발표했으며, 핀터레스트의 경우 로딩속도를 40% 개선한 이후 트래픽이 15% 증가하고, 회원가입이 15% 증가했다고 말한다. 만약 캐싱을 하게 된다면, 클라이언트 레벨에서 로딩 속도 향상의 이점이 존재하며, 서버 레벨에서는 사용하지 않는 메모리를 활용하여 원본 데이터베이스의 부하를 줄여 더 적은 리소스로 더욱 많은 트래픽을 처리할 수 있으니 비용 효율이라는 이점을 얻어갈 수 있다.
라이언이 Next.js 구간에서 컨텐츠 캐싱을 하는 것은 어떤지 제안해 줬다. 덕분에 캐시 레이어에 대한 비용, 관리 측면의 고민들을 덜 할 수 있게 되었다. 글의 목적은 Next.js의 캐싱으로 인해 발생할 수 있는 문제점이 존재하는지 점검하고, 서버 개발자로 협업이 예상되는 지점들을 찾고, 캐시 레이어에 대한 비용 절감으로 얻는 이득을 어떻게 사용할지에 대해서 고민하기 위해 작성한다.
매일메일 컨텐츠 캐싱의 현주소
API를 위한 캐싱
매일메일 서비스의 서버는 GET /question/{id} API에 대한 호출이 가장 잦다. 전체 구독자 5600명 중 하루 평균 로드밸런서 요청 건수가 4000건이라고 할 때, 보수적으로 해당 API에 대한 요청이 4000건이라고 가정해 봤다. 이때, 다음과 같은 쿼리를 작성해 보면 100건의 레코드가 반환된다.
select category, next_question_sequence
from subscribe
group by category, next_question_sequence
이 결과가 시사하는 바는 하루에 실질적으로 소비되는 컨텐츠는 100개라는 것이다. OS나 DBMS의 캐시, 구간에 대한 분포를 고려하지 않고 개략적으로 계산해 보자면 1개의 컨텐츠는 평균적으로 40번 읽힌다. 결국 읽히는 컨텐츠는 어느 정도 예견되어 있으며, 적은 비용으로 효과를 낼 수 있다.
벌크성 메일 전송을 위한 캐싱
벌크성 메일 전송을 위한 로컬 캐시의 경우 138개의 컨텐츠를 캐싱한다. 메일 전송을 위한 컨텐츠는 1시간 동안 로컬에서 유효한 상태로 남아있고 없어진다. 벌크성 메일 전송 시간대에는 캐시 일관성에 대한 고민이 필요 없었기 때문이다. 아직은 위와 같이 필요한 데이터만 핏 하게 캐싱을 하는 것은 불필요할 수 있다 판단된다.(코드의 복잡도를 생각한다면) 다만, 시간이 늘어 컨텐츠의 수가 많아진다면 위와 같이 필요한 데이터만 캐싱하는 접근 방식이 요구될 것이다.
과거 의사결정과 Next.js 컨텐츠 캐싱에 대한 생각
1개의 컨텐츠가 40번 읽힐 가능성이 존재하지만, API 요청에 대해서는 로컬 캐시를 사용하지 않는다. 벌크성 메일 전송과는 다르게 접근 시간대가 다양하기 때문에 캐시 일관성에 대한 고려가 필요했기 때문이다. 이를 해결하기 위해 글로벌 캐시를 도입할 수 있다. 글로벌 캐시는 인프라 비용과 관리 비용이 들지만 이에 비해서 얻는 이득이 적을 수 있기 때문에 도입을 망설이고 있었다. (TPS 20 정도만 나와도 4000건 요청을 3분 이내 쳐낼 수 있다. 그리고, 쿼리 대상 테이블의 레코드 수는 현재 138건, 주에 10건씩 쌓이며, PK 기반 조회이다. 물론 전송 네트워크 트래픽 사용량도 따져봐야겠지만.. 미디어가 포함되지 않으니 논의에서 제외해보겠다.)
서버 측에서는 사용자가 지금보다 더욱 늘어나고, 이에 대한 충분한 지표가 수집되는 경우 글로벌 캐시를 도입해보려고 했었다. 하지만, 프론트엔드 측 팀 리소스가 남아있고 활용 가능한 Next.js라는 인프라가 존재하니 Next.js 레벨 캐싱에 대해서는 긍정적으로 생각힌다. 이로 인해 얻을 수 있는 로딩 속도 개선과 서버 비용 절감을 기대한다.
예견되는 문제와 협업할 수 있는 부분
크게 세 가지 문제가 발생할 수 있다. (최대 캐싱 가능 컨텐츠 수, 캐시 관통, 캐시 일관성)
최대 캐싱 가능 컨텐츠의 수
Next.js는 서버 프로세스로 이해하고 있다. 결국 해당 프로세스에 할당된 메모리나 디스크 자원에 서버 사이드 렌더링된 컨텐츠를 저장할 것이다. 그러면 최대 저장 가능한 컨텐츠의 수는 얼마나 되는 지(저장 가능한 한도)와 컨텐츠의 캐싱 기간, 공간이 꽉 찼을 때 어떤 것을 희생시킬지에 대한 정책들이 필요하지 않을까 고민했다. 저장 공간 절감과 관련해서 서버 측에서 도움을 줄 수 있는 부분은 해당 날에 발송될 컨텐츠들을 미리 예측해서 내려주는 것이 방안이 될 수 있다. 그리고, Look Aside 방식으로 캐싱되지 않은 컨텐츠는 그때 조회해서 적재하는 방식으로 풀어 볼 수 있다.
캐시 관통
토스 기술 블로그의 캐시 문제 해결 가이드 아티클에 따르면, 원본 데이터베이스에서 값을 읽었음에도 불구하고 캐싱되지 않은 상황을 캐시 관통이라고 표현한다. 이로인해 불필요한 조회 요청이 자주 발생하고, 캐시의 의미가 퇴색될 수 있다. 데이터가 존재하지 않는다는 사실 자체를 캐싱하는 것을 통해 불필요한 서버 측 요청을 줄일 수 있다. 관련해서 서버와 협업할 가능성이 있는 부분은 조금 더 고민해봐야한다.
캐시 일관성
캐싱된 데이터가 오래된 데이터일 가능성이 존재한다. 캐싱된 데이터가 오래됐다는 것은 변경이 발생했다는 것이다. 그렇다면, 컨텐츠는 수정되는 데이터인가?를 따져볼 필요성이 있다.
select count(*)
from question
where datediff(updated_at, created_at) > 0;
위와 같은 쿼리를 작성했을 때, 반환되는 수는 58이다. 즉, 한 개의 컨텐츠는 다시 수정될 가능성이 높다. (변경의 정확한 빈도를 알아내려면 바이너리 로그를 활용해야하지만 그정도로 중요한 지표는 아니므로 넘어간다.) 다음 질문은 한 번 수정된 컨텐츠는 다시 재수정될 가능성이 있는가?이다.
관리자의 경우, 한 번 공들여 작성한 컨텐츠에 대해서 시간을 많이 사용하여 작성하기 때문에 다시 수정할 일이 드물다. "이정도 작성했으면, 충분하지 않을까?"와 같은 생각이 들때 발행하기 때문이다. 질문의 수정은 주로 질문지에 대해서 구독자가 컨텐츠 피드백을 하는 경우에 발생한다. 컨텐츠 피드백을 통해서 수정이 된다고 하면, 그 이후에 수정하는 경우는 경험 상 적다. 따라서, 나의 가설은.. 수정의 빈도는 적다는 것이다. (재수정될 가능성이 적기 때문이다.)
그럼에도 불구하고 일관성 문제는 발생한다. 라이언이 제안해 준 해결책은 업데이트 주기를 설정하여, 정해진 주기마다 업데이트를 하는 방식이다. 일관성이 깨지는 시간이 정해진 주기정도 될 테지만, 서비스에 큰 영향을 줄 정도는 아니라고 판단된다. 예상되는 문제지만, 해결책이 있으므로 상대적으로 걱정이 덜하다. 관련해서 서버와 협업할 가능성이 있는 부분은 이 역시 조금 더 고민해봐야 한다.
캐싱에 대한 고민을 덜었다.. 그다음은?
캐싱에 대한 고민을 덜었다. 캐시에 대한 우선순위는 과거보다 더욱 낮아졌고, 다른 구간의 우선순위를 높일 수 있다. 후보지는 크게 3가지이다. 아래 3가지 중에 가장 시급한 것은 1번으로 보인다. 최근들어 비정상적인 트래픽이 발견되고 있다. 1번에 대해서 학습 및 적용하고, 서비스 상태에 대한 알림을 추가하는 것이 가까운 미래에 해야 할 시급한 일이라고 생각한다.
- WAF, 처리율 제한 장치 등 디도스 공격에 대한 대응
- 분산 로깅과 시스템 메트릭 수집을 위한 서버 추가
- CI/CD와 무중단 배포
'개발과 관련된 짤짤이 메모' 카테고리의 다른 글
둥근 소 고민 (0) | 2025.01.21 |
---|---|
좋아요 기능 짤짤이 (3) | 2025.01.21 |
사용자가 늘어난다. 어쩌지..? (3) | 2024.12.27 |