Mongoose (Advanced)

공부의 기록·2022년 3월 24일
0

DB MongoDB

목록 보기
5/5
post-thumbnail

Introdcue

본 문서는 2022년 3월 24일 에 작성되었습니다.

여기서는 Velog - unchaptered / Mognoose (Basic) 에서 다루지 않은 다양한 기법을 포함할 생각입니다.

물론, 전 게시글에서도 Schema, Model 기능을 따로 설명하지는 않았습니다.
또한 mongoose 의 수많은 메서드나 예약어, 명령어 들도 설명하지 않았습니다.
이 부분은 개인의 학습에 맡기며, 저 또한 이를 무차별적으로 외우는 것이 불필요하다고 느꼈습니다.

따라서, 본문에서는 해당 내용을 알고 있다는 전제를 깔고 설명을 할 생각입니다.

  1. Promise.All([]) 을 활용한 DB 동시 취득
  2. Populate 를 이용한 계층형 정보 취득
  3. Denomalize 를 이용한 절차 감소 와 성능 향상
  4. Pagination 등...

Advanced

본 내용 중 다수는 JavaScript 지식이 필요합니다.
아래 포스트를 참고해주시고 Async-Await, Promise 는 구글링을 해주세요.

Velog - unchaptered / 시리즈 | JavaScript

방법(기대) 호출 수
RESTful API 식M * N 번
Promise.All([]) 사용N + 1 번
populate 사용3 번
virtual populate 사용3 번 ( 용량 차지 안함, 성능은 ? )
denomalize 사용1번 ( 극한의 IO, 하지만 별도의 처리 필요 ! )

Promise.All([]) 활용

기본적으로 DB 에서 값을 받아오는 과정은 딜레이가 있습니다.
따라서 이 결과를 사용하기 위해서 동기 처리(async - await) 가 필요합니다.
하지만 어떠한 DB 접근은 서로 무관한 접근 이기 때문에, 비동기처리(promise) 를 통해서 성능을 향상 시킬 수 있습니다.

// 동기 처리를 이용한 방법 N * M 의 호출 
const getUser = async (req, res) => {
  const userList = await UserModel.fine({}).limit(10);
  const postList = await PostModel.find({}).limit(10);
  const commentList = await CommentModel.find({}).limit(10);
}

// 비동기 처리 + 동기 처리를 이용한 방법 N + 1 의 호출
const getUser = async (req, res) => {
  const [ userList, postLists, commentList ] = await Promise([
      UserModel.fine({}).limit(10),
      PostModel.find({}).limit(10),
      CommentModel.find({}).limit(10)
  ]);
}

Populate 활용

RDBMS 의 Relationship 처럼 MongoDB 도 reference 가 존재합니다.
이렇게 reference 설정이 되어있는 경우, mongoose 의 populate 를 사용할 수 있습니다.

// Populate 활용
const getUser = async (req, res) => {
  const userList = await UserModel
  		.find({})
  		.popluate("postlist(키 값)")
  		.populate("commentlist(키 값)").limit(10);
}

# populate

Virtual Populate 활용

Populate 활용 에서 보듯이,
User - Post - Comment 로 연달아 이어지는 계층형 구조의 정보는
실제적으로 Schema 를 적어보면 다음과 같이 중복된 구조(무결성의 오류) 가 발생할 수 있습니다.

const userSchema = new Schema({
  // 대충 user 전용 필드
  posts: [{ type:ObjectId, ref: "Post" }],
  comments: [{ type:ObjectId, ref: "Comment" }]
});

const postSchema = new Schema({
  // 대충 post 전용 필드
  owner: { type:ObjectId, ref: "User" },
  comments: [{ type:ObjectId, ref: "Comment" }]
});

const commentSchema = new Schema({
  // 대충 comment 전용 필드
  owner: { type:ObjectId, ref: "User" },
  ownerPost: { type:ObjectId, ref: "Post" }
});

