캐싱 (feat. Redis)

문린이·2023년 4월 6일
0

캐싱이란?

캐싱이란 나중에 해당 데이터에 액세스하는 데 걸리는 시간을 줄이기 위해 메모리나 SSD와 같이 빠르게 액세스할 수 있는 위치에 데이터를 임시로 저장하는 컴퓨팅 기술이다.
캐싱 이면의 아이디어는 곧 다시 필요할 가능성이 있는 데이터를 하드 디스크 드라이브와 같이 더 느리고 더 먼 저장 위치에서 액세스해야 하는 경우보다 더 빨리 검색할 수 있는 위치에 저장하는 것이다.

캐싱은 하드웨어 수준(예: CPU 캐시)에서 애플리케이션 수준(예: 웹 페이지 캐싱)까지 컴퓨팅 시스템의 다양한 수준에서 구현될 수 있다.
웹 개발 맥락에서 캐싱은 브라우저나 프록시 서버에 자주 액세스하는 콘텐츠(예: 이미지, CSS 파일, JavaScript 파일)를 저장하여 웹 페이지의 전달 속도를 높이는 데 자주 사용된다.
콘텐츠를 더 빠르게 제공할 수 있고 이는 페이지 로드 시간을 줄이고 네트워크를 통해 전송해야 하는 데이터 양을 최소화하여 사용자 경험을 향상시킬 수 있다.

백엔드 관점에서의 캐싱

백엔드 관점에서 캐싱은 애플리케이션의 성능과 효율성을 향상시키기 위해 캐시에 데이터를 임시로 저장하는 것을 말한다.

애플리케이션이나 웹사이트가 클라이언트로부터 요청을 받으면 해당 요청을 이행하는 데 필요한 데이터를 가져와야 한다. 이는 특히 데이터가 원격 데이터베이스나 서비스에 저장된 경우 시간이 많이 걸리는 프로세스일 수 있다. 동일한 데이터를 반복적으로 가져오는 오버헤드를 피하기 위해 캐싱을 사용하면 애플리케이션이 데이터를 캐시에 임시로 저장할 수 있다. 캐시는 일반적으로 원래 데이터 소스보다 액세스 속도가 빠르다.

다음에 동일한 요청이 있을 때 애플리케이션은 원래 데이터 소스로 이동하는 대신 캐시에서 데이터를 가져올 수 있으므로 응답 시간이 크게 단축되고 애플리케이션의 전반적인 성능이 향상될 수 있다.

캐싱은 웹 페이지, 데이터베이스 쿼리, API 응답 등과 같이 자주 변경되지 않는 자주 액세스되는 데이터에 대한 백엔드 개발에서 자주 사용된다. 이 데이터를 캐싱함으로써 애플리케이션은 데이터를 반복적으로 가져오는 것을 방지하고 사용자 경험을 개선할 수 있다.

백엔드에서 사용할 수 있는 도구

  1. Redis: Redis는 데이터베이스, 캐시 및 메시지 브로커로 사용할 수 있는 메모리 내 데이터 구조 저장소이다. 높은 성능과 많은 양의 데이터를 처리할 수 있는 능력 때문에 캐싱 도구로 자주 사용된다.

  2. Amazon ElastiCache : Amazon ElastiCache는 아마존에서 제공하는 서비스로 클라우드에서 인 메모리 데이터 스토어 또는 캐시를 손쉽게 구축, 운영 및 확장할 수 있도록 지원하는 웹 서비스이다. 이 서비스는 더 느린 디스크 기반 데이터베이스에 전적으로 의존하기보다는, 빠른 관리형 인 메모리 데이터 스토어에서 정보를 검색할 수 있도록 지원하여 웹 애플리케이션의 성능을 높인다.

  3. Memcached: Memcached는 범용 분산 메모리 캐싱 시스템이다. 외부 데이터 소스(예: 데이터베이스)를 읽어야 하는 횟수를 줄이기 위해 데이터와 개체를 RAM에 캐싱하여 동적 웹 응용 프로그램의 속도를 높이는 데 자주 사용된다.

  4. Varnish: Varnish는 웹 페이지 및 기타 HTTP 요청을 캐시하는 HTTP 가속기이다. 요청이 있을 때마다 서버에서 검색하는 대신 캐시된 콘텐츠를 메모리에서 직접 제공하여 웹 사이트 속도를 높이는 데 자주 사용된다.

  5. Squid: Squid는 웹 페이지, DNS 조회 및 기타 네트워크 리소스를 캐싱하는 데 사용할 수 있는 캐싱 프록시 서버이다. 메모리에서 캐시된 콘텐츠를 제공하여 웹 애플리케이션 속도를 높이고 대역폭 사용량을 줄이는 데 자주 사용된다.

  6. Nginx: Nginx는 캐싱 도구로도 사용할 수 있는 고성능 웹 서버 및 리버스 프록시이다. 웹 페이지 및 기타 리소스를 메모리에 캐시하도록 구성하여 웹 애플리케이션 속도를 높이고 서버 부하를 줄일 수 있다.

