카테고리 없음

mongoose timestamp, lean()

불곰자리 2024. 8. 26. 16:25

mongooseMongoDBODM(Object Data Modeling) 라이브러리이다.

MongoDB는 유연한 데이터 모델(NoSQL)이어서 컬럼을 추가하거나 삭제하거나, 

저장할 수 있는 데이터의 형식이 정말 자유롭기 때문에 그만큼 부작용이 많다.

mongoose는 스키마와 모델을 정의해야하기 때문에 조금은 형식적으로 DB를 관리할 수 있다.

 

mongoose의 개요는 대략 이렇고, 나는 이 중 timestamp옵션과 lean() 함수에 대해 적으려고 한다.

 

timestamp

스키마 정의 시 timestamp 옵션을 사용하면 createdAt(생성 시각)과 updatedAt(갱신 시각) 값이 자동으로 저장된다.

데이터가 언제 생성되었고 언제 수정되었는지에 대한 정보는 프로젝트를 하다보면 빈번히 필요로 하는 정보인데 일일히 createdAt을 정의하는 것보다 옵션 설정으로 자동으로 저장할 수 있게 하는 것이 좋은 것 같다.

(스키마 변수 명).set('timestamps', true);
//또는
const (스키마 변수 명) = new Schema({
	// 속성 정의
}, {
	timestamps: true
});
//로 옵션 설정이 가능하다.
(스키마 변수 명).set('timestamps', { createdAt: false, updatedAt: true });
//둘 중 하나의 값만 저장하는 것도 가능하고

(스키마 변수 명).set('timestamps', { createdAt: "createDate", updatedAt: "updateDate" });
//이름을 변경하여 저장하는 것도 가능하다.

 

lean()

lean을 사용하면 mongoose 쿼리 실행 시 mongoose document로 반환하지 않고 바로 JavaScript Object 값으로 변환시켜준다. 이를 통해 쿼리 수행을 더 빨라지고 메모리를 집약적으로 사용할 수 있게 한다. mongoose documentJavaScript Object보다 무겁지만 상태 추적 등 여러 내부 기능이 존재하는데, 데이터의 수정 없이 쿼리를 전송한다면 lean을 사용하는 것이 좋다.

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

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

const normalDoc = await MyModel.findOne();
// `lean` 옵션을 사용할 수 있다면 `lean()`을 사용하자
const leanDoc = await MyModel.findOne().lean();

//v8Serialize는 모든 타입의 데이터를 buffer로 변환한다.
v8Serialize(normalDoc).length; // 180
v8Serialize(leanDoc).length; // 32, 5배 작다!

// mongoose document와 lean 옵션을 사용해 반환 받은 object의
// JSON 형태는 둘 다 크기가 같다.
// 이 추가적인 메모리는 Node.js 프로세스가 얼마나 많은 메모리를 사용하냐에 영향을 준다.
// (네트워크에 보내지는 데이터의 양X)
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true

 

다만 lean을 사용하면 다음과 같은 기능을 사용할 수 없다.

  • 변화 추적 (Change Tracking)
  • 캐스팅, 검증 (Casting and validation)
  • Getter, Setter
  • 가상 필드 (Virtuals)
  • save()

다음은 lean을 사용하지 않았을 때/사용했을 때의 gettersetter 결과이다.

// `Person` 모델을 정의한다. 2개의 직접 만든 getter과 가상 속성인 `fullName`을 가진다.
// lean 옵션이 설정되었다면 getter과 가상 속성에 접근할 수 없다.
const personSchema = new mongoose.Schema({
  firstName: {
    type: String,
    get: capitalizeFirstLetter
  },
  lastName: {
    type: String,
    get: capitalizeFirstLetter
  }
});

personSchema.virtual('fullName').get(function() {
  return `${this.firstName} ${this.lastName}`;
});

function capitalizeFirstLetter(v) {
  // 'bob' -> 'Bob'
  return v.charAt(0).toUpperCase() + v.substring(1);
}
const Person = mongoose.model('Person', personSchema);

// document로 생성하고 lean document로 가져온다.
await Person.create({ firstName: 'benjamin', lastName: 'sisko' });
const normalDoc = await Person.findOne();
const leanDoc = await Person.findOne().lean();

normalDoc.fullName; // 'Benjamin Sisko'
normalDoc.firstName; // `capitalizeFirstLetter()` getter의 영향으로 'Benjamin'가 된다.
normalDoc.lastName; // `capitalizeFirstLetter()` getter의 영향으로 'Sisko'가 된다.

leanDoc.fullName; // undefined
leanDoc.firstName; // 직접 지정한 getter가 작동하지 않아 'benjamin'가 된다.
leanDoc.lastName; // 직접 지정한 getter가 작동하지 않아 'sisko'가 된다.

 

populatevirtual populate는 가능하다.

populate는 밑의 코드를 예시로 들어 설명하자면

Group에서 쿼리를 실행하면 원래 members 필드의 값은 ObjectId로만 나오는데

populate를 실행하면 members 필드의 값을 Object로 바꿔서 데이터를 읽을 수 있게 한다.

// 모델 생성
const Group = mongoose.model('Group', new mongoose.Schema({
  name: String,
  members: [{ type: mongoose.ObjectId, ref: 'Person' }]
}));

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

// 데이터 초기화
const people = await Person.create([
  { name: 'Benjamin Sisko' },
  { name: 'Kira Nerys' }
]);

await Group.create({
  name: 'Star Trek: Deep Space Nine Characters',
  members: people.map(p => p._id)
});

// lean 쿼리 실행
const group = await Group.findOne().lean().populate('members');
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'

// `group`과 populate된 `member` 둘 다 lean이다.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false

 

virtual populate는 실제로 MongoDB에 저장되지는 않지만 어떤 계산된 속성을 사용해야할 때 사용하는 것 같다.

groupSchema.virtual('members', {
  ref: 'Person', // members 필드는 Person 스키마를 참조한다.
  localField: '_id', // Group 스키마에 저장된 _id를 기준으로 비교하여 가져온다.
  foreignField: 'groupId' // Person 스키마의 groupId가 _id와 비교된다.
});
// 모델 생성
const groupSchema = new mongoose.Schema({ name: String });
// `members`라는 가상 속성을 생성
groupSchema.virtual('members', {
  ref: 'Person', //참조 모델
  localField: '_id', //비교할 필드 (Group)
  foreignField: 'groupId' //비교 당할 필드(Person)
});

const Group = mongoose.model('Group', groupSchema);
const Person = mongoose.model('Person', new mongoose.Schema({
  name: String,
  groupId: mongoose.ObjectId
}));

// 데이터 초기화
const g = await Group.create({ name: 'DS9 Characters' });
await Person.create([
  { name: 'Benjamin Sisko', groupId: g._id },
  { name: 'Kira Nerys', groupId: g._id }
]);

// lean 쿼리 실행
const group = await Group.findOne().lean().populate({
  path: 'members',
  options: { sort: { name: 1 } }
});
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'

// `group`과 populate된 `members` 모두 mongoose doc이 아닌 lean이다.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false