<배경>
최근 지금까지 관계형 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 } // 매칭 안 되는 경우
]
*/
<참고>
애플리케이션조인할때는 인덱스가 더욱 중요해지겠지!
각 컬렉션/테이블에서 “키로 빨리 찾아와서 서로 다른 테이블에서 알맞게 매핑시켜야하기 때문이다.
'DB > MongoDB' 카테고리의 다른 글
| Typegoose:: Java에 “애노테이션”이 있다면, TypeScript에는 ‘데코레이터’가 있다 (1) | 2025.09.13 |
|---|---|
| [mongoDB]로컬 데이터를 MongoCLI로 올려보기. (gz,bson,json) (3) | 2025.08.31 |