[mongoose]Post_id와 User_id를 참조해서 갖고있는 Bookmark 문서에서 쿼리할 때의 문제점

김예진·2022년 1월 17일
0

사실 백엔드에서 큰 문제는 못 찾았다. 성능 저하에 대한 문제..라고 하면 populate를 가급적 쓰지 말아야한다고 하는데 2번이나 쓴다는 것? 이건 arregation으로 사용해보려고 했었기때문에 크게 문제(?)되지는 않는다.
다만 새글 리스트 페이지나 북마크 순으로 정렬하는 인기글 페이지, 카테고리 페이지 등 Post.find로 쿼리문을 시작하는데 유저가 북마크한 글을 조회할 때는 Bookmark.find로 쿼리문을 날리니 Post에서 쿼리문을 날렸을 때와 응답 결과 값이 조금 달랐다.

1. 지금 Bookmark schema

import mongoose from 'mongoose';

const Schema = mongoose.Schema;

const bookmarkSchema = new Schema({
  postId: {
    type: mongoose.Types.ObjectId,
    ref: 'Post',
    required: true,
    index: true,
  },
  userId: {
    type: mongoose.Types.ObjectId,
    ref: 'User',
    required: true,
    index: true,
  },
});

const Bookmark = mongoose.model('Bookmark', bookmarkSchema);

export default Bookmark;

Bookmark schema를 따로 만든 이유

:User 스키마에 북마크한 글의 id를 배열에 넣거나, Post 스키마에 해당 글을 북마크 한 유저의 id를 배열로 넣는 방법도 있다. 기획마다 달라지겠지만.. 나는 Post 스키마에 북마크한 유저의 id를 배열로 넣거나 지금처럼 Bookmark schema를 만드는 방법을 생각했는데. 우연히 블로그에서 배열에 유저를 담으면 for문을 돌아야하기 때문에 속도가 늦어진...다(?)는 글을 읽었다(블로그를 찾으려고하니까 또 안보여서 주소를 담지 못했다.) 아무튼 그렇다면 배열로 담는 것을 최대한 하지않으려고 Bookmark 문서를 만든 것.

2. Bookmark.find로 찾은 결과값(해당 유저가 북마크한 게시글 목록을 반환하는 값)

userDAO.js (쿼리문)

const findUserBookmark = async (user) => {
  const result = await Bookmark.find({ userId: user._id }).populate({
    path: 'postId',
    populate: { path: 'writer', select: 'nickname profileImage' },
  });
  //이렇게 쿼리문 날릴경우 postId { } 안에 또 한번 객체들이 담겨서 프론트에서 관리가 어려웠음
  return result;
};

POSTMAN 결과

