DB상 동시 수정이 필요한 경우 조회 -> 수정 시 시간차로 인한 오류가 발생할 수 있음.
따라서 MongoDB Pipeline 방식을 활용하여 현재 데이터 조회하고 이를 기반으로 수정 값을 업데이트 하는 방법을 은행 출금 알고리즘에 적용함.
(구글에 검색했을 때에는 조회 -> 수정 밖에 안나옴. 못찾은 것일수도.. (99%))
const account = require("../model/account");
...
const withdrawl = function(req, res, next) {
const { amount } = req.body;
const { accNum } = req.fullAccInfo;
async.waterfall([
(cb) => {
account.find({
accNum
})
.then(result => cb(null, result))
.catch(e => cb(e));
},
(prevVal, cb) => {
account.updateOne({
accNum
}, {
remain: prevVal.remain - amount
})
.then(result => cb(null, result))
.catch(e => cb(e));
}
], (err, result) => {
if (err || result.modifiedCount < 1) {
console.error(err);
return res.status(500).end(`Internal Server Error`);
}
next();
})
}
위 과정대로 작성시 발생하는 문제점이 있었다:
잔액: 10,000원 --------------------
7,000원 출금요청(요청A) ---------------
5,000원 출금요청(요청B) ---------------
요청 A 출금 가능여부 확인됨--------------
요청 B 출금 가능여부 확인됨 -------------
요청 A 출금됨 (잔액: 3,000원) -------------
요청 B 출금됨 (잔액: 5,000원) -------------
==> 최종잔액 (5,000원) > (은행입장) 실제 고객 보유 금액보다 2,000원 손해
==> mongoose updateOne 정식 문서 중 pipeline 지원 여부를 확인하여 pipeline을 사용한 즉시변경 방식을 사용하기로 결정.
const account = require("../model/account");
...
const withdrawl = function(req, res, next) {
const { amount } = req.body;
const { accNum } = req.fullAccInfo;
account.updateOne({
accNum
}, [
{
$set: { remain: { $subtract: [ "$remain", amount ] } }
}
])
}
조회과정 없이 데이터를 실시간으로 수정할 수 있음. 실시간 수정 덕분에 '조회 -> 데이터 수정중 값 변경으로 인한 결과 상이'를 막을 수는 있지만, 아직 subtract 과정 이후 -로 처리될 때에는 수정을 하면 안되는데, 아직 이를 해결하지 못해 pipeline상에서 한번에 처리하는 방법을 알아보아야 할듯 함.