데이터베이스 통신 최적화로 비용 절감하기

bshunter·2023년 8월 4일
0

웹 서비스의 성능과 비용적인 측면을 개선하려면 데이터베이스 및 서버 간의 통신을 최적화하는 것이 중요합니다.
이 글에서는 private 게시글을 조회하는 예제를 통해 DB와의 불필요한 통신을 줄이고
서버의 비용을 절감하는 방법을 살펴보겠습니다.

1. 캐싱 기법 활용

데이터베이스로부터 읽기 부하를 줄이기 위해 캐싱 기법을 활용할 수 있습니다.
캐싱은 오래된 데이터의 다수 조회를 피하기 위해 최근 엑세스한 데이터를 빠르게 가져올 수 있으며 성능 개선 효과가 높은 기술입니다.

a.사용자가 글을 작성할 때:

작성자 ID와 글 ID를 키로 사용하는 캐시 스토어(예: Redis)에 글 데이터 저장하기

b.글 열람 요청 시:

캐시에서 먼저 해당하는 데이터를 찾기. 캐시에 데이터가 있다면 캐시 데이터를 사용하여토큰과 작성자 ID를 확인하고 응답하기.

c.캐시에 데이터가 없거나 캐시가 만료된 경우:

DB에서 데이터를 조회한 후 작성자 ID 검증 및 응답하기. 이때 조회한 데이터를 다시 캐시에 저장하는 것도 고려할 수 있습니다.

주의 사항

글이 수정되거나 삭제될 경우, 캐시도 동시에 업데이트되어야 합니다.
캐시 저장소의 메모리 용량과 데이터 일관성 관리를 고려해야 합니다.

예제: Node.js와 Redis를 사용한 캐싱 구현

Redis 클라이언트 설치:

npm install redis

캐싱 서비스 모듈 생성(cacheService.js):

const redis = require('redis');

// Redis 클라이언트 객체 생성
const client = redis.createClient(process.env.REDIS_URL);

// 데이터 캐싱 함수
async function cacheData(key, value) {
  return new Promise((resolve, reject) => {
    client.set(key, JSON.stringify(value), 'EX', 60 * 60, (err, reply) => {
      if (err) {
        reject(err);
      } else {
        resolve(reply);
      }
    });
  });
}

// 캐시에서 데이터 가져오기 함수
async function getDataFromCache(key) {
  return new Promise((resolve, reject) => {
    client.get(key, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(JSON.parse(data));
      }
    });
  });
}

module.exports = { cacheData, getDataFromCache };

API 엔드포인트에서 캐싱 기능 사용:

const express = require('express');
const { cacheData, getDataFromCache } = require('./cacheService');
const { getPostById, verifyAuthor } = require('./postService');
const app = express();

app.get('/post/:id', async (req, res) => {
  const postId = req.params.id;
  const userId = req.user.id; // JWT 토큰에서 사용자 ID 가져오기

  // 캐시에서 데이터 가져오기
  let post = await getDataFromCache(postId);

  if (!post) {
    // 캐시에 데이터가 없을 경우 DB에서 조회
    post = await getPostById(postId);

    // 조회한 데이터를 캐시에 저장
    await cacheData(postId, post);
  }

  // 작성자 ID 검증
  if (verifyAuthor(post.authorId, userId)) {
    res.json(post);
  } else {
    res.status(403).send('접근 거부');
  }
});

// 서버 시작
app.listen(3000, () => {
  console.log('Server running on port 3000');
});

이렇게 캐싱을 통해 최근 열람된 글에 대한 데이터베이스 통신을 줄일 수 있으며,
다수의 사용자가 자주 엑세스하는 서비스에서 좋은 성능 개선 효과를 볼 수 있습니다.

2. 클라이언트 측에서 작성자 정보 저장

작성자 정보를 클라이언트에 저장한 뒤, 서버로 요청을 보내기 전에 클라이언트 측에서 먼저
작성자 ID와 글 ID가 일치하는지 검증하는 방법입니다.

a.클라이언트에서 작성자 ID 및 글 ID를 저장하고 관리합니다.

b.사용자가 글 열람 요청을 보낼 때 클라이언트에서 먼저 작성자 ID가 일치하는지 검증합니다.