{
    "status": "success",
    "result": [
        {
            "_id": "61e3e6aa56c2a3dcb20e064d",
            "postId": {
                "count": {
                    "bookmark": 1,
                    "comment": 0
                },
                "_id": "61e29a3e141f3af2373b145f",
                "postId": 20,
                "writer": {
                    "_id": "61e27c68497feff6162ae4f7",
                    "nickname": "테스트계정",
                    "profileImage": "basicProfileImage.png"
                },
                "category": "poetry",
                "content": "123123asds",
                "createdAt": "2022-01-15 18:55:53",
                "info_title": "",
                "info_url": "",
                "userBookmark": [
                    "61e3e68a56c2a3dcb20e063a"
                ],
                "__v": 0
            },
            "userId": "61e3e68a56c2a3dcb20e063a",
            "__v": 0
        },
        {
            "_id": "61e3e6ab56c2a3dcb20e0651",
            "postId": {
                "count": {
                    "bookmark": 2,
                    "comment": 0
                },
                "_id": "61e29a3d141f3af2373b145a",
                "postId": 19,
                "writer": {
                    "_id": "61e27c68497feff6162ae4f7",
                    "nickname": "테스트계정",
                    "profileImage": "basicProfileImage.png"
                },
                "category": "poetry",
                "content": "123123asds",
                "createdAt": "2022-01-15 18:55:53",
                "info_title": "",
                "info_url": "",
                "userBookmark": [
                    "61e27c68497feff6162ae4f7",
                    "61e3e68a56c2a3dcb20e063a"
                ],
                "__v": 0
            },
            "userId": "61e3e68a56c2a3dcb20e063a",
            "__v": 0
        },
        {
            "_id": "61e3e6ac56c2a3dcb20e0655",
            "postId": {
                "count": {
                    "bookmark": 2,
                    "comment": 0
                },
                "_id": "61e29a3c141f3af2373b1455",
                "postId": 18,
                "writer": {
                    "_id": "61e27c68497feff6162ae4f7",
                    "nickname": "테스트계정",
                    "profileImage": "basicProfileImage.png"
                },
                "category": "poetry",
                "content": "123123asds",
                "createdAt": "2022-01-15 18:55:53",
                "info_title": "",
                "info_url": "",
                "userBookmark": [
                    "61e27c68497feff6162ae4f7",
                    "61e3e68a56c2a3dcb20e063a"
                ],
                "__v": 0
            },
            "userId": "61e3e68a56c2a3dcb20e063a",
            "__v": 0
        },
        {
            "_id": "61e53c0cecd26da9eb16a368",
            "postId": {
                "image": {
                    "info_image": "1642325707433.png",
                    "originalImageName": "5.png"
                },
                "count": {
                    "bookmark": 2,
                    "comment": 0
                },
                "_id": "61e3e6cd56c2a3dcb20e065e",
                "postId": 21,
                "writer": {
                    "_id": "61e3e68a56c2a3dcb20e063a",
                    "nickname": "테스트1",
                    "profileImage": "basicProfileImage.png"
                },
                "category": "essay",
                "content": "teste",
                "createdAt": "2022-01-16 18:31:37",
                "info_title": "",
                "info_url": "",
                "userBookmark": [
                    "61e3e68a56c2a3dcb20e063a",
                    "61e3e68a56c2a3dcb20e063a"
                ],
                "__v": 0
            },
            "userId": "61e3e68a56c2a3dcb20e063a",
            "__v": 0
        },
        {
            "_id": "61e53c12ecd26da9eb16a36c",
            "postId": {
                "image": {
                    "info_image": "1642325707433.png",
                    "originalImageName": "5.png"
                },
                "count": {
                    "bookmark": 2,
                    "comment": 0
                },
                "_id": "61e3e6cd56c2a3dcb20e065e",
                "postId": 21,
                "writer": {
                    "_id": "61e3e68a56c2a3dcb20e063a",
                    "nickname": "테스트1",
                    "profileImage": "basicProfileImage.png"
                },
                "category": "essay",
                "content": "teste",
                "createdAt": "2022-01-16 18:31:37",
                "info_title": "",
                "info_url": "",
                "userBookmark": [
                    "61e3e68a56c2a3dcb20e063a",
                    "61e3e68a56c2a3dcb20e063a"
                ],
                "__v": 0
            },
            "userId": "61e3e68a56c2a3dcb20e063a",
            "__v": 0
        }
    ]
}

결과 요약

