heisje.devBlog
Next.js.

Next13 Mongodb 커넥션 유지로 조회속도 5배 빠르게 만들기

23. 11. 30.작성자 김희제
Next.js
MongoDb

서론

몽고디비를 연결하고싶어 레퍼런스를 많이 찾아봤는데, 전부 api연결 한번당 한번끊는 레퍼런스였습니다. 커넥션 연결 해제에는 많은 시간이 소요되고, 최소화하는 것이 좋다고 하던데요,,
그러던 중 커넥션과 연관된 좋은 레퍼런스를 찾아 소개드립니다! mongodb와 next13 세팅은 제외합니다!
그리고 저도 DB부분은 초보라, 틀린부분 있으면 알려주시면 감사하겠습니다!!

레퍼런스 : https://youtu.be/mOvW3iheF14?si=mE6ObG_JzibNl5kQ

기존 코드의 문제점

기존코드

export async function GET(request: Request, { params }: { params: { slug: string } }) {
  const url = process.env.DB_URL || '';
 
  // 일반 조회
  try {
    const client = await MongoClient.connect(url);
    const db = client.db('posts');
 
    // 조회수 조회
    const document = await db.collection('slug').findOne({ _slug: params.slug });
 
    await client.close();
    return NextResponse.json({ viewCount: document?.viewCount || 0 });
  } catch (e) {
    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

기존 코드는 MongoClient.connect()함수로 커넥션을 연결했다가, client.close()로 끊었다하는 과정이 필요했습니다.
백엔드 칭긔친구들한테 물어보니, 보통 잘 안끊는다고 하더군요,,, 그래서 DB연결을 유지하면서, 쿼리만 변경하며 통신하는 코드로 변경했습니다.

새로 작성한 코드

새로 작성한 코드 요약

디비 커넥션 정보를 서버에 한번 저장해두고, 이를 반복해서 사용해 커넥션을 줄입니다. 디비 정보를 서버에 저장하기 때문에, 클라이언트 측에선 볼 수 없습니다. (바꾸기 전 예제도 볼 수 없는 것은 동일합니다.)

1. MongoClient 설정

디렉토리
저는 다음과 같이 lib/mongodb/index.ts에 몽고db client 설정을 작성했습니다.

// 1. .env 파일에서 MongoDB URL을 로드합니다. 이 URL은 MongoDB 서버에 연결하는 데 사용됩니다.
const URL = process.env.DB_URL;
 
// 2. 옵션이 필요한 경우 작성합니다.
const options = {};
 
// 3. .env 파일이 잘 로드되는지 확인하고, URL이 없다면 에러를 발생시켜 프로세스를 중지합니다.
if (!URL) throw new Error('Add Mongo URL');
 
// 4. MongoDB 클라이언트 인스턴스를 생성합니다.
let client: MongoClient = new MongoClient(URL, options);
 
// 5. MongoDB 클라이언트를 연결하고 그 결과로 반환되는 Promise를 저장합니다.
//    이 Promise는 MongoDB 연결이 성공적으로 완료되었는지 확인하는 데 사용됩니다.
let clientPromise: Promise<MongoClient>;
 
// 6. 클라이언트를 MongoDB 서버에 연결합니다.
//    이 연결 작업은 비동기적으로 수행되므로 Promise를 반환합니다.
clientPromise = client.connect();
 
export default clientPromise;

위 코드의 5번 clientPromise에 저장된 MongoClient정보를 기반으로 데이터베이스를 조회합니다.

2. 즉시 실행 함수로 데이터베이스 연결하기

디렉토리
저는 lib/mongodb/posts.ts에 다음 코드를 작성했습니다.

// 1. MongoDB 클라이언트의 Promise를 가져옵니다.
import clientPromise from '@/lib/mongo/index';
import { Collection, Db } from 'mongodb';
 
// 2. MongoDB 클라이언트, 컬렉션을 위한 변수 선언
let client;
let posts: Collection;
 
// 3. MongoDB 데이터베이스 연결을 초기화하는 함수
async function init() {
  // 이미 데이터베이스 연결이 있다면 초기화를 생략합니다.
  if (posts) return;
 
  try {
    // MongoDB 클라이언트에 연결을 시도합니다.
    client = await clientPromise;
    // 'slug' 컬렉션을 가져옵니다.
    posts = client.db('posts').collection('slug');
  } catch (error) {
    // 데이터베이스 연결에 실패한 경우 오류를 던집니다.
    throw new Error('Failed to establish connection to database');
  }
}
 
// 4. 즉시 실행되는 익명 함수를 통해 데이터베이스 연결을 초기화합니다.
(async () => {
  await init();
})();
 
// 5. 필요한 목적을 작성합니다. 아래는 slug를 기반으로 조회수를 가져오는 함수입니다.
export async function getPosts(slug: string) {
  try {
    // posts 컬렉션이 초기화되지 않았다면 초기화를 진행합니다.
    if (!posts) await init();
 
    // slug라는 이름을 가진 post를 찾습니다.
    const result = await posts.findOne({ _slug: slug });
 
    // 조회된 문서의 viewCount를 반환하거나, 없다면 0을 반환합니다.
    return { viewCount: result?.viewCount || 0 };
  } catch (e) {
    // 조회에 실패한 경우 오류 메시지를 반환합니다.
    return { error: 'Failed to fetch posts!' };
  }
}

위 코드의 4번 init함수를 즉시 실행시켜 posts변수에 DB접근 정보를 저장합니다.
5번 getPosts처럼 서비스 코드를 작성합니다. 이후 next13의 api기능과 연동시킵니다.

3. next api와 연동

디렉토리
api를 저는 다음과 같이 app/api/posts/[slug]/route.ts에 작성했습니다.

import { NextResponse } from 'next/server';
import { getPosts } from '@/lib/mongo/posts';
 
// HTTP GET 요청을 처리하는 함수를 정의합니다.
export async function GET(request: Request, { params }: { params: { slug: string } }) {
  try {
    const { viewCount, error } = await getPosts(params.slug);
 
    if (error) {
      return NextResponse.json({ error: error }, { status: 500 });
    }
 
    // 오류가 없다면 조회수를 응답으로 반환합니다.
    return NextResponse.json({ viewCount: viewCount });
  } catch (e) {
    // 서버 내부에서 예외가 발생하면, 내부 서버 오류 메시지를 반환합니다.
    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

다음과 같이 작성하여 client.close()가 필요없는 구조로 코드를 작성했습니다.

결론

디렉토리
다음의 위에서 5개는 변경 전 커넥션을 자주 끊어줬던 코드이고, [커넥션유지테스트]라고 적혀있는 것은 커넥션을 유지하고 테스트를 한 것입니다.
1번의 커넥션이 일어날 때는 비슷한 속력으로 측정되지만, db조회 api가 많이 호출될 수록 7배 가량 빨라진 모습을 확인할 수 있었습니다.

레퍼런스

조회수: 0