Mongoose Atomic Update 방식을 찾아서

yejineee·2020년 11월 27일
3

🎯 해결하고 싶었던 것

Account 컬렉션은 transaction 도큐먼트의 objectId를 자신의 필드인 transactions에 추가하여야 한다.

그 과정은 다음 두 가지 일을 해야 한다.

  1. Account Object Id로 Account 도큐먼트를 찾는다.

  2. 그 도큐먼트의 trasactions에 새롭게 만든 transaction 도큐먼트의 Object Id를 추가한다.

공동가계부를 관리하게 된다면, 동시에 여러 사람이 거래내역을 추가, 삭제 등의 요청을 할 수 있다.

그러므로, 우리는 모든 디비에 대한 접근 요청이 유효하게 만들어주는 것을 고려해야 한다.

몽구스에서 Atomic Update 를 하기 위한 방법을 찾아보자.

✅ Update하는 여러 방법들

우선 update를 하는 여러 방법들을 찾아보았다.

  1. in-memory update

    const targetAccount = await this.findById(accountObjId).exec();
    targetAccount.transactions.push(transactionObjId);
    await targetAccount.save();

    find를 해서 가져온 도큐먼트를 update한 후 save하는 방식이다. 이 방식은 도큐먼트를 가져온 후, save가 되기 전에 다른 쿼리에 의해 도큐먼트가 변경될 수 있다.

  2. findOneAndUpdate()

User.findByIdAndUpdate(id, { $push: { createdEvents: eventId } }).exec();

몽구스 공식문서에 따르면 findOndAndUpdate는 atomic하다고 한다.

With the exception of an unindexed upsert, findOneAndUpdate() is atomic.

$push operator는 배열에 값을 추가한다. 이 방식으로 id에 해당하는 도큐먼트를 찾아서 createdEvents 배열에 eventId를 추가할 수 있게 된다.

  1. Update

update도 마찬가지로 atomic하다. 첫 번째 인자로 필터를 넣어주고, 두 번째 인자로 실행할 도큐먼트를 입력해주면 된다. findOneAndUpdate와 다른 점은 findOneAndUpdate는 도큐먼트를 리턴해주지만, update는 도큐먼트를 리턴하지 않는다.

PersonModel.update(
    { _id: person._id }, 
    { $push: { friends: friend } },
    done
);

✔️ 우리가 선택한 방법

this.findByIdAndUpdate(accountObjId, {
    $push: { transactions: transactionObjId },
}).exec();

findOneAndUpdate는 atomic하다고 한다. 우리는 Account 컬렉션에서 Object Id를 찾아서 update를 해줘야하므로, findByIdAndUpdate 가 적합하다고 생각하였다. findByIdAndUpdate 는 내부적으로 findOneAndUpdate를 불러준다고 하니, findByIdAndUpdate 또한 atomic 할 것 이라고 생각한다.

🆕 그 외 새롭게 알게된 것!

  • exec

update만 하면 쿼리가 실행되는 줄 알았는데, 공식문서의 예제를 보면, exec() 를 마지막에 불러오는게 있었다. exec를 왜 해주는 걸까???

그냥 query문을 호출하면, 실제적으로 쿼리가 실행되는 것이 아니다.

콜백 함수의 유무 에 따라 실행이 달라지게 된다.

콜백 함수가 있으면 실행이 되고, 콜백 함수가 없으면 쿼리를 리턴하게 된다.

A.findByIdAndUpdate(id, update, options, callback) // executes
A.findByIdAndUpdate(id, update, callback) // executes
A.findByIdAndUpdate(id, update, options)  // returns Query
A.findByIdAndUpdate(id, update)           // returns Query
A.findByIdAndUpdate()                     // returns Query

따라서, 콜백 함수 없이 바로 쿼리를 실행시키려면, update() 호출 후 exec()를 호출해야 한다.

const q = Model.where({ _id: id });
q.update({ $set: { name: 'bob' }}).update(); // not executed
q.update({ $set: { name: 'bob' }}).exec(); // executed
  • upsert option

upsert를 true로 주면, document가 있으면 update를 하고, 없으면, filter와 update를 결합하여 생성한다.

const filter = { name: 'Will Riker' };
const update = { age: 29 };

await Character.countDocuments(filter); // 0

let doc = await Character.findOneAndUpdate(filter, update, {
  new: true,
  upsert: true // Make this update into an upsert
});
doc.name; // Will Riker
doc.age; // 29

출처

Push items into mongo array via mongoose

https://ddcode.net/2019/05/12/is-mongodb-findoneandupdate-thread-safe/

https://mongoosejs.com/docs/tutorials/findoneandupdate.html

0개의 댓글