DB/MongoDB

DB 조인 vs 애플리케이션 조인

혜이드 2025. 9. 13. 16:55

<배경>

 

최근 지금까지 관계형 DB를 주로 접해왔던 나에게, MongoDB를 보며 좀 놀라운 지점이 있었다. 

 

DB상에서 테이블을 조인한 후 가져오는 것이 아니라

각 컬렉션에서 데이터를 따로 가져온 뒤 서버 코드에서 동일한 키(예: userId)를 기준으로 합쳐 쓰는 경우가 많았다.
이렇게 DB가 아닌 애플리케이션 레벨에서 직접 join을 구현하는 방식을 흔히 “애플리케이션 조인”이라고 부른다.

 

결론적으로 두 가지 관련성있는 테이블을 동시에 사용할 때, "어디서 합칠것인지"에 방점이 찍힌다. 

 

1, db에서 직접 조인해서 받아오기 (서버가 관련해서 하는 일은 없음)

2. db에서는 테이블 개별로 받아오고, 서버에서 각 테이블의 pk값(구분 id)등을 이용해서 직접 비교

 

관계형 DB에서는 보통 DB 차원에서 FK + JOIN으로 풀지만, MongoDB 같은 NoSQL에서는 스키마가 유연하고 FK 제약이 약하기 때문에 애플리케이션 조인을 쓰는 경우가 많다. 그래서 이번 포스팅에서는 이 ‘애플리케이션 조인’을 중심으로 얘기해보겠다

 

<애플리케이션 조인>

먼저 애플리케이션 조인을 쓰는 이유를 이해하기 위해 DB조인과 애플리케이션 조인의 장단점을 비교해보자..

 

1.DB 레벨 조인 + FK 제약

장점:FK등을 이용하면, PK에 존재하지 않는 값은 넣지 못하게 하는 등 정확도가 더 높아진다. 

단점: 스키마 의존 커짐,서비스 초기에 과도한 FK가 개발 속도·유연성 저하.

 

2.애플리케이션 레벨 조인 (application-level join)

장점: 스키마 유연

단점: 무결성 보장 X(빠뜨려도 DB가 못 막음)

 

=>그럼 언제 애플리케이션 조인을 사용하면 좋을까? 

즉, MongoDB처럼 컬렉션 간 FK를 강제하지 않는 환경에서는 애플리케이션 조인이 현실적인 선택지가 된다. 하지만 무결성이 중요한 데이터라면 여전히 DB 조인이 낫다.

 

아래는 디비모델을 별개로 가져와서 서버단에서 논리적 키로 매핑하는 애플리케이션 조인의 예시이다 .

// 1) 사용자 목록 가져오기
const users = await User.find({}, { _id: 0, userId: 1, name: 1 }).lean();

// Map으로 빠르게 조회 가능하게 변환
const userMap = new Map(users.map(u => [u.userId, u]));

// 2) 주문 목록 가져오기
const orders = await Order.find({}, { orderId: 1, userId: 1, item: 1 }).lean();

// 3) 주문과 사용자 매칭
const merged = orders.map(order => {
  return {
    ...order,
    user: userMap.get(order.userId) || null, // 일치하는 사용자 없으면 null
  };
});

console.log(merged);
/*
[
  { orderId: 101, userId: 'u1', item: 'Laptop', user: { userId: 'u1', name: 'Alice' } },
  { orderId: 102, userId: 'u2', item: 'Phone',  user: { userId: 'u2', name: 'Bob'   } },
  { orderId: 103, userId: 'u9', item: 'Tablet', user: null } // 매칭 안 되는 경우
]
*/

 

 

<참고> 

 

애플리케이션조인할때는 인덱스가 더욱 중요해지겠지!

각 컬렉션/테이블에서 “키로 빨리 찾아와서 서로 다른 테이블에서 알맞게 매핑시켜야하기 때문이다.