[mongoose] [error] findOneAndUpdate $inc가 두배로 되는 현상

개발하는 라마·2021년 3월 29일
0

해결 방법

mongoose의 query 사용 시, callback과 promise를 동시에 쓰면 두번 실행됨

  • mongoose의 query는 promise 가 아님.
  • query는 .then() 함수를 가짐 (for co and async/await as a convenience)
  • 하지만 Promise와 다르게, 쿼리의 .then()은 여러 번 실행 가능
  • callback과 promise를 쿼리에 함께 쓰면 두번씩 실행되는 결과를 갖게 됨


await 를 쓰려면 await Model.findOneAndUpdate( query, doc, options ) 를 쓰거나
await를 지우고 Model.findOneAndUpdate( query, doc, optiosn, callback) 을 쓴다.


구구절절

mongoose 로 db update 중에 에러를 만났다.
오늘의 년월일 구하기 -> 해당 년월일에 해당하는 document의 count++ 해주기
나의 잘못은 mongoose document에 아주 명확하게 써있었다.
(그래서 교훈은 항상 document 읽는 것을 게을리 하지 말자이다.)

export const updateVisitorCount = async (req, res) => {
  let today = getYearMonthDate();
  console.log(new Date());
  await Visitor.findOneAndUpdate(
    { date: today },
    { $inc: { count: 1 } },
    { new: true, upsert: true },
    (err, visitor) => {
      if (err) {
        console.log(`Something wrong when updating data : ${err}`);
        return res.status(500).json(err);
      }
      return res.status(200).json(visitor);
    }
  );
};

{ $inc: { count: 1 } } 를 통해서 count가 한번만 증가할 줄 알았는데
Postman으로 테스트 해보니 2씩 증가했다

mongoose.set('debug', true) 를 통해 다음과 같은 결과를 확인했다.

findOneAndUpdate

  • await가 있을 땐 2번씩 로그가 찍히고
  • await가 없을 땐 정상적으로 한번씩 업데이트가 된다.

나와 같은 에러를 겪는 사람이 있었고 코멘트는 다음과 같았다.. mongoose guide를 읽어보라고.
참고 : 깃헙이슈

Using a callback and a promise simultaneously will result in the query being executed twice. You should be using one or the other, but not both. Check out the mongoose guide here

그래서 미뤄뒀던 Mongoosejs Queries 가이드를 찬찬히 읽어봤습니다.


Queries

  • mongoose 모델은 CRUD 명령을 위한 다양한 static helper functions를 제공함
  • 각각의 function들은 mongoose의 Query obj를 return 함
    • ex) Model.find(), Model.findOneAndUpdate()...
  • mongoose query는 두가지 방식으로 실행될 수 있음
    1. callback function
      • 만약 callback으로 실행시키면 mongoose는 쿼리를 비동기적으로 실행시키고, 그 결과를 콜백으로 보내(pass)줄 것
    2. Promise로도 사용 가능
      • query는 .then() function 도 가지고 있음

1. Executing

  • 만약 callback 으로 쿼리를 실행시키면, 쿼리는 JSON document로 specify됨.
  • JSON document의 문법은 MongoDB Shell과 동일함
  • mongoose는 쿼리를 실행시키고나서, 그 결과를 callback으로 넘겨줌
  • mongoose에서 모든 callback은 동일한 패턴을 가짐
    • callback(error, result)
    • query 실행 시 에러 발생하면 error 파라미터에 error document가 들어가고 result===null
    • query 성공적으로 실행되면, error === null, result에 query의 결과가 담김
const Visitor = mongoose.model('Visitor', visitorSchema);
Visitor.findOne({'regDate': '210329'}, 'regDate count', (err, visitor) => {
	if (err) return handlerError(err);
        console.log(`${visitor.regDate} : ${visitor.count}`);
});
  • 만약에 콜백이 없을 시? 쿼리는 query 자료형의 변수가 됨
    • chaining syntax를 이용해 쿼리를 계속 build up 해갈 수 있음.
    • exec(callback) 을 통해 실행 가능
const query = Visitor.findOne({'regDate': '210329'});
query.select('egDate count`);
//execute the query at a later time
query.exec( (err, visitor) => {
	if (err) return handlerErr(err);
        console.log(`${visitor.regDate} : ${visitor.count}`);
});

Person.
  find({ occupation: /host/ }).
  where('name.last').equals('Ghost').
  where('age').gt(17).lt(66).
  where('likes').in(['vaporizing', 'talking']).
  limit(10).
  sort('-occupation').
  select('name occupation').
  exec(callback);
  

2. Queries are not Promises

  • mongoose의 query는 promise 가 아님.
  • query는 .then() 함수를 가짐 (for co and async/await as a convenience)
  • 하지만 Promise와 다르게, 쿼리의 .then()은 여러번 실행할 수 있음
const query  = SomeModel.updateMany({}, {isDeleted: true}, function() {
  console.log('Update 1');
});

q.then(() => console.log('Update 2'));
q.then(() => console.log('Update 3'));
  • callback과 promise를 쿼리에 함께 쓰면 두번씩 실행되는 결과를 갖게 됨
    (Don't mix using callbacks and promises with queries, or you may end up with duplicate operations)

  • await와 쿼리에 callback이 있으면 query가 즉각적으로 실행되게 되고, 그러면 바로 then() 함수가 실행되어서 한번 더 실행되게 됩니다.

const someftn = async (req, res) => { 
	await SomeModel.findOneAndUpdate(
    		{name: 'sth'}, 
    		{value: 'will be changed'}, 
            	(err, someModel) => { console.log(err, someModel) }
	);
}
  • 위에 처럼 되면 query obj가 생성된 후
    1. callback function이 있으니까 바로 query가 실행되고
    2. promise도 실행되서
    3. 총 2번 쿼리가 실행되는 결과를 초래함

profile
I love reading sth.

0개의 댓글