2014-07-10 3 views
1

내 데이터 모델에 계정이 있고 계정에 신용 거래가 있습니다. TransactionAccount에 추가되면, 다음과 같은 현상이 발생mongoose를 사용하여 하위 문서 배열에 복잡한 추가

var TransactionSchema = new Schema({ 
     amount: Number, 
     added: Date 
    }), 
    AccountSchema = new Schema({ 
     owner: ObjectId, 
     balance: Number, 
     transactions: [TransactionSchema] 
    }); 

:

  • transactions은 새가 푸시가
  • transactions에 의해 정렬됩니다 나는 하위 문서로 이러한 거래를 디자인했다 날짜 (추후 표시)
  • balance은 모두의 합계로 설정됩니다. transactions

나는 이것을 Schema.methods -function에 넣었다. 저장하기 전에 자바 스크립트에서 위와 같은 작업을 수행했다. 그러나 한 번에 여러 삽입에 대해 안전한지 확신 할 수 없습니다.

원자 또는 일종의 트랜잭션 업데이트를 사용하는 방법을 몽구스에서 어떻게 해결할 수 있습니까? SQL에서는 트랜잭션 만 수행하지만 MongoDB에서는 할 수 없으므로 transactionsbalance이 항상 올바른지 확인하는 방법은 무엇입니까?

답변

4

업데이트 작업을 모두 결합하는 유일한 방법 인이 작업을 모두 결합하는 update 호출로이 모든 작업을 수행 할 수 있습니다. 이 때문에 2.6에서 2.4에서 추가 및 업데이트 된 $push에 대한 $sort 수정을 사용한다는 것을

var transaction = { 
    amount: 500, 
    added: new Date() 
}; 

Account.update({owner: owner}, { 
     // Adjust the balance by the amount in the transaction. 
     $inc: {balance: transaction.amount}, 
     // Add the transaction to transactions while sorting by added. 
     $push: {transactions: { 
      $each: [transaction], 
      $sort: {added: 1} 
     }} 
    }, callback); 

참고 : 대신 당신이 변화의 양 balance를 업데이트, 업데이트가 진행되는 동안 트랜잭션을 합산하지 않는다 최근 빌드를 사용해야합니다.

+0

따라 갔다. 배열에 지속적으로 추가하거나 많은 양의 트랜잭션을 추가하는 지혜에 대한 아이디어가있었습니다. –

2

대답의 동일한 줄을 따라 가면서 펀치를 때리면 나에게 설명이 더 오래 걸렸으므로 시간이 좀 걸립니다.

한가지 더 조정하면 매우 똑같습니다. 균형에 $inc 연산자를 사용하여 트랜잭션을 수행하면, 오히려 재 계산보다는 코드 때문에 기본적으로이 블록을 원하는 것입니다 :

bucket.find({ 
    "account": accId, "owner": owner, "day": day 
    }).upsert().updateOne(
    { 
     "$inc": { "balance": amount }, 
     "$push": { 
     "transactions": { 
      "$each": [{ "amount": amount, "added": date }], 
      "$sort": { "added": -1 } 
     } 
     } 
    } 
); 

다른 부분은 버킷 개념이다 "조정할". 기본적으로 배열을 사용하여이 작업을 수행하는 것이 좋은 생각이며 $inc을 사용하면 트랜잭션의 해당 부분이 원 자성이되며 문제는 배열에 많은 항목을 원하지 않는다는 것입니다. 그리고 이것은 시간이 지남에 따라 상당히 증가 할 것입니다.

이 작업을 수행하는 가장 좋은 방법은 해당 배열에 너무 많은 항목을 보관하고 이들을 "양동이"결과로 제한하는 것입니다. 단 하나의 "균형"지점을 동기화 상태로 유지하려는 "시도"를 위해 여기에 좀 더 많은 처리 기능을 추가했습니다. 실제로는 결과적으로 여러 업데이트가 트랜잭션에 바인딩되지 않으므로 주기적으로 확인하려고합니다. .

하지만 "버킷"에 대한 업데이트는 원상태입니다. 마일리지는 실제 구현에 따라 달라질 수 있지만, 여기에 essentinal 데모 코드 :

var async = require('async'), 
    mongoose = require('mongoose'), 
    Schema = mongoose.Schema; 

mongoose.connect('mongodb://localhost/shop'); 

