backend
Redis 캐시 완벽 가이드 — Node.js API 응답 속도를 줄이는 방법
Redis를 Node.js API에 연결해 데이터베이스 조회를 캐싱하고, TTL 전략으로 응답 속도를 개선하는 방법을 실무 경험 기반으로 정리했다.

- ·Redis는 인메모리 저장소로 디스크 기반 DB 대비 읽기 속도가 수십~수백 배 빠르다
- ·TTL을 설정하지 않으면 캐시가 영구 보존되어 메모리가 무제한 증가할 수 있다
- ·Redis의 기본 포트는 6379이며 기본 설정에서는 비밀번호 없이 로컬 접속이 가능하다
- ·ioredis는 TypeScript 타입 지원이 내장되어 있어 Node.js TypeScript 프로젝트에 적합하다
사용자 목록 API의 응답 시간이 1.2초에 달하는 문제를 Redis 캐싱으로 80ms 수준까지 줄인 적이 있다. 처음엔 TTL 없이 캐시를 걸었다가 DB 데이터를 수정해도 이전 캐시 응답이 계속 반환되는 문제를 겪었고, 그때부터 데이터 특성에 따라 TTL을 다르게 가져가는 전략을 쓰게 됐다.
Redis 캐시란 무엇인가
Redis 캐시가 Node.js API 응답 속도를 높이는 원리
Redis는 Remote Dictionary Server의 약자로, 데이터를 메모리에 저장하는 인메모리 데이터 구조 저장소다. 일반적인 관계형 데이터베이스는 데이터를 디스크에 저장하기 때문에 조회할 때 디스크 I/O가 발생한다. Redis는 데이터를 메모리에 올려두기 때문에 디스크 I/O 없이 데이터를 읽을 수 있고, 이 차이가 응답 속도에서 수십 배 이상의 차이를 만든다. Node.js API에서 매 요청마다 DB를 조회하는 구조라면 자주 요청되는 데이터를 Redis에 캐싱해두면 DB 부하를 줄이고 응답 시간도 단축할 수 있다. 직접 적용해보니 1초 이상 걸리던 목록 API가 100ms 이하로 줄어드는 경험을 했다. Redis는 문자열, 해시, 리스트, 셋, 정렬된 셋 등 다양한 자료구조를 지원해서 단순 키-값 캐싱뿐만 아니라 순위 집계, 세션 관리 등 다양한 용도로 활용할 수 있다. Node.js에서는 ioredis나 node-redis 패키지로 쉽게 연동할 수 있고, 두 패키지 모두 Promise 기반 API를 제공해 async/await 코드와 자연스럽게 어울린다.
Redis 캐시 TTL 설정과 캐시 무효화 전략
Redis 캐시에서 TTL(Time To Live)은 데이터가 메모리에 얼마나 오래 남아있을지를 결정하는 핵심 설정이다. TTL 없이 캐시를 걸면 데이터가 바뀌어도 이전 캐시 데이터가 계속 반환되는 문제가 생긴다. 처음 Redis를 도입했을 때 TTL을 설정하지 않았다가 DB 데이터를 수정해도 API 응답이 그대로인 상황을 겪었고, 그때부터 데이터 특성에 맞게 TTL을 설정하는 게 필수임을 알게 됐다. TTL 전략은 데이터의 변경 빈도에 따라 다르게 가져가야 한다. 설정값이나 통계처럼 하루에 한 번 바뀌는 데이터는 TTL을 3600초 이상으로 길게 잡아도 된다. 반면 재고 수량처럼 실시간 정확도가 중요한 데이터는 TTL을 30초 이하로 짧게 잡거나 아예 캐싱하지 않는 것이 맞다. 캐시 무효화는 TTL 기반 자동 만료 외에 del 명령으로 특정 키를 즉시 삭제하는 방법도 있다. 데이터가 변경될 때 해당 캐시 키를 즉시 삭제하는 Cache Aside 패턴을 쓰면 TTL 만료를 기다리지 않아도 데이터 정합성을 유지할 수 있다.
Node.js에서 Redis 연동하는 방법
Node.js ioredis 설치와 Redis 캐시 기본 코드 작성
Node.js에서 Redis를 쓰려면 클라이언트 라이브러리를 설치해야 한다. ioredis는 TypeScript 타입 지원이 내장되어 있고 Promise 기반 API를 기본 제공해서 async/await 코드와 잘 맞는다. 설치는 npm install ioredis로 간단하다. Redis 클라이언트를 생성할 때는 싱글턴 패턴으로 연결을 한 번만 만들고 재사용하는 것이 중요하다. 매 요청마다 새 연결을 만들면 연결 오버헤드가 발생하고 Redis 연결 수 제한에 걸릴 수 있다. 기본 캐시 패턴은 먼저 Redis에서 키를 조회하고, 없으면 DB에서 데이터를 가져와 Redis에 저장한 뒤 반환하는 흐름이다. SET 명령에 EX 옵션으로 TTL을 초 단위로 지정하고, GET 명령으로 캐시 히트 여부를 확인한다. Redis에 저장할 값은 문자열이어야 하므로 객체는 JSON.stringify로 직렬화하고, 꺼낼 때 JSON.parse로 역직렬화하는 방식을 쓴다. 로컬 개발 환경에서는 docker run -d -p 6379:6379 redis:alpine 명령으로 Redis를 바로 시작할 수 있다.
import Redis from 'ioredis';
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
});
async function getCachedUsers() {
const cached = await redis.get('users:list');
if (cached) return JSON.parse(cached);
const users = await db.query('SELECT * FROM users');
await redis.set('users:list', JSON.stringify(users), 'EX', 300);
return users;
}Redis 캐시 실전 적용 패턴
Redis 캐시 키 네이밍 규칙과 네임스페이스 전략
Redis 캐시를 여러 기능에 적용하다 보면 키 이름 관리가 중요해진다. 처음에 users, posts 같은 짧은 키를 쓰다 보면 다른 기능과 충돌하거나 나중에 어떤 데이터인지 파악하기 어려워진다. 실무에서는 네임스페이스:식별자 형태로 키를 구성하는 패턴이 일반적이다. users:list는 전체 사용자 목록, users:123은 ID가 123인 사용자 개별 캐시를 의미하게 된다. 이 방식을 쓰면 특정 도메인의 캐시를 전부 초기화할 때 패턴으로 찾아 삭제하기 편하다. 다만 운영 환경에서 keys 명령은 O(N) 복잡도라 데이터가 많을 경우 서버를 잠시 블로킹할 수 있어서 scan 명령을 쓰는 것이 안전하다. 키에 버전 번호를 포함하는 전략도 있다. users:v2:list 형태로 스키마가 변경될 때 버전을 올리면 이전 캐시와 자동으로 격리된다. 직접 써봤더니 이 방식이 캐시 전략 변경 시 사이드 이펙트를 줄이는 데 효과적이었다. Redis 캐시 설정을 환경변수로 분리해 개발과 운영 환경에서 다른 TTL을 적용하면 유연하게 관리할 수 있다.
자주 묻는 질문
Redis 연결이 끊겼을 때 API가 다운되지 않으려면 어떻게 해야 하나요?+
ioredis는 연결이 끊기면 자동으로 재연결을 시도한다. 중요한 건 Redis 장애 시 캐시를 우회해 DB에서 직접 조회하는 폴백 로직을 포함시키는 것이다. try/catch로 Redis 조회를 감싸고 에러 발생 시 DB 조회로 전환하면 Redis 장애가 서비스 중단으로 이어지지 않는다.
로컬 개발 환경에서 Redis를 간단하게 실행하는 방법이 있나요?+
Docker를 쓴다면 docker run -d -p 6379:6379 redis:alpine 명령 한 줄로 로컬 Redis를 시작할 수 있다. Docker 없이 설치하려면 Ubuntu는 apt install redis-server, Mac은 brew install redis를 쓰면 된다. Docker Compose로 관리하면 팀 전체가 동일한 설정으로 개발할 수 있다.
Node.js 인메모리 캐시와 Redis 캐시의 차이는 무엇인가요?+
Node.js 인메모리 캐시(Map, node-cache)는 서버가 재시작되면 사라지고 멀티 인스턴스 환경에서 공유가 안 된다. Redis는 독립 프로세스로 서버 재시작에도 유지되고 여러 서버 인스턴스가 같은 캐시를 공유할 수 있어서 수평 확장 환경에 적합하다.
관련 글
PostgreSQL 인덱스와 쿼리 최적화 가이드 — 느린 쿼리를 빠르게 만드는 방법
PostgreSQL에서 데이터가 많아질수록 인덱스 없이는 쿼리가 느려진다. EXPLAIN ANALYZE로 실행 계획을 분석하고, 적절한 인덱스를 추가해 쿼리 속도를 개선하는 방법을 정리했다.
Docker Compose로 Node.js 개발 환경을 구성하는 방법 — 앱과 DB를 한 번에 올리는 방법
Node.js 앱과 PostgreSQL을 docker-compose.yml 하나로 묶어서 실행하면 팀원 누구나 동일한 개발 환경을 docker compose up 한 줄로 구성할 수 있다. 볼륨, 핫 리로드, 환경변수 설정까지 정리했다.
Next.js App Router fetch 캐싱과 revalidate 완벽 가이드 — 언제 데이터가 갱신되나
Next.js App Router에서 fetch는 기본으로 캐싱된다. 언제 캐시를 쓰고 언제 갱신할지 제어하는 cache, next.revalidate, next.tags 옵션과 revalidatePath, revalidateTag를 정리했다.