Redis로 Node API를 캐싱해봅시다.

Q kim·2021년 1월 24일
2


이번 글에서는 백엔드 API에서 어떻게 Redis가 쓰이는지 알아볼것입니다.
자주 쓰이는 request를 캐싱하여 성능을 향상시켜봅시다.

웹 어플리케이션을 캐싱하는 것은 반드시 해야할 일이고 큰 퍼포먼스 향상을 이끌어냅니다.웹 서비스를 생각해보면, 당신은 아마 자주 검색되는 내용에 대해서 매번 데이터베이스에 쿼리를 하는 것이 비효율적이라고 생각할 것입니다. 정말 자주 검색되는 내용이라면 어느 곳에 캐싱해두고 데이터베이스를 들르는 지연없이 바로 응답해줄 수 있다면 데이터베이스의 부하를 정말 많이 줄일 수 있고, 웹 서비스의 성능 또한 아주 좋아질겁니다.

Redis [REmote DIctionaly Server]

레디스는 고성능의 오픈소스 NoSQL 라이브러리 입니다. 래디스의 놀라운 점은 모든 데이터를 RAM(메모리)에 저장한다는 것입니다(아주 빠르다는 뜻이죠). 그러면서 동시에 고도로 최적화된 데이터 읽기,쓰기를 약속합니다. 또한 레디스는 많은 종류의 데이터 타입들을 지원해주고 여러 프로그래밍 언어를 지원해줍니다.

참고: https://redis.io/topics/introduction

Overview

아주 간단한 api 요청에 대해 래디스에 캐싱해볼 겁니다. { name: "KIM", ID: 121 }인 데이터를 만들어둔 뒤 처음 ID로 KIM 데이터를 검색할때 redis에 저장하도록 합니다. 두번째 검색부터는 Database를 거치지않고 redis에 캐시되어있는 데이터를 가져옵니다. 그렇기 때문에 DB에서 데이터를 가져오는 것보다 훨씬 속도가 빨라집니다.

레디스를 다운로드 합니다.

window: https://redislabs.com/blog/redis-on-windows-8-1-and-previous-versions/
ubunto: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04
mac: https://redis.io/download

실행시킵시다.

터미널을 키시고 아래를 입력합니다.

redis-server


잘 켜지네요.

래디스 CLI에 접근하기 위해 새로운 터미널을 키시고 아래를 입력하세요.

redis-cli


기본 세팅

새로운 프로젝트를 만들고 아래 3개의 디펜던시를 설치합시다.

npm i express redis nodemon mongoose

express

서버 구성을 쉽게 해기 위해 express를 이용합니다.

redis

우리의 서버와 redis 서버를 연결시켜주기 위해 이용합니다.

nodemon

개발의 편의를 위해 nodemon도 설치합니다.

그리고 nodemon을 설정해줍니다.

//package.json
"start": "nodemon index"

mongoose

데이터베이스로 MongoDB를 이용할 것입니다. mongoose를 이용하면 DB를 구성하기가 쉬워지죠.

API의 시작점인 파일을 설정해줍시다.

제 경우엔 index.js 파일이 시작점입니다.

//index.js
// 기본 의존성 주입
const express = require("express");
const redis = require("redis");
const bodyParser = require("body-parser");
const mongoose = require('mongoose');

// express 서버를 구성합니다.
const app = express();

// MongoDB Model Schema를 만듭시다.
const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
    name: String,
    type: String,
    ID: Number
})

// 이용할 유저 모델 스키마.
const UserModel = mongoose.model('User', UserSchema);

// 이용할 포트를 설정해주고.
// 저는 mongodb compass를 이용하여 로컬 디비에서 작업했습니다.
const port_redis = process.env.PORT || 6379;
const port = process.env.PORT || 3000;
const MONGOURI = 'mongodb://localhost:27017/redis-test';


// bodyParser는 request로 들어오는 인자들을 해석하기 위해 필요합니다.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 몽구스로 몽고DB를 연결합니다.
mongoose.connect(MONGOURI, {
          useNewUrlParser: true,
          useUnifiedTopology: true,
          useCreateIndex: true,
          useFindAndModify: false,
}, console.log('connected'));

// redis 클라이언트를 만들고.
const redisClient = redis.createClient(port_redis);

//3000번 포트를 받도록 설정해뒀습니다.
app.listen(port, () => console.log(`Server running on Port ${port}`));

서버가 돌아가는지 확인해봅시다.

npm start

!주의 다른 터미널에서 래디스가 작동중이어야 합니다.
잘 실행되네요.


이제 본격적으로 redis에 데이터를 Caching해봅시다.

// index.js
// 미리 데이터를 하나 만들어두겠습니다.
UserModel.create({ name: "bruno", ID: 12}, (err, user) => {
  console.log(user);
});