var ownerSchema = new Schema({ 
    name: String, 
    email: String, 
    accounts: [{ type: Schema.Types.ObjectId, ref: "Account" }] 
}); 

var transactionSchema = new Schema({ 
    amount: Number, 
    added: Date 
}); 

var recentBucketSchema = new Schema({ 
    _id: { type: Schema.Types.ObjectId, ref: "AccountBucket" }, 
    day: Date 
}); 

var accountSchema = new Schema({ 
    owner: { type: Schema.Types.ObjectId, ref: "Owner" }, 
    balance: { type: Number, default: 0 }, 
    recent: [recentBucketSchema] 
}); 

var accountBucketSchema = new Schema({ 
    day: Date, 
    account: { type: Schema.Types.ObjectId, ref: "Account" }, 
    owner: { type: Schema.Types.ObjectId, ref: "Owner" }, 
    balance: { type: Number, default: 0 }, 
    transactions: [transactionSchema] 
}); 

var Owner = mongoose.model("Owner", ownerSchema); 
var Account = mongoose.model("Account", accountSchema); 
var AccountBucket = mongoose.model("AccountBucket", accountBucketSchema); 

var owner = new Owner({ name: "bill", emal: "[email protected]" }); 
var account = new Account({ owner: owner }); 
owner.accounts.push(account); 

var transact = function(accId,owner,amount,date,callback) { 

    var day = new Date(
    date.valueOf() - (date.valueOf() % (1000 * 60 * 60 * 24))); 

    var bucket = AccountBucket.collection.initializeOrderedBulkOp(); 
    var account = Account.collection.initializeOrderedBulkOp(); 

    bucket.find({ 
    "account": accId, "owner": owner, "day": day 
    }).upsert().updateOne(
    { 
     "$inc": { "balance": amount }, 
     "$push": { 
     "transactions": { 
      "$each": [{ "amount": amount, "added": date }], 
      "$sort": { "added": -1 } 
     } 
     } 
    } 
); 

    bucket.execute(function(err,response) { 
     if (err) throw err; 

     var upObj = { 
     "$inc": { "balance": amount } 
     }; 

     if (response.nUpserted > 0) { 
     var id = response.getUpsertedIds()[0]._id; 
     upObj["$push"] = { 
      "recent": { 
      "$each": [{ "_id": id, "day": day }], 
      "$sort": { "day": -1 }, 
      "$slice": 30 
      } 
     }; 
     } 

     console.log(JSON.stringify(upObj, undefined, 4)); 

     account.find({ "_id": accId }).updateOne(upObj); 
     account.execute(function(err,response) { 
     callback(err,response); 
     }); 
    } 
); 
}; 

