Backend/Node.js

[Node.js 교과서] ch12. NodeAuction 개선하기 4 : NginX, Redis

P.Venti 2023. 5. 2. 17:51

1.0 들어가기 전

 

포스팅이 늦어져서 죄송합니다. 개인 사정으로 인해 프로그래밍과 블로그를 할 시간이 여의치 않았습니다.

 

 이전에 만든 NodeAuction을 프로덕션으로 배포하기에는 여러 가지 문제점이 있습니다. 그중 크게 두 가지, 아래와 같은 내용을 살펴보겠습니다.

 

1. 로드밸런싱 부재

2. 잦은 Database의 접근

 

1.1 Node.js에서 로드밸런싱

 Node.js에서 로드밸런싱 방법으로는 Cluster 모듈을 사용, pm2 라이브러리 사용 또는 NginX를 경유하여 서버에 접근하도록 설계를 해주시면 됩니다. 저희는 이중 NginX를 사용하여 로드밸런싱을 하겠습니다.

 

1.2 잦은 Database의 접근

 Database에 접근하는 작업은 비교적 비용이 큰 작업입니다. 그렇다고 Database에 접근을 아예 하지 않는 것은 서버의 역할을 제대로 수행하지 못할 것입니다. 그렇기 때문에 반복되는 요청에는 캐시 서버에 저장해 둔 요청에 대한 결과를 반환하도록 설계하는 것이 Database의 접근을 최소화할 수 있습니다.

 

 

2.0 NginX 사용

 

 2.1 NginX 설치

 필자는 Window10 환경에서 개발을 하고 있기때문에 Window10 환경에서 설명을 드리겠습니다.

 

 

nginx: download

 

nginx.org

 

 위 링크에 들어가셔서 Stable Version의 NginX를 설치해 줍니다.

 

 2.2 NginX 환경 설정 

경로 : conf/nginx.conf

내용을 보시면 여러가지 작성되어 있을 텐데 일부만 수정하겠습니다.

user  www;
worker_processes  auto; #작업 프로세스가 작업 환경에 맞게 생성됩니다.

http { 
  include mine.types;
  default_type application/octat-stream;
  
  # 경매물품 등록시 이미지 파일이 올라가기 때문에 body size 제한을 설정해주셔야합니다.
  client_max_body_size 10M; 
  
  sendfile on;
  keepalive_timeout 65; #연결을 끊지않고 대기하는 시간입니다.
  
  server {
    listen 80;
    server_name localhost; 
    
    location / { 
      proxy_pass http://127.0.0.1:3000;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      
      # sever-sent events 통신을 위해 필요한 옵션
      proxy_set_header Connection '';
      proxy_http_version 1.1;
      chunked_transfer_encoding off;
    }
    
    이하 생략
  }
}

 

 위와 같이 설정을 해준 후 프로세스를 확인해 보시면 작업 프로세스가 여러 개 생성되어 있는 것을 확인하실 수 있습니다.

또 기존에는 localhost:3000을 통해 node.js 서버에 접속을 했다면, NginX를 적용한 현재에는 localhost만 작성하더라도 Nginx가 server_name와 포트 번호를 확인해 http://128.0.0.1:3000 포트로 요청을 잘 넘겨줍니다. 

 

+ NginX 설정에 대한 글은 아래 표시된 포스트에 지속적으로 업데이트될 예정입니다.

 

[NginX] nginx.conf 설정

NginX 설정 에 대한 글은 지속적으로 업데이트 될 예정입니다. 413. Request Entity Too Largy body size가 클 경우 발생하는 에러 문구 : client_max_body_size 예시 http { client_max_body_size 10M; } SSE (Server Sent Events) 지

pventi.tistory.com

 

3.0 Redis 사용 (캐시 서버)

 

 NodeAuction 웹 애플리케이션에서 반복적인 조회를 하는 부분은 메인 화면에서 경매 상품을 조회하는 부분입니다.

메인 화면에 접속할때마다 Database(MySQL)에 접근한다면 위에서 언급했듯이 비용이 많이 듭니다.

 

 3.1 구현로직

  1. redis 캐시서버에 해당 데이터가 있는지 확인

  2. 데이터가 없으면(Cache Miss) 데이터 베이스에서 조회 후 캐시 서버에 저장, 응답

  3. 다시 메인 화면에 접근 시 캐시 서버에 데이터 있으므로(Cache Hit) 캐시 서버에서 불러옵니다. 

 

 3.2 구현 

 경로 :./modules/redis.module.js

 클래스로 정의하여 client 관리합니다.(싱글톤 방식) 

