개요
지금까지 Redis를 활용해 Next.js fetch 캐시를 저장하고 외부에서 핸들링하는 방법들을 다뤘습니다. 저 역시 사이드 프로젝트에서 캐시를 GCP의 Redis로 관리해봤지만, 프로젝트의 규모에 비해 무시할 수 없는 비용(하루 약 2천 원)이 발생하다 보니, 다른 대안을 고민하게 되었습니다.
Next.js가 제공하는 custom cache handler는 캐시를 key-value 형식으로 저장할 수 있는 다양한 저장소를 활용할 수 있습니다. 이 글에서는 GCS(Google Cloud Storage) 를 이용해 캐시를 관리하는 방법과 함께 Redis와의 성능 및 비용을 비교해 보겠습니다.
GCS 연결
시작하기 전에
GCS가 이미 생성되어 있고 접근 권한이 있다고 가정합니다. 이 글에서 사용할 변수는 다음과 같습니다:
projectId: GCP의 Project ID
keyFilename: GCS에 접근하기 위한 key 파일 경로
bucketName: GCS bucket 이름
prefix: GCS bucket 내부 경로
캐시를 커스터마이징하기 위해 revalidate tag를 활용해 캐시키를 핸들링합니다. 이 방법에 대한 자세한 내용은 이전 글 RSC payload key 핸들링하기를 참고해주세요.
next.config.js 세팅
Redis와 마찬가지로 GCS를 위한 cacheHandler를 설정합니다. 이 설정은 cache-handler-gcs.mjs
파일에서 진행됩니다.
GCS 핸들러 연결
RedisClient를 연결한 것처럼, GCS를 저장소로 사용하기 위해 핸들러를 연결합니다.
구현는 next.config.js에서 지정한 cache-handler-gcs.mjs
파일에서 이루어집니다.
@google-cloud/storage 패키지 설치
GCS 객체를 생성하기 위해 @google-cloud/storage
패키지를 설치합니다.
npm i @google-cloud/storage
GCS bucket 연결
Storage
객체를 생성하고, 접근할 bucket을 설정합니다.
Custom CacheHandler 구현
Redis와의 차이점은 get 대신 download, set 대신 upload를 사용한다는 점입니다.
get 메서드
캐시 데이터를 다운로드하는 과정입니다:
- 캐시가 저장된 filePath를 찾습니다.
- 해당 filePath에 맞는 파일을 다운로드합니다.
- 파일이 없을 경우 예외 처리로 Cache Miss를 관리합니다.
set 메서드
캐시 데이터를 GCS에 업로드하는 과정입니다:
- filePath를 생성합니다.
- 데이터를 Next.js 캐시 형식에 맞게 구성합니다.
- 데이터를 GCS에 저장합니다.
전체 코드
Redis와 GCS 비교
GCS를 사용하려는 이유는 비용 절감이지만, 속도와 비용 사이에는 트레이드오프가 존재합니다.
비용 비교
먼저 두 서비스의 금액 산정 기준이 다릅니다.
- GCS는 1,000회 요청당 $0.004로, 1,000,000(백만 건) 요청 기준 $4의 비용이 듭니다. (저장 비용, 업데이트 비용, 네트워크 비용은 거의 없다고 가정하고 제외했습니다.)
- Redis는 서버 사용 시간당 요금이 부과되며, 글 작성일 기준으로 월 $47.45입니다.
비슷한 스펙으로 비교한 결과는 아래와 같습니다:
항목 | GCS | Redis |
---|---|---|
기본 비용 | 스토리지 저장 + 요청 비용 | 서버 시간당 요금 (메모리 크기) |
리전 | 서울 단일 리전 | 서울 리전 |
최소 사양 | 1GB Standard 스토리지 | 1GB Redis 인스턴스 |
요청 횟수(한 달) | 1,000,000 (백만 건) | 1,000,000 (백만 건) |
한 달 비용 | $4.00 (요청 횟수에 따라 변동) | $47.45 (고정) |
GCS는 요청 횟수에 따라 비용이 비례하여 증가하지만, 요청 횟수가 많지 않은 경우 매우 경제적으로 사용할 수 있습니다. 반면 Redis는 일정한 고정 비용이 들며, 스펙이 증가할수록 GCS와의 비용 차이가 더 벌어질 가능성이 높습니다.
속도 비교
Cloud Run을 이용해 같은 리전에 위치한 GCS와 Redis를 적용한 Next.js 서버를 테스트했습니다.
Cache Hit
Cache Hit 시간은 API 함수를 console.time으로 감싸서 확인합니다. 로컬에서 테스트 시, cache log와 거의 동일한 시간이 걸리는 걸 확인할 수 있습니다.
각 서비스에서 5번씩 호출 후 평균값은 GCS가 약 27.307ms, Redis가 약 4.48ms으로 GCS가 대략 6배 정도 느린걸 확인할 수 있습니다.
GCS ( 27.307ms ) | Redis ( 4.48ms ) |
---|---|
![]() | ![]() |
Cache Miss ( 데이터 업데이트 )
Cache Miss의 경우, DB 서버로부터 조회하는게 아닌, 새로운 데이터를 업데이트(set) 하는 시간입니다.
5번씩 호출 후 평균값은 GCS가 88.14ms, Redis가 2ms로 약 44배 느립니다. 확실히 GCS가 업데이트하는 데에는 시간이 좀 걸리는 걸 알 수 있습니다.
GCS ( 88.14ms ) | Redis ( 2ms ) |
---|---|
![]() | ![]() |
무엇을 선택할까?
Redis와 GCS는 각각 속도와 비용 면에서 명확한 장단점이 있습니다.
비용
- GCS는 요청 횟수에 따라 비용이 증가하지만, 적은 요청 횟수에서는 매우 경제적입니다.
- Redis는 고정적인 비용 구조를 가지며, 요청 횟수와 상관없이 일정한 비용이 발생합니다.
- 따라서 비용 효율성을 우선으로 고려한다면 GCS가 유리합니다. 특히, 요청 횟수가 적은 환경에서는 비용 절감 효과가 큽니다.
속도
- Redis는 GCS보다 훨씬 빠릅니다. 테스트 결과 Cache Hit에서는 약 6배, Cache Miss(업데이트) 에서는 약 44배의 속도 차이가 발생했습니다.
- 그러나, 두 서비스의 응답 시간은 밀리초(ms) 단위로 짧기 때문에 요청 빈도가 낮거나 속도가 절대적으로 중요한 요인이 아닌 경우 GCS도 충분히 괜찮은 옵션이 될 수 있습니다.
마무리
Redis와 GCS를 비교해보면 적용 방법에는 큰 차이가 없습니다. 두 저장소 모두 접근 가능하도록 설정한 뒤, 저장소에 맞게 get
과 set
메서드를 정의하면 됩니다.
소규모 프로젝트에서는 GCS를 활용한 방식이 성능 면에서 크게 떨어지지 않으면서, 비용 면에서 큰 이점을 제공합니다. 향후 더 많은 요청이 발생하거나 실시간성을 요구하는 환경이 된다면 Redis로 전환할 가능성을 열어두되, 당분간은 GCS를 사용해 관리해볼 생각입니다.
추가 고려 사항
fetch 데이터는 디코딩을 통해 역으로 분석될 수 있기 때문에 민감 정보 유출을 피하기 위해 기본적으로 접근 제한을 설정하고, 권한을 주는 방식으로 구성해야 합니다.
- 예제처럼 개인의 접근 권한을 keyFilename으로 주입하거나
- 서비스 계정에 GCS 권한을 부여하거나
- VPC를 이용해 내부에서만 접근 가능하도록 구성해야 합니다.