mongoose.connection.on("open",function(err,conn) { 

    async.series([ 

    function(callback) { 
     async.each([Owner,Account,AccountBucket],function(model,complete) { 
     model.remove(function(err) { 
      if (err) throw err; 
      complete(); 
     }); 
     },function(err) { 
     if (err) throw err; 
     callback(); 
     }); 
    }, 

    function(callback) { 
     async.each([account,owner],function(model,complete) { 
     model.save(function(err) { 
      if (err) throw err; 
      complete(); 
     }); 
     },function(err) { 
     if (err) throw err; 
     callback(); 
     }); 
    }, 

    function(callback) { 
     var trandate = new Date(); 
     transact(account._id,owner._id,10,trandate,function(err,response) { 
     if (err) throw err; 

     console.log(JSON.stringify(response, undefined, 4)); 
     callback(); 
     }); 
    }, 

    function(callback) { 
     var trandate = new Date(); 
     trandate = new Date(trandate.valueOf() + (1000 * 60 * 60 * 1)); 
     transact(account._id,owner._id,-5,trandate,function(err,response) { 
     if (err) throw err; 

     console.log(JSON.stringify(response, undefined, 4)); 
     callback(); 
     }); 
    }, 

    function(callback) { 
     var trandate = new Date(); 
     trandate = new Date(trandate.valueOf() - (1000 * 60 * 60 * 1)); 
     transact(account._id,owner._id,15,trandate,function(err,response) { 
     if (err) throw err; 

     console.log(JSON.stringify(response, undefined, 4)); 
     callback(); 
     }); 
    }, 

    function(callback) { 
     var trandate = new Date(); 
     trandate = new Date(trandate.valueOf() - (1000 * 60 * 60 * 24)); 
     transact(account._id,owner._id,-5,trandate,function(err,response) { 
     if (err) throw err; 

     console.log(JSON.stringify(response, undefined, 4)); 
     callback(); 
     }); 
    }, 

    function(callback) { 
     var trandate = new Date("2014-07-02"); 
     transact(account._id,owner._id,10,trandate,function(err,response) { 
     if (err) throw err; 

     console.log(JSON.stringify(response, undefined, 4)); 
     callback(); 
     }); 
    }, 

    ],function(err) { 

    String.prototype.repeat = function(num) { 
     return new Array(num + 1).join(this); 
    }; 

    console.log("Outputs\n%s\n", "=".repeat(80)); 

    async.series([ 

     function(callback) { 
     Account.findById(account._id,function(err,account) { 
      if (err) throw err; 

      console.log(
      "Raw Account\n%s\n%s\n", 
      "=".repeat(80), 
      JSON.stringify(account, undefined, 4) 
     ); 
      callback(); 
     }); 
     }, 

     function(callback) { 
     AccountBucket.find({},function(err,buckets) { 
      if (err) throw err; 

      console.log(
      "Buckets\n%s\n%s\n", 
      "=".repeat(80), 
      JSON.stringify(buckets, undefined, 4) 
     ); 
      callback(); 
     }); 
     }, 

     function(callback) { 
     Account.findById(account._id) 
      .populate("owner recent._id") 
      .exec(function(err,account) { 
      if (err) throw err; 

      var raw = account.toObject(); 

      raw.transactions = []; 
      raw.recent.forEach(function(recent) { 
       recent._id.transactions.forEach(function(transaction) { 
       raw.transactions.push(transaction); 
       }); 
      }); 

      delete raw.recent; 

      console.log(
       "Merged Pretty\n%s\n%s\n", 
       "=".repeat(80), 
       JSON.stringify(raw, undefined, 4) 
      ); 
      callback(); 
      }); 
     } 

    ],function(err) { 
     process.exit(); 
    }); 

    }); 

}); 

이 목록은 MongoDB를 2.6에서 사용할 수있는 "대량"업데이트 API 기능을 사용하고 있습니다,하지만 당신은 그것을 사용할 필요가 없습니다.업데이트에서 더 의미있는 응답을 버리는 것은 여기에 있습니다.

트랜잭션을 "버킷으로 넣기"하는 일반적인 경우는 어떻게 든 분할하는 것입니다. 여기의 기본 예제는 "day"이지만, 아마도 다른 것이 더 실용적 일 수 있습니다.

해당 식별자가 변경 될 때 새 버킷을 만들 수 있도록 MongoDB 업데이트의 "upsert"기능이 사용됩니다. 나중에 모든 버킷에서 균형을 유지할 수 있기 때문에 일반적으로 괜찮을 것입니다.하지만이 경우에는 "계정"마스터를 동기화하려고 적어도 시도 할 것입니다. 데모.

현재 버킷에 대한 업데이트가 완료된 후 응답이 검사되어 "업서 트"가 발생했는지 확인합니다. 기존 또는 몽구스 API .update()에서는 콜백의 세 번째 인수에 "upserted"문서의 _id을 반환합니다.

"upsert"가 발생하고 새 버킷이 생성되는 경우이를 최근 버킷 목록, 실제로 가장 최근 30 개의 버킷 목록으로 마스터 "Account"에 추가합니다. 따라서 이번에는 $push 작업은 다른 수정자를 다른 $each$sort 작업에 추가로 사용합니다.

마지막 두 개는 추가 할 배열 요소가 하나만 있어도 함께 사용해야합니다. MongoDB 2.4 버전에서는 실제로이 수정 자와 함께 $slice이 항상 필요하므로 제한하지 않으려면 $slice을 큰 수로 설정하십시오. 그러나 배열 길이를 제한하는 것이 좋습니다.

모든 경우에 모든 샘플 코드가 삽입되는 순서에도 불구하고 가장 최근 날짜순으로 날짜가 정렬됩니다. 출력은이 형식으로 실제로 쓰기 작업에서 발생하는 모든 것을 보여 주지만 일반적인 최종 결과의 요약은 여기에 있습니다.

Outputs 
======================================================================== 

