본문 바로가기

Node

커넥션 풀(Connection Pool)

커넥션 풀(Connection Pool)

  1. 데이터베이스와의 연결을 미리 여러개 생성하여 풀에 저장한다.
  2. 필요할 때마다 재사용하여 성능을 향상시킨다.
  3. 미리 생성된 연결을 사용함으로써 응답 시간을 감소시킨다.

커넥션 풀의 장단점

  • 장점
  1. 더 많은 연결이 더 많은 처리를 동시에 할 수 있어 동시 처리 능력이 향상된다.
  2. 동접자가 많은 환경에서 유리하다.
  3. 여러개의 연결을 유지함으로써 갑작스러운 트래피증가에 고가성용성을 제공한다.
  • 단점
  1. 사용하지 않는 커넥션으로 자원이 낭비된다.
  2. 데이터베이스 서버에 과도한 부하를 줄 수 있다. = 성능저하
  3. 네트워크 성능에 영향을 미친다. (대역폭 사용 증가)

** 실제 고성능 게임서버의 경우 수천개 이상의 커넥션을 유지하기도 한다.

서버에 DB 연동

환경변수에 DB 정보를 세팅한다. 현재는 로컬 환경에 MySQL로 연결

//환경변수들을 선언해서 사용하기위한 env.js파일에 DB의 내용을 추가해준다.

export const DB1_NAME = process.env.DB1_NAME || "database1"; //1번 DB이름
export const DB1_USER = process.env.DB1_USER || "user1"; //1번 유저 이름 
export const DB1_PASSWORD = process.env.DB1_PASSWORD || "password1"; //1번 비밀번호
export const DB1_HOST = process.env.DB1_HOST || "localhost";// 호스트 주소
export const DB1_PORT = process.env.DB1_PORT || 3306; //db포트번호

export const DB2_NAME = process.env.DB2_NAME || "database2";
export const DB2_USER = process.env.DB2_USER || "user2";
export const DB2_PASSWORD = process.env.DB2_PASSWORD || "password2";
export const DB2_HOST = process.env.DB2_HOST || "localhost";
export const DB2_PORT = process.env.DB2_PORT || 3306;

환경변수 설정이 끝났으므로 config 객체에 DB정보를 등록한다.

//환경 설정과 관련된 모든것을 사용할때는 config파일에서 호출에서 사용한다.
import {
//.env에 지정해둔 값들
  CLIENT_VERSION,
  DB1_HOST,
  DB1_NAME,
  DB1_PASSWORD,
  DB1_PORT,
  DB1_USER,
  DB2_HOST,
  DB2_NAME,
  DB2_PASSWORD,
  DB2_PORT,
  DB2_USER,
  HOST,
  PORT,
} from "../constants/env.js";
import { PACKET_TYPE_LENGTH, TOTAL_LENGTH } from "../constants/header.js";

export const config = {
  server: {
    port: PORT,
    host: HOST,
  },
  client: {
    version: CLIENT_VERSION,
  },
  packet: {
    totalLength: TOTAL_LENGTH,
    typeLength: PACKET_TYPE_LENGTH,
  },
  //여기부터 DB
  //여기에 환경변수들을 가져와 DB를 등록한다.
  database: {
    GAME_DB: {
      name: DB1_NAME, 
      user: DB1_USER,
      password: DB1_PASSWORD,
      host: DB1_HOST,
      port: DB1_PORT,
    },
    USER_DB: {
      name: DB2_NAME,
      user: DB2_USER,
      password: DB2_PASSWORD,
      host: DB2_HOST,
      port: DB2_PORT,
    },
  },
};

이제 db/database.js파일을 생성하고 코드를 작성해준다.

import mysql from "mysql2/promise";
import { config } from "../config/config.js";
import { formatDate } from "../utils/dateFomatter.js";

const { database } = config;

