MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

wstaw wiele błędów zduplikowanych obsługi

W rzeczywistości MongoDB domyślnie nie tworzy zduplikowanych danych, w których występuje "unikalny klucz", z których _id ( aliasem mangusty jako id , ale ignorowane przez insertMany() więc musisz być ostrożny ), ale jest w tym o wiele szersza historia, której naprawdę musisz być świadomy .

Podstawowym problemem jest to, że zarówno "mongusta" implementacja insertMany() podobnie jak sterownik bazowy są obecnie nieco "bored", delikatnie mówiąc. To, że istnieje pewna niespójność w sposobie, w jaki sterownik przekazuje odpowiedź na błąd w operacjach „luzem”, jest w rzeczywistości potęgowane przez „mangusę”, która tak naprawdę nie „szuka we właściwym miejscu” rzeczywistych informacji o błędzie.

„Szybką” częścią, której brakuje, jest dodanie { ordered: false } do operacji „Bulk”, której .insertMany() po prostu zawija połączenie. Ustawienie tego gwarantuje, że „partia” żądań jest faktycznie przesyłana „w całości” i nie zatrzymuje wykonywania w przypadku wystąpienia błędu.

Ale ponieważ „mangusta” nie radzi sobie z tym zbyt dobrze (tak samo jak sterownik „konsekwentnie” ), tak naprawdę musimy szukać możliwych „błędów” w „odpowiedzi”, a nie w wyniku „błędu” wywołania zwrotnego.

Jako demonstracja:

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const songSchema = new Schema({
  _id: Number,
  name: String
});