Raw Account 
======================================================================== 
{ 
    "_id": "53bf504ac0716cbc113fbac5", 
    "owner": "53bf504ac0716cbc113fbac4", 
    "__v": 0, 
    "recent": [ 
     { 
      "_id": "53bf504a79b21601f0c00d1d", 
      "day": "2014-07-11T00:00:00.000Z" 
     }, 
     { 
      "_id": "53bf504a79b21601f0c00d1e", 
      "day": "2014-07-10T00:00:00.000Z" 
     }, 
     { 
      "_id": "53bf504a79b21601f0c00d1f", 
      "day": "2014-07-02T00:00:00.000Z" 
     } 
    ], 
    "balance": 25 
} 

Buckets 
======================================================================== 
[ 
    { 
     "_id": "53bf504a79b21601f0c00d1d", 
     "account": "53bf504ac0716cbc113fbac5", 
     "day": "2014-07-11T00:00:00.000Z", 
     "owner": "53bf504ac0716cbc113fbac4", 
     "transactions": [ 
      { 
       "amount": -5, 
       "added": "2014-07-11T03:47:38.170Z" 
      }, 
      { 
       "amount": 10, 
       "added": "2014-07-11T02:47:38.153Z" 
      }, 
      { 
       "amount": 15, 
       "added": "2014-07-11T01:47:38.176Z" 
      } 
     ], 
     "balance": 20 
    }, 
    { 
     "_id": "53bf504a79b21601f0c00d1e", 
     "account": "53bf504ac0716cbc113fbac5", 
     "day": "2014-07-10T00:00:00.000Z", 
     "owner": "53bf504ac0716cbc113fbac4", 
     "transactions": [ 
      { 
       "amount": -5, 
       "added": "2014-07-10T02:47:38.182Z" 
      } 
     ], 
     "balance": -5 
    }, 
    { 
     "_id": "53bf504a79b21601f0c00d1f", 
     "account": "53bf504ac0716cbc113fbac5", 
     "day": "2014-07-02T00:00:00.000Z", 
     "owner": "53bf504ac0716cbc113fbac4", 
     "transactions": [ 
      { 
       "amount": 10, 
       "added": "2014-07-02T00:00:00.000Z" 
      } 
     ], 
     "balance": 10 
    } 
] 

Merged Pretty 
======================================================================== 
{ 
    "_id": "53bf504ac0716cbc113fbac5", 
    "owner": { 
     "_id": "53bf504ac0716cbc113fbac4", 
     "name": "bill", 
     "__v": 0, 
     "accounts": [ 
      "53bf504ac0716cbc113fbac5" 
     ] 
    }, 
    "__v": 0, 
    "balance": 25, 
    "transactions": [ 
     { 
      "amount": -5, 
      "added": "2014-07-11T03:47:38.170Z" 
     }, 
     { 
      "amount": 10, 
      "added": "2014-07-11T02:47:38.153Z" 
     }, 
     { 
      "amount": 15, 
      "added": "2014-07-11T01:47:38.176Z" 
     }, 
     { 
      "amount": -5, 
      "added": "2014-07-10T02:47:38.182Z" 
     }, 
     { 
      "amount": 10, 
      "added": "2014-07-02T00:00:00.000Z" 
     } 
    ] 
} 
+0

수치심 나는 ​​대답으로 둘 다 설정할 수 없습니다. 내 '거래'는 사실이 사용 사례에서는 거의 없으며 한 달에 1-2 회입니다. 그러나 당신의 대답은 상당히 통찰력이 있으며, 나는 당신이 다른 지점에서 썼던 것을 실제로 사용할 수 있습니다. 사실, 제가 궁금해했던 곳이기도합니다. – Lanbo

+0

@LambdaDusk 소수는 친척이 될 수 있습니다. 배열이 수백 개의 항목으로 성장하는 것을 원하지 않습니다. 이는 주로 여기에서 요점입니다. 나와 같이 전체 예제에 붙여 넣을 때 다른 하나가 올바르게 표시되었습니다. 그것은 기본 포인트를 만드는 내 upvote 가져옵니다. 그러나 각 업데이트에서 '$ 슬라이스 (slice)'할 준비가되어 있지 않은 한 특정 제한을 초과하는 트랜잭션을 버리는 경우가 아니라면 배열을 계속 성장시켜야합니다. 현실적인 한계가 있습니다. 500을 가지고 있어야합니다. –

관련 문제