const createPool = (dbConfig) => {
  const pool = mysql.createPool({
    host: dbConfig.host,
    port: dbConfig.port,
    user: dbConfig.user,
    password: dbConfig.password,
    database: dbConfig.name,
    waitForConnections: true,
    connectionLimit: 10, // 커넥션 풀에서 최대 연결 수
    queueLimit: 0, // 0일 경우 무제한 대기열
  });

  const originQuery = pool.query;

  pool.query = (sql, params) => {
    const date = new Date();
    console.log(
      `[${formatDate(date)}] Executing query : ${sql} ${params ? `${JSON.stringify(params)}` : ``}`,
    );
    return originQuery.call(pool, sql, params);
  };
  return pool;
};

//여기에 여러 DB커넥션 풀 생성
const pools = {
  GAME_DB: createPool(database.GAME_DB),
  USER_DB: createPool(database.USER_DB), 
  //예시) LOG_DB: createPool(database.LOG_DB), 
};

export default pools;

이렇게 하면 pools객체가 가지고 있는 커넥션풀을 사용하여 DB에 쿼리를 전송할 수 있게 해준다.

로그를 남길때 날짜 포맷을 위해 dataFomatter.js도 만둘어준다.

export function formatDate(date) {
  //getFullYear메서드를 사용해 입력된 날짜의 연도를 가져옴
  const year = date.getFullYear(); 
  //getMonth()는 0부터 시작하는 월(0=1월,1=2월...)을 반환하므로 1을 더해 실제 월을 얻는다.
  //String(...).padStart(2, '0')를 사용하여 한 자리 수인 경우 앞에 0을 추가하여 두 자리로 만듬
  // 1인경우 (1 => 01)
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  const seconds = String(date.getSeconds()).padStart(2, '0');

//템플릿 리터럴을 사용하여 연,월,일,시간,분,초를 조합한 최종 문자열을반환
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

//사용예시
const now = new Date();
console.log(formatDate(now)); // 예: "2024-10-28 14:05:09"

서버 시동시 DB에 대한 테스트 코드 = DB생존여부를 확인하는 코드

const testDbConnection = async (pool, dbName) => {
  try {
    const [rows] = await pool.query(`select 1+1 AS solution`);
    console.log(`${dbName}테스트 쿼리 결과 : ${rows[0].solution}`);
  } catch (err) {
    console.error(`${dbName}테스트 쿼리 실행중 오류났단거임 : ${err}`);
  }
};

const testAllConnection = async (pools) => {
  await testDbConnection(pools.GAME_DB, "GAME_DB");
  await testDbConnection(pools.USER_DB, "USER_DB");
  await testDbConnection(pools.LOG_DB, "LOG_DB");
};

export { testDbConnection, testAllConnection };

//성공시 로그
[2024-10-28 21:50:39] Executing query : select 1+1 AS solution
GAME_DB테스트 쿼리 결과 : 2
[2024-10-28 21:50:39] Executing query : select 1+1 AS solution
USER_DB테스트 쿼리 결과 : 2

//실패시 로그
[2024-10-28 21:27:45] Executing query : select 1+1 AS solution
GAME_DB테스트 쿼리 실행중 오류났단거임 : Error: Unknown database 'game_db'
[2024-10-28 21:27:45] Executing query : select 1+1 AS solution
USER_DB테스트 쿼리 실행중 오류났단거임 : Error: Unknown database 'user_db'

서버 초기화 함수에 DB테스트 함수를 추가

 // 서버 초기화 작업
import { loadGameAssets } from './assets.js';
import { loadProtos } from './loadProtos.js';
import { testAllConnections } from '../utils/db/testConnection.js';
import pools from '../db/database.js';

const initServer = async () => {
  try {
    await loadGameAssets();
    await loadProtos();
    await testAllConnections(pools);//DB테스트 함수
    // 다음 작업
  } catch (e) {
    console.error(e);
    process.exit(1); // 오류 발생 시 프로세스 종료
  }
};

export default initServer;

'Node' 카테고리의 다른 글

process.exit() 메서드  (0) 2024.11.01
객체지향 패턴(Object-Oriented Patterns)  (0) 2024.10.29
TCP 이해하기  (0) 2024.10.28
socket 이벤트 / 버퍼(Buffer)  (0) 2024.10.23
Socket.io 기본 기능  (0) 2024.09.30