{ "status" : "success",
"result" : [ {bookmark_id데이터1 : 값 , post_id데이터1 : {객체로 된 값}, user_id데이터1 : {객체로 된 값},{bookmark_id데이터2 : 값 , post_id데이터2 : {객체로 된 값}, user_id데이터2 : {객체로 된 값}   ]

-> result : [ ] 배열 안에 Bookmark 문서에 저장된 객체 데이터들이 있고 하나의 bookmark 객체 데이터들은 각각 postId와 userId를 담고 있다. 프론트 입장에서는 백엔드로부터 받은 data안에서 result안에서 postId안으로 들어가서 각각의 내용들을 접근해야하는것(data.result.postId.count..)

+) 추가적으로 bookmark.find를 해도 아래 post.find한 것과 같은 결과값을 '만들어'줄 수는 있겠지만 1시간이 넘도록 구글링하고 생각을 해봐도 방법을 찾지못했다..(....ㅜㅜ)

3. Post.find로 찾은 결과값 (게시글 목록을 반환하는 값)

userDAO.js (쿼리문)

const findUserBookmarkTest = async (user) => {
  //post 문서에서 찾아서 그런지 리스트 페이지와 똑같이 결과값이 반환되어 프론트에서 관리하기 편했음.
  // 서버의 성능에 대해서 공부하고 뭐가 더 좋은지 찾아보자. 객체중에서 find하는 것이 좋은지, userBookmark 배열 안에서 find하는 것이 좋은지
  const result = await Post.find({ userBookmark: user._id }).populate(
    'writer',
    'nickname profileImage'
  );
  return result;
};

POSTMAN 결과

{
    "status": "success",
    "result": [
        {
            "count": {
                "bookmark": 2,
                "comment": 0
            },
            "_id": "61e29a3c141f3af2373b1455",
            "postId": 18,
            "writer": {
                "_id": "61e27c68497feff6162ae4f7",
                "nickname": "테스트계정",
                "profileImage": "basicProfileImage.png"
            },
            "category": "poetry",
            "content": "123123asds",
            "createdAt": "2022-01-15 18:55:53",
            "info_title": "",
            "info_url": "",
            "userBookmark": [
                "61e27c68497feff6162ae4f7",
                "61e3e68a56c2a3dcb20e063a"
            ],
            "__v": 0
        },
        {
            "count": {
                "bookmark": 2,
                "comment": 0
            },
            "_id": "61e29a3d141f3af2373b145a",
            "postId": 19,
            "writer": {
                "_id": "61e27c68497feff6162ae4f7",
                "nickname": "테스트계정",
                "profileImage": "basicProfileImage.png"
            },
            "category": "poetry",
            "content": "123123asds",
            "createdAt": "2022-01-15 18:55:53",
            "info_title": "",
            "info_url": "",
            "userBookmark": [
                "61e27c68497feff6162ae4f7",
                "61e3e68a56c2a3dcb20e063a"
            ],
            "__v": 0
        },
        {
            "count": {
                "bookmark": 1,
                "comment": 0
            },
            "_id": "61e29a3e141f3af2373b145f",
            "postId": 20,
            "writer": {
                "_id": "61e27c68497feff6162ae4f7",
                "nickname": "테스트계정",
                "profileImage": "basicProfileImage.png"
            },
            "category": "poetry",
            "content": "123123asds",
            "createdAt": "2022-01-15 18:55:53",
            "info_title": "",
            "info_url": "",
            "userBookmark": [
                "61e3e68a56c2a3dcb20e063a"
            ],
            "__v": 0
        },
        {
            "image": {
                "info_image": "1642325707433.png",
                "originalImageName": "5.png"
            },
            "count": {
                "bookmark": 2,
                "comment": 0
            },
            "_id": "61e3e6cd56c2a3dcb20e065e",
            "postId": 21,
            "writer": {
                "_id": "61e3e68a56c2a3dcb20e063a",
                "nickname": "테스트1",
                "profileImage": "basicProfileImage.png"
            },
            "category": "essay",
            "content": "teste",
            "createdAt": "2022-01-16 18:31:37",
            "info_title": "",
            "info_url": "",
            "userBookmark": [
                "61e3e68a56c2a3dcb20e063a",
                "61e3e68a56c2a3dcb20e063a"
            ],
            "__v": 0
        }
    ]
}

결과 요약

{ "status" : "success",
"result" : [ {post 객체데이터1},{post 객체데이터2}, {post 객체데이터3} ]

-> result : [ ] 배열 안에 Bookmark 문서에 저장된 객체 데이터들이 있고 하나의 post 객체 데이터들에는 게시글에 대한 정보를 갖고있다. 모든 게시글 리스트 반환 값들이 이런 형태로 되어있었기 때문에 프론트 입장에서는 이런 데이터를 기대하고 있다가 위와 같이 postId 객체로 또 접근해야하니 당황했을 것..

배열에서 검색하는게 느리다는 글을 봐서 배열에서 검색하는 일을 피하고자 문서를 하나 더 만든건데 값이 달라져서 난처해지는 일이 생기다니... 배열에서 find하는 것이 정말 좋지 않는걸까? 쿼리 성능에 영향을 크게 미칠까? 많은 차이가 날까? 그럼 어떨때 차이가 날까? 찾아보니 원하는 답은 아직 찾지 못했다.

그러나 디비는 인덱스를 얼마나 잘 지정하느냐에 따라 검색 성능을 늘릴 수 있는 걸로 알고 있고, 몽고디비 역시 그러하다고 하므로 배열 안에 객체를 저장하더라도 인덱스를 잘 지정하면 되지 않을까? 했는데.

출처 : https://www.zerocho.com/category/MongoDB/post/57a6faddc90c5815005babc3

인덱스는 일반 필드 뿐만 아니라, 객체 값을 가지는 필드의 내부 속성에도 지정할 수 있습니다. 예를 들어 { name: { first: 'Zero', last: 'Cho' } } 가 있을 경우, createIndex({ 'name.last': 1 }); 이렇게 인덱스 지정이 가능합니다.
참고로 _id 필드는 자동으로 인덱스 설정이 되어 있습니다. 웬만한 경우면 _id를 사용해서 조회하는 게 좋습니다.

...배열 안에 user_id를 담고 있으므로 이미 인덱스로 설정이 된 것 아닌가? 하는 생각이 들었다. _id로 조회하라는 것이 findById를 사용하라는 것 같기도 하고.. 일단은 Post에서 find로 접근하는 것으로 하고, 이렇게 되면 bookmark 문서가 있지 않아도 되기때문에 다시 삭제를 해야겠다..!

profile
Backend Developer 🌱 벨로그 내용을 티스토리로 이사중~!

0개의 댓글