[서버 성능 개선 스토리 #2] 캐시와 Redis 알아보기

일반적으로 요청-응답 시 데이터베이스에 접근할 때 가장 많은 시간이 소요된다고 한다. 그리고 지금 내가 진행중인 프로젝트는 WEB-WAS-DB의 3티어 구조이다. 사용자가 많이 늘어나 DB에 무리가 갈 것을 사전에 방지 하고자 캐시를 활용해 서버 성능을 개선 시켜 보려고 한다.

Cache

  • 캐시는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다.
  • 캐시는 보통 메모리를 사용하기 때문에 속도가 상당히 빠르다.
  • 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터에 접근할 수 있다.
  • 즉, 미리 결과를 저장하고 나중에 요청이 오면 그 요청에 대해서 DB에 접근하지 않고 Cache에 요청을 처리한다.

이렇게 유용하게 쓰이는 캐시에도 단점이 존재한다고 한다. 캐시 서버는 속도를 위해서 주로 메모리를 사용하기 때문에 서버에 장애가 생기면 메모리가 날라가서 데이터가 손실될 수 있다. 그래서 때로는 디스크를 사용하도록 구성하거나 무분별한 캐시 사용은 피해야 겠다.

Redis

  • Redis(Remote Dictionary Server)는 "키-값" 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈소스 기반의 비관계형 데이터베이스 관리 시스템(DBMS)이다.
  • 모든 데이터를 메모리로 불러와서 처리하는 메모리 기반 DBMS이다.
  • Redis가 지원하는 데이터 구조
    • String
    • List
    • Set
    • Sorted
    • Set
    • Hashes

Redis 설정

Redis 설치(Mac)

Redis는 Homebrew를 사용하여 Mac에 설치할 수 있다.

brew install redis

Redis 시작

다음 명령어를 실행하여 Redis 서버를 로컬로 시작할 수 있다.

redis-server

NodeJS 프로젝트 설정

종속성 설치

Redis에 연결하기 위해 redis 의존성을 설치한다.

npm install redis

만들어 놓은 서버와 Redis 서버를 연결

server.js

const express = require('express');
const redis = require('redis'); // Redis 모듈 불러오기
const axios = require('axios');
const bodyParser = require('body-parser');

const redisClient = redis.createClient();
// ...

createClient() 메서드는 새로운 RedisClient 객체를 생성하는데, redis 서버와 express 서버가 같은 호스트에서 돌아가고 있다면 createClient() 에 별도의 설정을 해 주지 않아도 된다. 만약 호스트가 다르다면 호스트 url, 포트 번호 등의 설정을 추가해주어야 한다.

//...
app.get('/photos', async(req, res) => {
    const albumId = req.query.albumId
    const {data} = await axios.get(
        'https://jsonplaceholder.typicode.com/photos',
        {params : {albumId}},
        )
    res.json(data)
})

//...

Json api를 테스트해볼 수 있는 사이트를 통해 데이터가 큰 "/photos"를 보내달라고 요청해봤다.
응답에 걸리는 시간은 0.4초. 자 그럼 동일한 요청이 들어올 경우 캐시를 반환 하도록 수정해보자.

//...

// 캐시 체크를 위한 미들웨어
checkCache = (req, res, next) => {
    redisClient.get(req.url, (err, data) => {
      if (err) {
        console.log(err);
        res.status(500).send(err);
      }
      // Redis에 저장된게 존재한다.
      if (data != null) {
        res.send(data);
      } else {
        // Redis에 저장된게 없기 때문에 다음 로직 실행
        next();
      }
    });
  };

app.get('/photos', checkCache, async (req, res) => {
    try {
      console.log(req.url)
      const {data} = await axios.get('https://jsonplaceholder.typicode.com/photos');
  
      await redisClient.setex(req.url, 1440, JSON.stringify(data));
  
      return res.json(data);
    } catch (error) {
      console.error(error);
      return res.status(500).json(error);
    }
  });

//...
  • 우선, '/photos'로 요청이 들어오면 "checkCache" 미들웨어에서 Redis 서버에 해당 데이터가 캐싱되어 있는지 확인한다.
  • RedisClient객체의 get 메서드는 첫 번째 인자로 key 값을 받고, 두 번째 인자로 콜백 함수를 전달받는다.
  • 이 경우 '/photos'로 저장되어 있는 value가 있는지 확인하고, 캐시값이 존재한다면 해당 데이터를 보여준다.

수정이 됐다면 잊지말고 Redis 서버도 실행시킨 후 다시 요청을 보내보자.
동일한 요청을 반복해서 보내는 경우 응답 시간은 0.03초. 첫 번째 요청과 비교하면 매우 빨라진 것을 확인할 수 있었다.

이렇게 Redis를 통해 캐시를 활용해봤다. 잘 활용하면 애플리케이션의 속도와 성능을 크게 증가시키는 역할을 할 수 있겠다. 적절한 api에 적용 후 서버 부하 테스트를 다시 돌려서 향상된 성능을 직접 느껴봐야겠다.

참조

https://redis.io/docs/about/
https://ko.wikipedia.org/wiki/%EC%BA%90%EC%8B%9C
https://sabarada.tistory.com/103
https://brunch.co.kr/@jehovah/20
https://charming-kyu.tistory.com/37

좋은 웹페이지 즐겨찾기