Redis

Redis는 데이터베이스, 캐시 및 메시지 브로커로 사용할 수 있는 메모리 내 데이터 구조 저장소이다. 키-값 저장소 또는 NoSQL 데이터베이스라고도 하며 이는 데이터를 키-값 쌍으로 저장한다는 의미이다. Redis는 고성능, 확장성 및 안정성을 위해 2009년 살바토르 산필리포가 처음 개발했고 2015년부터 Redis Labs가 지원하고 있다.

Redis는 종종 캐시로 사용된다. 데이터가 메모리에 저장되기 때문에 디스크에 저장된 경우보다 훨씬 빠르게 액세스할 수 있다. 또한 개발자가 사용자 세션 데이터를 메모리에 저장하여 웹 애플리케이션의 성능을 크게 향상시킬 수 있으므로 세션 관리에 자주 사용된다.

Redis에는 문자열, 해시, 목록, 세트 및 정렬된 세트를 포함하여 여러 데이터 구조가 내장되어 있다. 이를 통해 개발자는 응용 프로그램의 요구 사항에 따라 다양한 방식으로 데이터를 저장하고 조작할 수 있다.

또한 Redis에는 고가용성 및 장애 조치를 위해 서버 클러스터에서 Redis를 실행할 수 있는 복제, 개발자가 사용자 지정 스크립트를 작성하여 Redis에서 데이터를 조작할 수 있는 Lua 스크립팅과 같은 여러 고급 기능이 있다.

전반적으로 Redis는 웹 애플리케이션의 성능과 안정성을 개선하기 위해 다양한 방법으로 사용할 수 있는 강력하고 유연한 도구이다.

Redis의 장점과 단점

장점

  1. 속도: Redis는 매우 빠르고 초당 많은 양의 요청을 처리할 수 있으므로 빠른 데이터 액세스가 필요한 실시간 애플리케이션에 적합하다.

  2. 유연성: Redis는 문자열, 해시, 목록, 세트 등을 포함한 광범위한 데이터 구조를 지원한다. 따라서 캐싱, 세션 관리 및 실시간 메시징과 같은 다양한 사용 사례에 적합하다.

  3. 확장성: Redis는 수평적으로 쉽게 확장할 수 있으므로 트래픽이 증가함에 따라 클러스터에 더 많은 노드를 추가할 수 있다.

  4. 지속성: Redis는 데이터 지속성을 지원하므로 데이터를 디스크에 저장하고 나중에 복원할 수 있다. 따라서 Redis는 장기간 데이터를 저장해야 하는 애플리케이션에 적합하다.

  5. 커뮤니티: Redis에는 개발에 기여하고 지원을 제공하며 유용한 타사 도구 및 라이브러리를 생성하는 크고 활동적인 커뮤니티가 있다.

단점

  1. 메모리 내 제한: Redis는 모든 데이터를 메모리에 저장하므로 대용량 데이터 세트의 경우 제한이 될 수 있다. 즉, Redis는 많은 양의 데이터를 저장해야 하는 애플리케이션에 최선의 선택이 아닐 수 있다.

  2. 단일 스레드: Redis는 단일 스레드이므로 한 번에 하나의 명령만 처리할 수 있다. 이는 일부 사용 사례에서 이점이 될 수 있지만 일부 시나리오에서는 성능을 제한할 수도 있다.

  3. 데이터 일관성: Redis는 엄격한 데이터 일관성을 보장하지 않는다. 즉, 메모리에 저장된 데이터와 디스크에 저장된 데이터 간에 약간의 불일치가 있을 수 있다.

  4. 복잡성: Redis는 추가 리소스와 전문 지식이 필요할 수 있는 다른 캐싱 솔루션보다 설정 및 구성이 더 복잡할 수 있다.

  5. SQL 지원 안 함: Redis는 광범위한 데이터 구조를 지원하지만 기존 SQL은 지원하지 않으므로 일부 사용 사례에 제한이 있을 수 있다.

ioredis

(Weekly Downloads가 360만이다. (redis는 310만))

