2015-09-02 2 views
0

Мое приложение становится немного кошмаром, и это нестандартная проблема MongoDB, которую я не смог найти в другом месте.Как предотвратить MongoDB от создания нескольких записей из-за async

Мой поток сервера, как это:

  1. Пользователь загружает список объектов, содержащих {names, emails and company domains} на мой сервер
  2. Мой сервер превращает все это в Person объектов.
  3. Как только Person был сохранен, я ищу MongoDB, чтобы узнать, существует ли запись для человека Domain.
  4. Если он существует, я добавляю Mongo Person Mongo _id к списку пользователей от Domain.
  5. Если он не существует, я создаю новый документ Domain и сохраняю его.

Это работает теоретически, НО, из-за Async, иногда я отправляю тысячи объектов Person в функцию сохранения домена одновременно. Что означает (по крайней мере, то, что я думаю, что происходит):

  • Mongo поисковики для «домена 1», видит, что нет документа, поэтому он создает один, а затем сохраняет его.
  • Пока это все еще происходит, Монго ищет «Домен 1» от отдельного пользователя. Документ еще не сохранен, поэтому он не находит и делает новый.
  • Теперь у меня есть два документа с тем же идентификатором домена.

Вот код, который я сейчас использую:

Domain.findOne({ 
    domain: domn 
}, function(err, rec) { 
    if (err) { 
    console.log("Domain finding error: " + err) 
    bigCount.doneDoms++; 
    checkCount() 
    } else if (rec){ 
    var tempObj = {} 
     tempObj['$addToSet'] = { users: id } 
     tempObj['$addToSet'].emails = user.email; 

    if (userDoms.indexOf(rec._id) === -1) { 
     userDoms.push(rec._id) 
    } 

    Domain.update({domain: domn}, tempObj, function(err) { 
     if (err) { 
     console.log("Old rec save Error: " + err) 
     bigCount.doneDoms++; 
     checkCount(); 
     }else{ 
     // Saved Document 
     } 
    }); 

    } else { 
    var newDom = new Domain(); 
    newDom.domain = user.domain; 
    newDom.company = user.company; 
    newDom.users = []; 
    newDom.users.push(id); 
    newDom.emails = []; 
    newDom.emails.push(user.email); 

    newDom.save(function(err, record) { 
     if (err) { 
     console.log("Dom save error: " + err) 
     } else { 
     // Saved Document 
     } 
    }); 

    } 
}) 

Возможно, урезанная версия вопрос, как я могу справиться somethign так:

