이 글에서는 Redis 캐시를 활용한 샘플 애플리케이션을 실제 예제 코드와 함께 자세히 설명합니다.
Express Node.js 애플리케이션 생성 방법은 별도 문서를 참고하시면 되며, 여기서는 바로 본론으로 들어가겠습니다.
캐싱(Caching)은 왜 필요할까?
동일한 응답을 여러 번 제공해야 하는 상황이라면, 데이터를 분산 서버의 메모리에 저장해 두는 것이 효율적입니다. 매 요청마다 스토리지 계층에서 데이터를 다시 가져오는 것보다 메모리에서 조회하는 편이 훨씬 빠르기 때문입니다. 캐싱은 애플리케이션이 지정된 기간 동안 값을 복제해 보관하고, 웹 요청에 즉시 응답할 수 있도록 해주는 기능입니다.
특히 다음과 같은 상황에서 캐싱 기법은 큰 효과를 발휘합니다.
- 서드파티(3rd party) API를 호출해야 하고, 호출 횟수 제한이 있는 경우
- 클라우드와 서버 간 데이터 전송 비용이 부담되는 경우
- 동시 요청(concurrent request) 처리량에 따라 서버 응답 속도가 크게 좌우되는 경우
Redis 캐시란 무엇인가?
Redis는 중앙화된 분산 시스템에 데이터 캐시를 저장하는 대표적인 인메모리 데이터 스토어입니다. 흔히 '데이터 구조 서버(data structures server)'라고도 불리는데, 이는 다양한 프로세스가 로딩 지연 없이 동시에 데이터를 조회하고 수정할 수 있다는 의미입니다.
Redis의 주요 장점
Redis가 갖는 특수한 데이터 구조상의 장점은 다음과 같습니다.
- 데이터가 자주 요청되고 수정되더라도 RAM이 아닌 디스크에 영속적으로 저장할 수 있습니다.
- 고급 프로그래밍 언어와 달리, 메모리 최적화 사용에 초점을 맞춰 구현되었습니다.
- 복제(Replication), 내구성 수준(Durability), 클러스터링(Clustering), 고가용성(HA) 같은 엔터프라이즈급 기능을 제공합니다.
- Memcached보다 복잡한 연산인 리스트(List), 셋(Set), 정렬된 데이터 셋 등을 처리할 수 있습니다.
로컬 시스템에 Redis 클라이언트 설치하기
Redis를 사용하려면 먼저 로컬 시스템에 Node.js Redis 클라이언트를 설치해야 합니다. Windows OS의 경우 GitHub에서 msi 파일(본문 작성 기준 v3.0.504 안정 버전)을 다운로드해 일반적인 설치 절차를 진행하면 됩니다.
최신 Redis 클라이언트는 WSL(Windows Subsystem for Linux)을 활용해 설치할 수도 있으며, 이 경우 Redis 6.x 버전을 사용할 수 있습니다. 또한 Docker 환경 구성이나 Mac, Linux OS의 경우 공식 다운로드 페이지의 안내를 따르면 됩니다.
다음 명령어로 Redis 클라이언트를 실행합니다.
redis-server
Express Node.js 애플리케이션 생성하기
PowerShell 또는 cmd 창을 열고 원하는 폴더로 이동한 뒤, 아래 명령어를 실행합니다.
npx express expressRedisCaching –hbs
다음 명령어로 모듈을 설치하고,
npm i아래 추가 패키지들도 함께 설치합니다.
npm i redis
npm i isomorphic-fetch모든 패키지 설치가 완료되면 앱을 실행할 준비가 된 것입니다. 이번 예제에서는 'getCached.js'라는 새 라우트 파일을 만들고 'App.js'에 연결했습니다.
app.use('/getCached', require('./routes/getCached'));해당 엔드포인트가 호출되면 JSON Placeholder(목업 API) 사이트에서 응답을 가져와 Redis 캐시에 저장한 후 요청에 응답합니다. 이후 들어오는 요청에는 캐시된 데이터가 즉시 전달됩니다.
const express = require('express')
require('isomorphic-fetch')
const redis = require('redis')
const router = express.Router()
const client = redis.createClient({
host:'127.0.0.1',
port:6379
})
let sourceURL = 'https://jsonplaceholder.typicode.com/todos';
router.get('/',async(req, res)=>{
await client.connect();
const value = await client.get('todos');
if(value){
console.log("from cached data")
res.send(JSON.parse(value))
}
else{
const resp = await fetch(sourceURL)
.then((response) => response.json());
await client.set('todos', JSON.stringify(resp));
console.log("from source data")
res.send(resp);
}
await client.disconnect();
})
module.exports = routerredis 모듈로 생성한 객체는 다음과 같습니다.
redis.createClient({ host:'127.0.0.1', port:6379});각 옵션의 의미는 다음과 같습니다.
host : 현재 Redis가 로컬 시스템에서 실행 중이므로 기본값인 127.0.0.1을 사용합니다.
port : Windows OS는 기본적으로 Redis에 6379 포트를 할당하지만, 필요에 따라 변경할 수 있습니다.
애플리케이션 실행 및 성능 확인
이제 아래 명령어로 앱을 실행해 보겠습니다.
npm start

첫 번째 요청에서는 소스 API에서 직접 데이터를 가져왔으며, 응답 시간은 약 1156ms가 걸렸습니다. 이 데이터는 인메모리 데이터 스토어인 Redis 캐시에 저장됩니다. 반면 두 번째 요청부터는 캐시에서 데이터를 제공받기 때문에 응답 시간이 약 10ms, 심지어 6ms 수준으로 눈에 띄게 감소하는 것을 확인할 수 있습니다.
이처럼 몇 가지 간단한 단계만으로 캐싱을 통한 성능 최적화 목표를 달성할 수 있습니다.