ioredis는 Redis 데이터베이스에 연결하고 상호 작용하기 위한 사용하기 쉬운 인터페이스를 제공하는 인기 있는 Redis용 Node.js 클라이언트이다.
다음과 같이 Redis 클라이언트 라이브러리에 비해 많은 기능과 이점을 제공한다.

  1. 고성능: ioredis는 처음부터 성능을 염두에 두고 제작되었다. 한 번에 Redis에 여러 명령을 보낼 수 있는 파이프라이닝 기술을 사용하여 결과를 얻는 데 필요한 왕복 횟수를 줄인다. 또한 즉시 사용 가능한 클러스터링을 지원하므로 수평으로 쉽게 확장할 수 있다.

  2. 더 많은 기능: ioredis는 Redis의 모든 기능과 많은 추가 기능을 지원한다. 예를 들어 Lua 스크립팅, 트랜잭션, 게시/구독 메시징 등을 기본적으로 지원한다.

  3. 향상된 안정성: ioredis에는 Redis와의 연결이 끊어진 경우 자동 재연결 지원 기능이 내장되어 있다. 또한 바로 사용 가능한 센티널 및 클러스터 모드를 지원하므로 고가용성 Redis 클러스터를 보다 쉽게 설정할 수 있다.

  4. 더 나은 개발자 경험: ioredis에는 일관되고 직관적인 현대적이고 사용하기 쉬운 API가 있다. 또한 훌륭한 문서와 번성하는 커뮤니티가 있어 질문에 대한 답을 쉽게 찾을 수 있다.

사용기

먼저 자신의 프로젝트에서 ioredis를 설치해준다.

yarn add ioredis

redis를 사용하는 미들웨어

// cache.js

const Redis = require('ioredis'); // ioredis 라이브러리 가져오기

const redisClient = new Redis(); // 새 Redis 클라이언트 인스턴스 생성 (따로 설정이 없으면 127.0.0.1:6379 에서 실행된다.)

const cacheMiddleware = async (req, res, next) => {
  try {
    const cachedResponse = await redisClient.get(req.originalUrl);
    if (cachedResponse) {
      console.log('Serving response from cache');
      res.send(JSON.parse(cachedResponse));
      return;
    } // 미들웨어 기능 내에서 먼저 Redis에서 요청된 URL에 대해 캐시된 응답이 있는지 확인하고 캐시된 응답이 있으면 클라이언트로 다시 보낸다.
    res.sendResponse = res.send;
    res.send = async body => {
      await redisClient.set(req.originalUrl, JSON.stringify(body), 'EX', 3600);
      res.sendResponse(body);
    }; // 캐시된 응답이 존재하지 않는 경우 res.send() 메서드를 가로채 응답을 클라이언트에 다시 보내기 전에 캐시한다. TTL(time-to-live)이 1시간인 요청된 URL을 키로 사용하여 Redis에 대한 응답을 저장한다. 그런 다음 res.sendResponse()를 호출하여 응답을 다시 클라이언트로 보낸다.
    next();
  } catch (error) {
    console.error(error);
    next();
  }
};

module.exports = cacheMiddleware;

비교를 위해 만든 API

모든 게 똑같고 cache 미들웨어를 중간에 사용하는지 안 하는지만 다르다.

// index.js

const express = require('express');

const redis = express.Router();

const cacheMiddleware = require('middlewares/cache');
const { getFileLocal, getFileCache } = require('./redis.ctrl');

redis.get('/local', getFileLocal); // redis를 사용하지 않았을 때

redis.get('/cache', cacheMiddleware, getFileCache); // redis를 사용했을 때

module.exports = redis;
// redis.ctrl.js

const asyncWrapper = require('middlewares/async-wrapper');

const { getFileLocal, getFileCache } = require('./redis.svc');

exports.getFileLocal = asyncWrapper(async (req, res) => {
  const file = await getFileLocal();
  res.status(200).send(file);
});

exports.getFileCache = asyncWrapper(async (req, res) => {
  const file = await getFileCache();
  res.status(200).send(file);
});
// redis.svc.js

const axios = require('axios');

exports.getFileLocal = async () => {
  const { data } = await axios.get(
    'https://jsonplaceholder.typicode.com/todos', // 데이터를 주는 API
  );
  return data;
};

exports.getFileCache = async () => {
  const { data } = await axios.get(
    'https://jsonplaceholder.typicode.com/todos',
  );
  return data;
};

결과

redis를 사용하지 않았을 때

redis를 사용했을 때

속도가 평균 40ms -> 1ms로 빨라졌다. (불러오는 데이터가 클수록 더 차이가 크다.)

마무리

다음에는 redis를 통한 세션 관리에 관한 글을 작성하겠습니다.

profile
Software Developer

0개의 댓글