# Mongoose lean 이야기

mongoose를 이용해서 데이터를 조회한 후 해당 데이터를 json 형태로 변환해서 바로 response를 보낼 때에는 별 문제가 없지만, 해당 데이터를 spread opreator로 분해를 하거나 unit 테스트 할 때 종종 원치 않는 데이터가 포함 된 것을 확인할 수 있습니다.

예상 했던 데이터와 실제 데이터에는 차이가 있습니다.

예상 했던 것

{
    "name": "0ams",
    "position": "SE",
    "roles": [
      "ADMIN"
    ]
}
1
2
3
4
5
6
7

실제로 담겨 있는 것

{
  "$__": {
    "$options": {
      "defaults": true
    },
    "$setCalled": {},

    // ... (중략)

    "backup": {
      "activePaths": {
        "default": {
          "_id": true
        },
        "modify": {
          "name": true,
          "position": true,
          "roles": true
        }
      }
    },
    "cachedRequired": {},

    // ... (중략)

    "getters": {},
    "inserting": true,
    "pathsToScopes": {
      "roles": "000000000000000000000000"
    },
    "session": null,
    "strictMode": true,
    "validating": null,
    "wasPopulated": false
  },
  "$locals": {},
  "$op": null,
  "_doc": {
    "__v": 0,
    "_id": "000000000000000000000000",
    "name": "0ams",
    "position": "SE",
    "roles": [
      "ADMIN"
    ]
  },
  "isNew": false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# Mongoose Document

그 이유는 mongoose Query를 통해서 조회 된 데이터는 단순한 Plain Old JavaScript Objects(POJO)가 아닌 Moogoose Document형태이기 때문.

Mongoose Document는 아래와 같은 것들을 포함하게 된다.

  • Change tracking
  • Casting and validation
  • Getters and setters
  • Virtuals
  • save()

이런 식의 추가 정보와 메서드들을 포함하고 있기 때문에 Query 결과 값을 가지고 수정한 후 save() 메서드를 통해서 수정 된 내용을 DB에 반영할 수 도 있는 것이다

const findAndUpdateName = async (oldName, newName) => {
  const user = await User.findOne({ name: oldName });
	user.name = newName;
	const savedUser = await user.save();

  return savedUser;
};
1
2
3
4
5
6
7

# Plain Old Javascript Object (POJO)

그런데 만약 mongoose에서 붙여주는 다른 정보나 메서드를 사용하지 않는다면 굳이 Mongoose Document를 사용 할 필요가 없다. 이럴때 사용 하는 것이 바로 lean()이다. lean의 경우는 mongoose 3.0.0에서 처음 도입된 method로 바로 POJO를 반환 하는 메소드이다.

# toObejct()와 비교,

toObject() 메서드를 이용하는 경우 Query 결과를 Mongoose Document로 만들기 위한 과정을 거쳤다가 다시 POJO로 변환되는 반면, lean() 메서드를 이용할 경우 중간 과정 없이 바로 POJO를 반환하게 된다.

# toObject() 메서드 이용

const findByName = async (name) => {
  const user = await User.findOne({ name });
	return user.toObejct();
};
1
2
3
4

# lean() 메서드 이용

const findByName = async (name) => {
  const user = await User.findOne({ name }).lean();
	return user;
};
1
2
3
4

# Mongoose Document와 POJO의 용량 비교

10배 이상의 차이를 가지게 된다.

const schema = new mongoose.Schema({ name: String });
const Model = mongoose.model('Test', schema);

await Model.create({ name: 'test' });

// Module that estimates the size of an object in memory
const sizeof = require('object-sizeof');

const normalDoc = await Model.findOne();
// To enable the `lean` option for a query, use the `lean()` function.
const leanDoc = await Model.findOne().lean();

sizeof(normalDoc); // >= 1000
sizeof(leanDoc); // 86, 10x smaller!

// In case you were wondering, the JSON form of a Mongoose doc is the same
// as the POJO. This additional memory only affects how much memory your
// Node.js process uses, not how much data is sent over the network.
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc.length); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# lean() 메서드의 사용

물론 lean() 메서드를 이용해서 얻은 데이터는 Mongoose Document가 아니기 때문에 추가 정보나 save()와 같은 메서드들은 사용이 불가능하다. 그래서 아래와 같은 경우 케이스에서 이용하면 좋다.

  • 단순 데이터 조회
  • 다른 Document를 수정 또는 변경 하기 위해 사용 되는 metadata로 사용 되는 경우

추가로, 기존에 toObject() 메서드를 사용했던 코드들을 lean() 메서드로 수정한다면 효율 개선에 도움될 수 있다

# Add Info

lean의 경우, toObject()와 같은 mongoose 동작이 제외 되기 때문에, id: string의 값을 못얻는다. 이런 부분을 해소 하기 위해 여러 플러그인을 제공하고 있어 아래를 참고 하여 설정하면 된다.

# 참고

Last Updated: 11/7/2022, 1:57:59 AM