우당탕탕

MongoDB 처음 써보면서 RDBMS와 달라서 막혔던 부분들 정리 본문

Database

MongoDB 처음 써보면서 RDBMS와 달라서 막혔던 부분들 정리

모찌모찝 2026. 5. 20. 10:38

사실 저는 기존에 Oracle과 MySQL 같은 RDBMS만 써봤는데, MongoDB를 처음 써보면서 꽤나 삽질을 많이 했거든요. 쿼리 작성법도 다르고, 데이터 구조도 완전히 다르다 보니 적응하는 데 시간이 좀 걸렸어요.

이 글에서는 MongoDB를 처음 써보는 분들이 꼭 알아야 할 RDBMS와의 차이점을 제가 실제로 경험한 쿼리 작성과 최적화 사례 위주로 풀어보려고 해요. 실행 결과도 함께 보여드리니까 이해가 훨씬 쉬울 거예요.

개발 환경 / 버전 정보

제가 사용한 버전은 MongoDB 6.0이고, RDBMS는 MySQL 8.0입니다. 개발은 주로 Node.js 18 환경에서 Mongoose ODM 라이브러리를 써서 진행했어요.

MongoDB에서 쿼리는 이렇게 했어요

사실 이 부분이 가장 헷갈렸는데, RDBMS에서 쓰던 SQL과 완전히 달라서 처음엔 어떻게 데이터를 조회하고 조인하는지 감이 안 잡혔어요. MongoDB는 기본적으로 문서(Document) 단위라 조인보다는 중첩 문서 구조를 많이 쓰거든요.

// users 컬렉션에서 이름이 '홍길동'인 사용자 조회
// RDBMS에서는 SELECT * FROM users WHERE name = '홍길동'
 db.users.find({ name: '홍길동' })

// 실행 결과 예시
// {
//   _id: ObjectId("641f1c3e9f1c2b5e9f2a1d3b"),
//   name: '홍길동',
//   age: 30,
//   contacts: { email: 'hong@example.com', phone: '010-1234-5678' }
// }

find() 메서드로 간단히 조건에 맞는 문서를 가져오는 게 기본 조회인데, SQL처럼 별도의 SELECT 필드 지정 없이 문서 전체가 리턴되는 점이 신선했어요. 그리고 조인 같은 복잡한 연산은 aggregate 파이프라인을 써야 해서 처음에 엄청 당황했죠.

중첩 문서와 조인 다르게 접근하는 방법

RDBMS에서 자주 쓰던 조인은 MongoDB에선 aggregate에서 $lookup 연산자를 써서 구현하는데, 처음엔 너무 복잡해서 그냥 데이터를 중첩해서 저장하는 방식을 택했어요. 사용자 정보에 주소나 전화번호 같은 연관 데이터를 다 같이 넣는 거죠.

// 예: 사용자 문서 예시
{
  name: '홍길동',
  age: 30,
  contacts: {
    email: 'hong@example.com',
    phone: '010-1234-5678'
  }
}

// 이렇게 하면 조인 없이 한 번의 조회로 관련된 모든 데이터를 얻을 수 있어요.

이게 생각보다 성능 면에서 좋을 때가 많아서, 무조건 RDBMS 스타일로 분리하려고 하면 오히려 복잡해지는 걸 깨달았어요.

aggregate 파이프라인으로 복잡한 쿼리 처리

그런데 여기서 많이 틀리는 게 aggregate 이해하기인데요, 저는 처음에 너무 복잡한 거 한꺼번에 하려다가 헷갈렸어요. 단계별로 쪼개서 생각하는 게 훨씬 편하더라고요.

// 예: users 컬렉션에서 30세 이상이고 이메일이 존재하는 사용자만 조회
 db.users.aggregate([
   { $match: { age: { $gte: 30 }, 'contacts.email': { $exists: true } } },
   { $project: { name: 1, age: 1, 'contacts.email': 1 } }
 ])

// 실행 결과
// [ { name: '홍길동', age: 30, contacts: { email: 'hong@example.com' } }, ... ]

$match는 SQL의 WHERE, $project는 SELECT할 필드를 정하는 느낌인데, stages를 파이프라인처럼 연결하는 점이 익숙해질 때까지 시간이 좀 걸렸어요.

여기서 삽질했던 부분들

실제로 막혔던 에러 중 하나는 컬렉션 이름 오타였는데, 에러 메시지가 너무 애매해서 한참 헤맸어요. "NamespaceNotFound"라고 뜨는데, 컬렉션이 없다고 알려주는 거더라고요.

{
  "ok" : 0,
  "errmsg" : "ns not found",
  "code" : 26,
  "codeName" : "NamespaceNotFound"
}

한참 컬렉션 이름 맞는지 확인하고, DB 선택 잘 됐는지 점검하면서 해결했어요. 그리고 aggregate 사용할 때는 파이프라인 배열이 빈 배열이면 아무 데이터도 안 나오는 점도 헷갈렸죠.

성능 최적화하면서 알게 된 팁들

MongoDB는 인덱스만 잘 잡으면 조회 성능이 확실히 좋아지는데요, 특히 복합 인덱스 만들 때 필드를 어떤 순서로 넣느냐가 중요하더라고요. 그리고 너무 많은 중첩 문서가 있으면 업데이트가 느려질 수 있어서 적절한 균형을 맞춰야 해요.

예를 들어 이런 식으로 복합 인덱스를 만들었더니 쿼리 수행 시간이 3배 가까이 빨라졌어요.

// 나이와 이메일 존재 여부를 빠르게 필터링 하기 위한 복합 인덱스
 db.users.createIndex({ age: 1, 'contacts.email': 1 })

자주 물어보시는 것들

Q. MongoDB에서 RDBMS처럼 꼭 조인을 써야 할 때는 어떻게 하나요?

A. $lookup 연산자를 쓰면 됩니다. 하지만 조인을 남발하면 성능 저하가 심하니, 가능하면 데이터를 중첩해서 저장하는 게 좋아요. 아래 간단 예시 참고하세요.

// orders 컬렉션과 users 컬렉션을 조인해서 주문자 정보 가져오기
 db.orders.aggregate([
   {
     $lookup: {
       from: 'users',
       localField: 'userId',
       foreignField: '_id',
       as: 'userInfo'
     }
   },
   { $unwind: '$userInfo' }
 ])

이 방식은 조건이 복잡하거나 다른 컬렉션 데이터를 함께 보고 싶을 때 꼭 써야 하니 꼭 알아두시고요.

MongoDB를 처음 쓰면서 RDBMS와는 완전히 다른 사고방식과 쿼리 방식을 익히는 게 가장 큰 숙제였는데, 그게 해결되니 훨씬 편해지더라고요. 혹시나 이 글 보시는 분들도 저처럼 막히는 부분 있으면 하나씩 천천히 해보시면 좋겠어요.

Comments