작성자 ID가 일치하는 경우에만 서버로 요청을 전송하고 응답을 받습니다.

이러한 방식은 서버와 데이터베이스 간의 통신을 줄일 수 있으나, 클라이언트 측에서 조작이 가능한 만큼
보안이 상대적으로 약할 수 있습니다.
따라서 서버 측에서도 다시 한 번 검증하는 작업이 필요할 수 있습니다.

3.JWT 토큰에 작성자 정보 저장

JWT 토큰에 작성자와 글 ID 정보를 저장하여 서버와 클라이언트 간의 통신 비용을 줄이는 방법을 적용할 수 있습니다.
이 방법은 글 작성 시 토큰에 작성자와 글 ID 정보를 포함하게 되며, 이를 통해 빠르게 조회할 수 있습니다.

a.사용자가 글을 작성할 때 작성자 ID와 함께 글 ID를 JWT 토큰에 추가하기.

새 글 작성 후 토큰을 업데이트해야 합니다.

b.사용자가 글 열람 요청을 보낼 때 서버에서 먼저 JWT 토큰을 확인하여 작성한 글 ID 목록에 요청한 글의 ID가 포함되어 있는지 확인하기.

만약 작성한 글 ID 목록에 요청한 글의 ID가 있다면, 데이터베이스에서 해당 데이터를 가져온 뒤 응답하기. 그렇지 않은 경우, 접근을 거부할 수 있습니다.

주의 사항

토큰 크기가 글을 많이 작성한 사용자에 따라 커질 수 있으며, 클라이언트와 서버 간의 불필요한
데이터 전송량이 증가할 수 있습니다.

글 수정, 삭제 등 다양한 상황에서 토큰 내의 글 ID 목록을 업데이트해야 하는 추가 작업이 필요합니다.

예제: Node.js와 JWT를 사용한 작성자 정보 저장 및 검증 구현

JWT 라이브러리 설치:

npm install jsonwebtoken

JWT 관련 함수 추가(tokenService.js):

const jwt = require('jsonwebtoken');

function generateToken(payload) {
  return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });
}

function verifyToken(token) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET);
  } catch (err) {
    return null;
  }
}

function addPostIdToToken(token, postId) {
  const decoded = verifyToken(token);
  if (decoded) {
    decoded.posts.push(postId);
    return generateToken(decoded);
  }
  return null;
}

module.exports = { generateToken, verifyToken, addPostIdToToken };

클라이언트와 서버에서 작성자 정보를 저장하고 검증하는 API 구현 및 사용:

const express = require('express');
const { generateToken, verifyToken, addPostIdToToken } = require('./tokenService');
const { createPost, getPostById } = require('./postService');
const app = express();

app.use(express.json());

app.post('/post', async (req, res) => {
  const postData = req.body.post;
  const userId = req.user.id; // JWT 토큰에서 사용자 ID 가져오기

  // 글 작성 및 ID 획득
  const postId = await createPost(postData, userId);

  // 글 ID를 토큰에 추가
  const newToken = addPostIdToToken(req.headers.authorization, postId);

  res.json({ postId, token: newToken });
});

app.get('/post/:id', async (req, res) => {
  const postId = req.params.id;
  const token = req.headers.authorization;

  // 토큰에서 사용자 작성 글 정보 가져오기
  const decoded = verifyToken(token);
  
  // 작성한 글 ID 목록에 요청한 글의 ID가 있는지 확인
  if (decoded && decoded.posts.includes(postId)) {
    const post = await getPostById(postId);
    res.json(post);
  } else {
    res.status(403).send('접근 거부');
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

위처럼 JWT 토큰을 사용하여 작성자와 글 ID를 저장하고 검증하는 방법을 구현하면, 클라이언트와 서버 간의 통신 비용 및 데이터베이스 조회를 줄일 수 있습니다.

결론
데이터베이스 통신을 최적화하기 위한 전략에는 캐싱 기법 활용과 클라이언트 측에서 작성자 정보를 저장하는 방법 등이 있습니다.
상황과 요구 사항에 따라 적절한 방법을 선택하여 구현하면 전체 시스템의 성능과 안정성을 향상시킬 수 있습니다.
이를 통해 서버의 비용을 절감하고 웹 서비스의 효율성을 높일 수 있습니다.

0개의 댓글