이러한 경우 RDBMS 로 치면 외래키 참조에 의한 강한 결합 이라고 할 수 있을 것이나,
역시나 느슨한 결합Virtual 이라는 메서드를 이용해서 구현할 수 있습니다/

postSchema 의 comments 를 해당 기능을 이용해 결합하면,
다음과 같이 구현할 수 있습니다.

const postSchema = new Schema({
  // 대충 post 전용 필드
  owner: { type:ObjectId, ref: "User" },
  // comments: [{ type:ObjectId, ref: "Comment" }] 
});

postSchema.virtual("comments", {
  ref: "comment",
  localField: "_id",
  foreignField: "post"
});

postSchema.set("toObject", { virtual: true });
postSchema.set("toJSON", { virtual: true });

const Post = model("Post", postSchema");
                   
export default Post;

더 자세한 내용은 Mongoose Virtuals 를 참고해야 할 것 같습니다.

Denomalize 활용

Denomalize 는 반정규화라는 내용입니다.
DB 의 입출력 성능을 높이기 위해, 비즈니스 로직 상 유리하다고 판단되는 부분을 중복된 형태로 가공 저장 하는 것을 의미합니다. MongoDB 에서는 이것을 서로 다른 document 를 내장형태로 저장 하는 방법으로 구현됩니다.

const postSchema = new Schema({
  postTitle: { type:String, reqruied:true, default:"제목 없음" },
  postTexts: { type:String, reqruied:true, default:"내용 없음" },
  comment: [{
    commentTexts: { type:String, required:true, defualt:"내용 없음" },
    commentOwner: { type:ObjectId }
  }]
});

단,
이러한 Denomalize 의 경우에는 MongoDB 고유의 Document 크기 제한인 16MB 에 걸릴 수 있으므로,
별도의 후속 처리를 해야지 안정적으로 사용할 수 있습니다.

Nesting 활용

Nesting 은 Denomalize 되어 있는 배열 정보를 수정하기 위한 기능 으로서 $ 키워드 를 사용합니다.

const postSchema = new Schema({
  postTitle: { type:String, reqruied:true, default:"제목 없음" },
  postTexts: { type:String, reqruied:true, default:"내용 없음" },
  comment: [{
    commentTexts: { type:String, required:true, defualt:"내용 없음" },
    commentOwner: { type:ObjectId }
  }]
});

위와 같은 구조로 만들어진 post document 에는 comment 라고 하는 배열 정보가 담겨 있습니다.
이러한 comment 의 특정한 comment 만 수정하기 위해서는 다음의 2가지 방법이 가능합니다.

  1. document 를 변수로 저장한 후 탐색, 수정, save()
  2. document 를 변수로 저장하지 않고, nesting 사용 하여 탐색과 동시에 수정
// 1번 방법

const patchCommentInPost = async (req, res) => {
  
  const post = await postModel.findById(postId);
  
  const targetIndex = undefined;
  
  post.forEach((value, key) => {
    if (value._id === commentId) return targetIndex = key;
  });
  post.comments[targetIndex] = content;
  post.save();
  
  // ... 이후 비즈니스 로직 실행
  
}

// 2번 방법

const patchCommentInPost = async (req, res) => {
  
  await postModel.updateOne(
    { "comment._id" : commentId }, // 특정한 comment 1개를 찾고
    { "comments.$.content" : content } // $ 키워드로 그 1개에 의 content 를 수정
  );
  
  // ... 이후 비즈니스 로직 실행
  
}

Thoery

사실,
여기서부터는 시기장조라고 생각하지만,
MongoDB 최적화 관련된 내용을 치면서 만난 색다른 포스트들이었기에 포함해보았습니다.

  1. Cold Data VS Warm Data VS Hot Data 란 ?
  2. 월간 10억 PV를 지지하기 위한 MongoDB Tip
  3. MongoDB 성능 최적화 전략
  4. MongoDB 성능 개선 및 팁 + 정규표현식 관련 성능
profile
2022년 12월 9일 부터 노션 페이지에서 작성을 이어가고 있습니다.

0개의 댓글