const Song = mongoose.model('Song', songSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

let docs = [
  { _id: 1, name: "something" },
  { _id: 2, name: "something else" },
  { _id: 2, name: "something else entirely" },
  { _id: 3, name: "another thing" }
];

mongoose.connect(uri,options)
  .then( () => Song.remove() )
  .then( () =>
    new Promise((resolve,reject) =>
      Song.collection.insertMany(docs,{ ordered: false },function(err,result) {
        if (result.hasWriteErrors()) {
          // Log something just for the sake of it
          console.log('Has Write Errors:');
          log(result.getWriteErrors());

          // Check to see if something else other than a duplicate key, and throw
          if (result.getWriteErrors().some( error => error.code != 11000 ))
            reject(err);
        }
        resolve(result);    // Otherwise resolve
      })
    )
  )
  .then( results => { log(results); return true; } )
  .then( () => Song.find() )
  .then( songs => { log(songs); mongoose.disconnect() })
  .catch( err => { console.error(err); mongoose.disconnect(); } );

A może trochę ładniej, ponieważ obecny node.js LTS ma async/await :

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const songSchema = new Schema({
  _id: Number,
  name: String
});

const Song = mongoose.model('Song', songSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

let docs = [
  { _id: 1, name: "something" },
  { _id: 2, name: "something else" },
  { _id: 2, name: "something else entirely" },
  { _id: 3, name: "another thing" }
];

(async function() {

  try {
    const conn = await mongoose.connect(uri,options);

    await Song.remove();

    let results = await new Promise((resolve,reject) => {
      Song.collection.insertMany(docs,{ ordered: false },function(err,result) {
        if (result.hasWriteErrors()) {
          // Log something just for the sake of it
          console.log('Has Write Errors:');
          log(result.getWriteErrors());

          // Check to see if something else other than a duplicate key, then throw
          if (result.getWriteErrors().some( error => error.code != 11000 ))
            reject(err);
        }
        resolve(result);    // Otherwise resolve

      });
    });

    log(results);

    let songs = await Song.find();
    log(songs);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})()

W każdym razie otrzymujesz ten sam wynik pokazujący, że zapisy są zarówno kontynuowane, jak i że z szacunkiem „ignorujemy” błędy związane z „zduplikowanym kluczem” lub inaczej znane jako kod błędu 11000 . „Bezpieczna obsługa” polega na tym, że oczekujemy takich błędów i odrzucamy je, szukając obecności „innych błędów”, na które możemy po prostu zwrócić uwagę. Widzimy również, że reszta kodu jest kontynuowana i wyświetla wszystkie dokumenty faktycznie wstawione przez wykonanie kolejnego .find() zadzwoń:

Mongoose: songs.remove({}, {})
Mongoose: songs.insertMany([ { _id: 1, name: 'something' }, { _id: 2, name: 'something else' }, { _id: 2, name: 'something else entirely' }, { _id: 3, name: 'another thing' } ], { ordered: false })
Has Write Errors:
[
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
    "op": {
      "_id": 2,
      "name": "something else entirely"
    }
  }
]
{
  "ok": 1,
  "writeErrors": [
    {
      "code": 11000,
      "index": 2,
      "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
      "op": {
        "_id": 2,
        "name": "something else entirely"
      }
    }
  ],
  "writeConcernErrors": [],
  "insertedIds": [
    {
      "index": 0,
      "_id": 1
    },
    {
      "index": 1,
      "_id": 2
    },
    {
      "index": 2,
      "_id": 2
    },
    {
      "index": 3,
      "_id": 3
    }
  ],
  "nInserted": 3,
  "nUpserted": 0,
  "nMatched": 0,
  "nModified": 0,
  "nRemoved": 0,
  "upserted": [],
  "lastOp": {
    "ts": "6485492726828630028",
    "t": 23
  }
}
Mongoose: songs.find({}, { fields: {} })
[
  {
    "_id": 1,
    "name": "something"
  },
  {
    "_id": 2,
    "name": "something else"
  },
  {
    "_id": 3,
    "name": "another thing"
  }
]

Dlaczego więc ten proces? Powodem jest to, że wywołanie bazowe faktycznie zwraca zarówno err i result jak pokazano w implementacji wywołania zwrotnego, ale zwracane są niespójności. Głównym powodem, dla którego należy to zrobić, jest fakt, że widzisz „wynik”, który zawiera nie tylko wynik udanej operacji, ale także komunikat o błędzie.

Wraz z informacją o błędzie znajduje się nInserted: 3 wskazując, ile faktycznie zostało napisanych z „partii”. Możesz prawie zignorować insertedIds tutaj, ponieważ ten konkretny test faktycznie obejmował dostarczenie _id wartości. W przypadku, gdy inna właściwość miała „unikalne” ograniczenie, które spowodowało błąd, jedynymi wartościami tutaj byłyby te z rzeczywistych udanych zapisów. Nieco mylące, ale łatwe do przetestowania i zobaczenia na własne oczy.

Jak wspomniano, haczykiem jest „niespójność”, którą można zademonstrować na innym przykładzie ( async/await tylko dla zwięzłości wykazu):

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const songSchema = new Schema({
  _id: Number,
  name: String
});

const Song = mongoose.model('Song', songSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

let docs = [
  { _id: 1, name: "something" },
  { _id: 2, name: "something else" },
  { _id: 2, name: "something else entirely" },
  { _id: 3, name: "another thing" },
  { _id: 4, name: "different thing" },
  //{ _id: 4, name: "different thing again" }
];

(async function() {

  try {
    const conn = await mongoose.connect(uri,options);

    await Song.remove();

    try {
      let results = await Song.insertMany(docs,{ ordered: false });
      console.log('what? no result!');
      log(results);   // not going to get here
    } catch(e) {
      // Log something for the sake of it
      console.log('Has write Errors:');

      // Check to see if something else other than a duplicate key, then throw
      // Branching because MongoError is not consistent
      if (e.hasOwnProperty('writeErrors')) {
        log(e.writeErrors);
        if(e.writeErrors.some( error => error.code !== 11000 ))
          throw e;
      } else if (e.code !== 11000) {
        throw e;
      } else {
        log(e);
      }

    }

    let songs = await Song.find();
    log(songs);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})()

W zasadzie to samo, ale zwróć uwagę na to, jak błąd jest tutaj rejestrowany:

Has write Errors:
{
  "code": 11000,
  "index": 2,
  "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
  "op": {
    "__v": 0,
    "_id": 2,
    "name": "something else entirely"
  }
}

Zauważ, że nie ma informacji o „sukcesie”, mimo że otrzymujemy tę samą kontynuację listingu, wykonując kolejne .find() i uzyskanie danych wyjściowych. Dzieje się tak, ponieważ implementacja działa tylko na „wyrzucony błąd” podczas odrzucenia i nigdy nie przechodzi przez rzeczywisty result część. Więc mimo że poprosiliśmy o ordered: false , nie otrzymujemy informacji o tym, co zostało ukończone, chyba że zawiniemy wywołanie zwrotne i sami zaimplementujemy logikę, jak pokazano na początkowych zestawieniach.

Inna ważna „niespójność” ma miejsce, gdy występuje „więcej niż jeden błąd”. Odkomentowanie dodatkowej wartości dla _id: 4 daje nam:

Has write Errors:
[
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
    "op": {
      "__v": 0,
      "_id": 2,
      "name": "something else entirely"
    }
  },
  {
    "code": 11000,
    "index": 5,
    "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 4 }",
    "op": {
      "__v": 0,
      "_id": 4,
      "name": "different thing again"
    }
  }
]

Tutaj możesz zobaczyć kod "rozgałęziony" w obecności e.writeErrors , który nie istnieje, gdy jest jeden błąd. Natomiast wcześniejsza response obiekt ma oba hasWriteErrors() i getWriteErrors() metody, niezależnie od tego, czy w ogóle występuje błąd. Jest to więc bardziej spójny interfejs i powód, dla którego powinieneś go używać zamiast sprawdzać err sama odpowiedź.

Poprawki sterowników MongoDB 3.x

To zachowanie zostało faktycznie naprawione w nadchodzącej wersji 3.x sterownika, która ma zbiegać się z wydaniem serwera MongoDB 3.6. Zachowanie zmienia się w ten sposób, że err odpowiedź jest bardziej zbliżona do standardowego result , ale oczywiście sklasyfikowany jako BulkWriteError odpowiedź zamiast MongoError którym jest obecnie.

Dopóki to nie zostanie wydane (i oczywiście dopóki ta zależność i zmiany nie zostaną rozpropagowane w implementacji "mongusty"), zalecanym sposobem działania jest bycie świadomym, że przydatne informacje znajdują się w result i nie err . W rzeczywistości twój kod prawdopodobnie powinien szukać hasErrors() w result a następnie wróć, aby sprawdzić err również w celu zaspokojenia zmiany, która ma zostać zaimplementowana w sterowniku.

Uwaga autorów: Wiele z tych treści i związanych z nimi odczytów jest już tutaj udzielonych w funkcji insertMany() nieuporządkowanej:właściwy sposób na uzyskanie zarówno błędów, jak i wyniku? a natywny sterownik MongoDB Node.js po cichu połyka bulkWrite wyjątek. Ale powtarzanie i rozwijanie tutaj, aż w końcu dociera do ludzi, że jest to sposób obsługi wyjątków w obecnej implementacji sterownika. I faktycznie działa, gdy spojrzysz we właściwe miejsce i napiszesz kod, który odpowiednio go obsłuży.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB $ne Operator potoku agregacji

  2. znajdź identyfikator ostatniego poddokumentu wstawionego w manguście

  3. Automatycznie przedawniaj dokumenty kolekcji MongoDB

  4. $rozwiń pustą tablicę

  5. Dopasowywanie ObjectId do String dla $graphLookup