본문으로 바로가기

👨‍🏫 임베디드 방식

 

관계가 설정된 도큐먼트를 직접 넣는 방식입니다.

{
  _id: ObjectId(...),
  boardaName: ...,
  posts: [
    {title: "asdf", contents: "aaaa"},
    {title: "asdf", contents: "aaaa"},
    {title: "asdf", contents: "aaaa"},
    {title: "asdf", contents: "aaaa"},
  ]
}

 

 

👨‍🏫 임베디드 방식 vs 레퍼런스 방식

 

프로그래밍의 기본 규칙이 divide and conquer임을 고려해볼때 임베디드 방식은 별로 좋은 방식이 아니다.

 

1️⃣ 우선, 도큐먼트의 크기가 무한히 커질 수 있다. 한 Board 도큐먼트에 임베디드 방식으로 Post 도큐먼트를 넣는다고 생각해보자. Board를 불러올 때마다 많은 양의 post가 불러와질 것이다. 반면 레퍼런스 방식은 post의 키값만 저장하므로 도큐먼트의 크기가 작아질 것이다.

2️⃣ 도큐먼트의 일부 내용만 필요하다면 임베디드 방식은 읽기 성능을 떨어뜨리는 요인이 될 것이다.

3️⃣ 게다가 하나의 도큐먼트의 크기는 16MB로 제한되어 있으므로 도큐먼트의 크키가 무한히 커지는 것은 바람직하지 못하다. 

4️⃣ 임베디드된 도큐먼트를 추가해야 할 때 도큐먼트를 수정해야 하므로 해당 도큐먼트를 불러와야 할텐데, 도큐먼트의 크기가 크므로 속도가 느려질 것이다.

 

그나마 장점이 있다면, mongoDB가 샤딩된 경우 연결된 정보를 여기저기서 탐색하지 않고 곧바로 불러올 수 있다는 점이겠다.

 

잘 생각해서 한 chunk로 불러와야 하는 적당한 선까지는 임베디드 방식으로 하고, 쪼개야할 필요가 있을때는 레퍼런스 방식을 사용하는 것이 좋을 것이다.

 

 

👨‍🏫 레퍼런스 방식

 

도큐먼트를 통째로 넣는 임베디드 방식과 달리 해당 도큐먼트를 식별할 수 있는 식별자만 저장하는 방식입니다.

 

Video 모델과 Comment 모델을 연결하고, 연결키는 _id(objectId)를 사용한다고 가정합시다.

한 비디오에는 여러 개의 댓글이 달릴 수 있으므로 Video 모델에 여러 Comment의 _id를 추가하는 방법과 하나의 Comment에 Video의 _id를 주는 방법이 있습니다.

 

둘 중 하나만 해도 연결은 됩니다. 다만 양쪽에 모두 연결 해줘야 이후에 정보를 불러오는 데 편리하긴 합니다.

 

문제는 한 도큐먼트의 용량 제한이 13MB라는 것입니다. 무한히 많이 저장해야 한다면 아무리 레퍼런스 방식이라도 무리가 옵니다. 필요에 따라 한 쪽에만 연결하는 경우도 많습니다.

 

(예를들어, cineps 프로젝트를 진행했을 때는, 포스트에 작성자 _id를 저장하고, 유저 도큐먼트에는 작성한 글의 _id를 저장하지 않기로 했습니다. 쿼리를 통해 누가 작성자가 작성한 포스트를 불러오는 게 더 낫겠다고 판단했기 때문입니다. 대신 인덱스 설정을 잘 해줘야겠죠.)

 

Video 모델에 여러 Comment의 _id를 추가하는 방법을 보자면, 다음과 같이 배열로 묶어준 후

type에는 mongoose.Schema.Types.ObjectId를 주고, ref에는 연결하기 원하는 모델의 이름을 설정하면 됩니다.

 

import mongoose from "mongoose";

const VideoSchema = mongoose.Schema({
  fileUrl: {
    type: String,
    required: "File URL is required",
  },
  title: {
    type: String,
    required: "Title is required",
  },
  description: String,
  views: {
    type: Number,
    default: 0,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
  // comments라는 이름으로 관계를 설정해봅시다.
  comments: [
    {
      // type은 ObejctId로
      type: mongoose.Schema.Types.ObjectId,
      // ref에는 연결하기를 원하는 모델
      ref: "Comment",
    },
  ],
});

const model = mongoose.model("Video", VideoSchema);

export default model;

 

 

다중으로 관계 설정을 해 놓은 후에는 저장할 때 주의해야 합니다.

 

1의 입장에서 '다'의 객체 넣기

 

User와 Post는 1대 다의 관계입니다.

 

// Post 측에서 User와 연결
author: {
  type: mongoose.Schema.Types.ObjectId,
  ref: "User",
},

// User 측에서 Post와 연결
posts: [
  {
    type: mongoose.Schema.Types.ObjectId,
    ref: "Post",
  },
],

 

이 두 관계를 연결하기 위해서는 다음과 같이 코드합니다.

 

'다'의 입장에서 1의 모델 인스턴스를 저장할 때는 단순히 찾아진 인스턴스를 저장하면 되지만

1의 입장에서는 '다'를 배열 [] 로 저장하므로 push 메서드로 넣어줍시다.

const foundUser = await User.findOne({ email: decoded.email })

// 레퍼런스 방식으로 저장하니까 _id만 저장해둡시다.
const post = new Post({
  title: title,
  contents: contents,
  author: foundUser._id,
  section: section,
});

// User에서 post를 쉽게 불러오기 위해서 User에도 레퍼런스 방식으로 Post의 _id를 저장합시다.
foundUser.posts.push(post._id);


// 각각 저장합니다.
post.save(function (err) {
  if (err) return res.json({ postSaved: false, error: error.message });
});

foundUser.save(function (err) {
  if (err) return res.json({ postSaved: false, error: error.message });
});

 

 

그리고 이렇게 연결된 모델들은 populate하여 사용하면 됩니다.


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체