새로고침하면
이름이 'KIM'이고 ID가 121 인 데이터가 생성됐습니다.
한번 만들었으니 주석화해주시고.

서버에 HTTP 통신을 이용해서 방금 만든 KIM 데이터를 검색해봅시다. ID로 검색해보죠. localhost:3000/findById/121 API를 만들고 검색합니다.

app.get(`/findById/:id`, async (req, res) => {
  const id = req.params.id
  try {
    const user = await UserModel.find({ ID: 121});

    res.send(user);
  } catch (error) {
    console.error(error);
  }
});

검색되네요!

자주 찾게 되는 데이터를 redis를 저장하기 "redisClient.set"

이번에는 KIM에 대한 정보를 redis에 캐싱해봅시다. .set() 메서드를 이용하면 됩니다. 기존 findById/:id에 한줄 추가해줍니다.

app.get(`/findById/:id`, async (req, res) => {
  const id = req.params.id
  try {
    const user = await UserModel.find({ ID: 121});

    // 바로 아래가 추가된 코드입니다.
    redisClient.set(`findByID/${id}`, user.toString());
    res.send(user);
  } catch (error) {
    console.error(error);
  }
});

!주의 redis에는 Object형태로 저장이 되지 않으니 제 경우에는 문자열로 바꿔( user.toString() ) 저장했습니다. 위의 /findById/:id 코드는 ID가 121인 데이터를 찾은뒤 redis에 캐싱합니다.

캐싱된 데이터를 바로 가져오기.

이제는 우리가 찾는 데이터가 캐싱되어있다면 데이터 데이터베이스를 들르지 않고 바로 전달받을수 있게 해줍시다. 미들웨어를 이용해 캐싱된 데이터를 확인하고 받아올 것입니다.

const checkRedisBeforeDatabase = (req, res, next) => {
  const id = req.params.id;
  
  redisClient.get(`findByID/${id}`, (error, data) => {
    if (error) res.status(400).send(error);
    if (data !== null) {
      console.log('have Data in redis')
      res.status(200).send(data);}
      else next();
  });
}

그리고 /findById/:id에 미들웨어로 끼워 넣습니다.

// checkRedisBeforeDatabase가 추가되었습니다.
app.get(`/findById/:id`, checkRedisBeforeDatabase, async (req, res) => {
  const id = req.params.id
  try {
    const user = await UserModel.find({ ID: 121});

    // 바로 아래가 추가된 코드입니다.
    redisClient.set(`findByID/${id}`, user.toString());
    res.send(user);
  } catch (error) {
    console.error(error);
  }
});

새로고침하여 재실행시킵시다. 그리고 localhost:3000/findById/121로 다시 서버에 요청을 합니다.

Database에 검색하기전에 redis부터 검색하게 되었습니다.

성능 차이

redis를 이용하지 않을 때와 이용할때의 속도 차이를 보면,
#이용전
#이용후
3배나 빠릅니다. 게다가 제 코드는 정말 단순하기 때문에 3배 밖에 빠르지 않은 것이니 실서비스에서의 성능향상은 기본 3배보다 월등히 높을 것입니다.

전체 코드입니다.

// index.js

//set up dependencies
const express = require("express");
const redis = require("redis");
const axios = require("axios");
const bodyParser = require("body-parser");
const mongoose = require('mongoose');
const { json } = require("express");

//setup port constants
const port_redis = process.env.PORT || 6379;
const port = process.env.PORT || 3000;

const MONGOURI = 'mongodb://localhost:27017/redis-test';

mongoose.connect(MONGOURI, {
          useNewUrlParser: true,
          useUnifiedTopology: true,
          useCreateIndex: true,
          useFindAndModify: false,
},);

const UserSchema = new mongoose.Schema({
    name: String,
    type: String,
    ID: Number
})

const UserModel = mongoose.model('User', UserSchema);


//configure redis client on port 6379
const redisClient = redis.createClient(port_redis);

//configure express server
const app = express();

//Body Parser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

//listen on port 3000;
app.listen(port, () => console.log(`Server running on Port ${port}`));



const checkRedisBeforeDatabase = (req, res, next) => {
  const id = req.params.id;
  
  redisClient.get(`findByID/${id}`, (error, data) => {
    if (error) res.status(400).send(error);
    if (data !== null) {
      console.log('have Data in redis')
      res.status(200).send(data);}
      else next();
  });
}


app.get(`/findById/:id`, checkRedisBeforeDatabase, async (req, res) => {
// app.get(`/findById/:id`, async (req, res) => {
  const id = req.params.id;
  try {
    const user = await UserModel.find({ ID: 121});

    redisClient.set(`findByID/${id}`, user.toString());
    res.send(user);
  } catch (error) {
    console.error(error);
  }
});
profile
https://medium.com/nodejs-server

0개의 댓글