const redis = require('redis')
const config = require('../config')

/**
 * reids의 저장되는 데이터가 object인지 아닌지 맵핑 테이블
 */
const ValueTypes = {
    'goods' : true,
}

module.exports = class Redis{ 
    /**
     * 
     * 싱글톤 방식으로 redis 클라이언트 관리 
     * 
     */
    constructor() {
        this.client = redis.createClient(config.redis)

        // 만약 redis socket이 닫혀있다면 재연결 시도를 함.
        this._count = 0
    }
    
    static getInstance() {
        if(!Redis.instance) {
            Redis.instance = new Redis()   
        }
        
        return Redis.instance;
    }

    /**
     * 
     * redis에서 데이터를 불러온다.
     * 저장된 데이터가 객체인지 맵핑테이블을 통해 확인 후 json 변환 까지 진행
     * 
     * @param {String} key      redis의 key값
     * @returns {null | any}    redis에서 불러온 데이터, 없으면 null 반환
     */
    async get(key){
        try{
            let item = null
            if(ValueTypes[key]){
                const tmp = await this.client.get(key)
                if(!tmp) return null
                item = JSON.parse(tmp)
            }else{ 
                item = await this.client.get(key)
            }
            
            this._count = 0;
            if(!item) return null
            return item
        }catch(err){
            if(err.message === "The client is closed"){
                await this._open()
                
                if(this._count < 5){
                    this._count++;
                    return this.get(key)
                }else{
                    return null;
                }
            }
            throw err;
        }
    }    

    /**
     * 
     * @param {String}} key 
     * @param {any} val 
     * @param {Boolean} type 
     */
    async set(key, val, type = true) {
        try{
            if(type)
                await this.client.set(key, JSON.stringify(val))
            else 
                await this.client.set(key, val)
            this._count = 0;
        }catch(err){
            if(err.message === "The Client is closed"){
                await this._open()
                
                if(this._count < 5) {
                    this._count++;
                    return this.set(key, val, type)
                }else{
                    return
                }
            }
        }
    }

    _open(){
        return new Promise(async (resolve, reject) => {
            try{
                await this.client.connect()
                resolve()
            }catch(err){ 
                reject(err.message)
            }
        })             
    }

    _close(){
        return new Promise((resolve, reject) => {
            this.client.disconnect()
        })    
    }
}

 

경로 :./services/auction.js

const { User, Good, Auction, sequelize } = require('../models')
const schedule = require('node-schedule')
const Redis = require('../modules/redis.module')
const redisInstance = Redis.getInstance()

module.exports = {
  ... 생략 ...
  
  showAll : async function(){
    try{
      let goods = await redisInstance.get('goods')
      
      if(!goods !! (Array.isArray(goods) && goods.length === 0) {
        goods = await Good.findAll({
          where : {
            SoldId : null
          }
        })
        
        redisInstance.get('goods', goods, type = true)
      }
      return goods;
    }catch(err){
      throw err;
    }
  }
  
  ... 생략 ...
}

 

마치면서 

 

 추가적으로 개선할 점이 남아있습니다.

 1. Redis 캐시 서버에 저장되는 값은 데이터베이스에 저장된 값 그대로를 저장하고 있습니다. 

 2. Cache 정책이 제대로 구현되지 않아 데이터베이스와의 데이터 정합성이 좋지 않습니다. 

 3. 하나의 Database에서 읽기와 쓰기를 모두 행하고 있습니다. 

 

다음 장에서는 위 개선할 점을 끝으로 NodeAuction를 마무리하겠습니다. 

공감과 댓글은 필자에게 힘이 됩니다. 

 

 

GitHub - dotredbee/NodeAuction_update: Node.js교과서 ch12 코드에서 추가로 업데이트한 서버입니다.

Node.js교과서 ch12 코드에서 추가로 업데이트한 서버입니다. Contribute to dotredbee/NodeAuction_update development by creating an account on GitHub.

github.com