var arr = [{dom: 'dom1.com', user: 'James'}, {dom: 'dom1.com', user: "Phil"}, {dom: 'dom1.com', user: "Jess"} ...x1000... {dom: 'dom1.com', user: "Chris"]; 

for(var i - 0; i< arr.length; i++){ 
    var dom = arr[i]; var user = arr[i].user; 
    Domain.findOne({domain: dom}, function(err, rec){ 
    if(rec){ 
     // Update old rec 
     if(rec.users.indexOf(user) === -1){ 
     rec.users.push(user); 
     } 
     rec.save(); 
    ]else{ 

    // Make a new rec 
    var rec = New Domain(); 
    rec.users = [user] 
    rec.save(); 
    } 
    }) 
} 

Благодаря скорость/Async, здесь будет создано множество записей, когда действительно я хочу только один

ответ

2

Я бы лично пойти на подходе, который вы собираетесь о вещах неправильный путь вокруг, так же как и вы можете использовать некоторое управление потоком здесь.

Независимо от источника «список», общий поток, что должно происходить в:

  • объект Instantiate для пользователя (вы получите _id взамен после того, как все)

  • Ищут данные домена, если он существует, а если нет, то создайте его при одновременном добавлении пользователя.(Очень возможно)

  • Наконец добавить совпавший домен пользователя и сохранить их

Все это следует шаблон легко реализовать с .findOneAndUpdate() вместе с опцией «upsert», который создаст новый документ если не найден, и, во всяком случае, вернуть полученный документ, найденный или созданный.

Так с некоторыми node async библиотеки хелперов, здесь:

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

var userSchema = new Schema({ 
    name: { type: String, required: true }, 
    domain: { type: Schema.Types.ObjectId, ref: 'Domain' } 
}); 

var domainSchema = new Schema({ 
    name: { type: String, required: true }, 
    users: [{ type: Schema.Types.ObjectId, ref: 'User' }] 
}); 

var User = mongoose.model('User',userSchema), 
    Domain = mongoose.model('Domain',domainSchema); 

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

var arr = [ 
    {dom: 'dom1.com', user: "James"}, 
    {dom: 'dom2.com', user: "Phil"}, 
    {dom: 'dom1.com', user: "Jess"}, 
    {dom: 'dom1.com', user: "Chris"}, 
    {dom: 'dom3.com', user: "Jesse"} 
]; 

async.series(
    [ 
    // Clean removal of data for demo 
    function(callback) { 
     async.each([User,Domain],function(model,callback) { 
     model.remove({},callback); 
     },callback); 
    }, 

    // The actual insertion process 
    function(callback) { 
     async.eachLimit(arr,10,function(item,callback) { 
     var user = new User({ name: item.user }); 

     // user already has the _id 

     Domain.findOneAndUpdate(
      { "name": item.dom }, 
      { "$push": { "users": user._id } }, 
      { "new": true, "upsert": true }, 
      function(err,domain) { 
      if (err) callback(err); 
      user.domain = domain._id; // always returns something 

      // now save the user 
      user.save(callback); 
      } 
     ); 

     },callback); 
    }, 

    // List back populated as the proof 
    function(callback) { 
     User.find({}).populate('domain').exec(function(err,users) { 
     if (err) callback(err); 

     //console.log(users); 
     //callback(); 

     var options = { 
      path: 'domain.users', 
      model: 'User' 
     }; 

     User.populate(users,options,function(err,results) { 
      if (err) callback(err); 
      console.log(JSON.stringify(results, undefined, 2)); 
      callback(); 
     }); 
     }); 
    } 
    ], 
    function(err) { 
    if (err) throw err; 
    mongoose.disconnect(); 
    } 
); 

И что будет производить выход, как:

[ 
    { 
    "_id": "55e6aa0e85e8b9102179f5c2", 
    "domain": { 
     "_id": "55e6aa0ecb536c5a93574ff5", 
     "name": "dom1.com", 
     "__v": 0, 
     "users": [ 
     { 
      "_id": "55e6aa0e85e8b9102179f5c2", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "James", 
      "__v": 0 
     }, 
     { 
      "_id": "55e6aa0e85e8b9102179f5c4", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "Jess", 
      "__v": 0 
     }, 
     { 
      "_id": "55e6aa0e85e8b9102179f5c5", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "Chris", 
      "__v": 0 
     } 
     ] 
    }, 
    "name": "James", 
    "__v": 0 
    }, 
    { 
    "_id": "55e6aa0e85e8b9102179f5c3", 
    "domain": { 
     "_id": "55e6aa0ecb536c5a93574ff6", 
     "name": "dom2.com", 
     "__v": 0, 
     "users": [ 
     { 
      "_id": "55e6aa0e85e8b9102179f5c3", 
      "domain": "55e6aa0ecb536c5a93574ff6", 
      "name": "Phil", 
      "__v": 0 
     } 
     ] 
    }, 
    "name": "Phil", 
    "__v": 0 
    }, 
    { 
    "_id": "55e6aa0e85e8b9102179f5c4", 
    "domain": { 
     "_id": "55e6aa0ecb536c5a93574ff5", 
     "name": "dom1.com", 
     "__v": 0, 
     "users": [ 
     { 
      "_id": "55e6aa0e85e8b9102179f5c2", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "James", 
      "__v": 0 
     }, 
     { 
      "_id": "55e6aa0e85e8b9102179f5c4", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "Jess", 
      "__v": 0 
     }, 
     { 
      "_id": "55e6aa0e85e8b9102179f5c5", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "Chris", 
      "__v": 0 
     } 
     ] 
    }, 
    "name": "Jess", 
    "__v": 0 
    }, 
    { 
    "_id": "55e6aa0e85e8b9102179f5c5", 
    "domain": { 
     "_id": "55e6aa0ecb536c5a93574ff5", 
     "name": "dom1.com", 
     "__v": 0, 
     "users": [ 
     { 
      "_id": "55e6aa0e85e8b9102179f5c2", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "James", 
      "__v": 0 
     }, 
     { 
      "_id": "55e6aa0e85e8b9102179f5c4", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "Jess", 
      "__v": 0 
     }, 
     { 
      "_id": "55e6aa0e85e8b9102179f5c5", 
      "domain": "55e6aa0ecb536c5a93574ff5", 
      "name": "Chris", 
      "__v": 0 
     } 
     ] 
    }, 
    "name": "Chris", 
    "__v": 0 
    }, 
    { 
    "_id": "55e6aa0e85e8b9102179f5c6", 
    "domain": { 
     "_id": "55e6aa0ecb536c5a93574ff7", 
     "name": "dom3.com", 
     "__v": 0, 
     "users": [ 
     { 
      "_id": "55e6aa0e85e8b9102179f5c6", 
      "domain": "55e6aa0ecb536c5a93574ff7", 
      "name": "Jesse", 
      "__v": 0 
     } 
     ] 
    }, 
    "name": "Jesse", 
    "__v": 0 
    } 
] 

Так что все домены либо получили созданы или были повторно использованы, когда то существовала , и мы добавили пользователя в список там в то же время, используя $push, так как у нас уже был _id для пользователя после создания экземпляра.

С возвращенным документом домена, являющимся либо новым, либо чем-то найденным, вы просто устанавливаете домен для пользователя и сохраняете его.

async.eachLimit также является «специальным» вариантом, который «ограничивает» количество одновременных процессов, выполняемых под циклом. Это разумная практика в реальных сценариях, поскольку вы не хотите, чтобы каждое обновление происходило одновременно.

Кроме того, независимо от процесса, «Домен» не может создаваться более одного раза. Атомные операции MongoDB предотвратят это, и вы получите только существующий возврат или новый документ в зависимости от того, что было на момент запроса.

Как вы можете видеть на выходе, все может хорошо заполняться, так что детали «Пользователь» и «Домен» видны на всех уровнях.

Мораль истории «Не привязывайте себя в узлах, сохраняющих одно и повторяющихся снова и снова». Просто сделайте это один раз и сделайте это. Это, конечно, быстрее.

+0

Большое спасибо за подробный ответ. Хотя, я должен сказать, я * думал *, я следовал принципам, которые вы описываете. Я создал суть моего кода здесь: https://gist.github.com/jascination/327a9b11c213bb53b79e Это основано на нижеприведенном предложении 'async', но независимо от того, Async все еще создает несколько объектов, которые содержат одни и те же данные (кроме некоторых, есть только один адрес электронной почты в «электронных письмах», в то время как другие имеют все соответствующие письма) – Jascination

+1

@Jascination Если вы следите за тем, что делается в этом ответе, у вас не будет таких проблем. Как сказано, использование '.findOneAndUpate()' с «uspert» в том же шаблоне не может привести к дублированию записей. Это основной принцип разработки, который невозможен при таком шаблоне. –

1

§addToSet вместе с async делает именно то, что вы ищете:

var items = [ 
    {dom: 'dom1.com', user: "Johnny"}, 
    {dom: 'dom1.com', user: "Doggie"}, 
    {dom: 'dom1.com', user: "Lisa"}, 
    {dom: 'dom2.com', user: "Mark"}, 
    {dom: 'dom3.com', user: "Denny"} 
]; 

async.each(items, function(item, callback){ 
     Domain.findOneAndUpdate(
      {domain: item.dom}, 
      {$addToSet: {users: item.user}}, 
      { "new": true, "upsert": true }, 
      callback 
     ); 
    }, 
    function (err){ 
     // done/handle errors 
    } 
); 

Выход:

[{ 
    "_id": ObjectID("55e6c9bf63006d730254ea8b"), 
    "domain": "dom1.com", 
    "users": [ 
     "Johnny", 
     "Doggie", 
     "Lisa" 
    ] 
}, 
{ 
    "_id": ObjectID("55e6c9bf63006d730254ea8c"), 
    "domain": "dom2.com", 
    "users": [ 
     "Mark" 
    ] 
}, 
{ 
    "_id": ObjectID("55e6c9bf63006d730254ea8d"), 
    "domain": "dom3.com", 
    "users": [ 
     "Denny" 
    ] 
}] 
+0

Я пробовал это, но по какой-то причудливой причине все еще получаю те же результаты (то есть несколько записей из того же домена). – Jascination

+0

Пожалуйста, проверьте отредактированный ответ, я думаю, что он подходит именно к вашей проблеме. – cviejo

Смежные вопросы