mongodb: вставить, если не существует

каждый день, я получаю запас документов (обновление). То, что я хочу сделать, это вставить каждый элемент, который еще не существует.

  • Я также хочу отслеживать первый раз, когда я вставил их, и последний раз, когда я видел их в обновлении.
  • Я не хочу иметь дубликаты документов.
  • Я не хочу удалить документ, который ранее был сохранен, но не в моем обновлении.
  • 95% (по оценкам) записей не изменены изо дня в день.

Я использую драйвер Python (pymongo).

то, что я сейчас делаю ,это (псевдо-код):

for each document in update:
      existing_document = collection.find_one(document)
      if not existing_document:
           document['insertion_date'] = now
      else:
           document = existing_document
      document['last_update_date'] = now
      my_collection.save(document)

моя проблема в том, что он очень медленный (40 минут для менее чем 100 000 записей, и у меня их миллионы в обновлении). Я уверен, что для этого есть что-то встроенное, но документ для update () - это mmmmhhh.... немного лаконично.... (http://www.mongodb.org/display/DOCS/Updating)

кто-то может посоветуйте, как это сделать быстрее?

8 ответов


звучит так, как будто вы хотите сделать "upsert". MongoDB имеет встроенную поддержку для этого. Передайте дополнительный параметр вызову update (): {upsert: true}. Например:

key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument

это полностью заменяет блок if-find-else-update. Он будет вставляться, если ключ не существует, и будет обновляться, если он существует.

перед:

{"key":"value", "key2":"Ohai."}

после:

{"key":"value", "key2":"value2", "key3":"value3"}

вы также можете указать, какие данные вы хотите написать:

data = {"$set":{"key2":"value2"}}

теперь ваш выбранный документ обновит только значение "key2" и оставит все остальное нетронутым.


начиная с MongoDB 2.4, вы можете использовать $setOnInsert (http://docs.mongodb.org/manual/reference/operator/setOnInsert/)

сеть insertion_date с помощью $setOnInsert и last_update_date с помощью $набор в команду вставки.

чтобы превратить ваш псевдокод в рабочий пример:

now = datetime.utcnow()
for document in update:
    collection.update_one(
        {"_id": document["_id"]},
        {
            "$setOnInsert": {"insertion_date": now},
            "$set": {"last_update_date": now},
        },
        upsert=True,
    )

вы всегда можете сделать уникальный индекс, который заставляет MongoDB отклонять конфликтное сохранение. Рассмотрим следующее, сделанное с помощью оболочки mongodb:

> db.getCollection("test").insert ({a:1, b:2, c:3})
> db.getCollection("test").find()
{ "_id" : ObjectId("50c8e35adde18a44f284e7ac"), "a" : 1, "b" : 2, "c" : 3 }
> db.getCollection("test").ensureIndex ({"a" : 1}, {unique: true})
> db.getCollection("test").insert({a:2, b:12, c:13})      # This works
> db.getCollection("test").insert({a:1, b:12, c:13})      # This fails
E11000 duplicate key error index: foo.test.$a_1  dup key: { : 1.0 }

вы можете использовать Upsert с оператором $ setOnInsert.

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})

1. Используйте Update.

опираясь на ответ Ван Нгуена выше, используйте update вместо save. Это дает вам доступ к опции вставки.

Примечание: этот метод переопределяет весь документ при его обнаружении (документы)

var conditions = { name: 'borne' }   , update = { $inc: { visits: 1 }} , options = { multi: true };

Model.update(conditions, update, options, callback);

function callback (err, numAffected) {   // numAffected is the number of updated documents })

1.a. Используйте $set

если вы хотите обновить выбор документа, но не все, вы можете использовать метод $set с обновлением. (опять от docs)... Итак, если вы хотите установить...

var query = { name: 'borne' };  Model.update(query, ***{ name: 'jason borne' }***, options, callback)

отправить его в качестве...

Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)

это помогает предотвратить случайную перезапись всех ваших документов с помощью { name: 'jason borne' }.


Я не думаю, что mongodb поддерживает этот тип выборочного upserting. У меня та же проблема, что и у Лемиза, и использование обновление (критерии, newObj, upsert, multi) не работает правильно при работе как с "созданной", так и с "обновленной" меткой времени. Учитывая следующее утверждение upsert:

update( { "name": "abc" }, 
        { $set: { "created": "2010-07-14 11:11:11", 
                  "updated": "2010-07-14 11:11:11" }},
        true, true ) 

Сценарий #1-документ с "именем ""abc" не существует: Новый документ создается с 'name' = 'abc', 'created' = 2010-07-14 11: 11: 11 и 'updated' = 2010-07-14 11:11:11.

Сценарий #2-документ с "именем "" abc " уже существует со следующим: 'name' = 'abc', 'created' = 2010-07-12 09:09:09 и 'updated' = 2010-07-13 10:10:10. После upsert документ теперь будет таким же, как результат в сценарии № 1. Невозможно указать в upsert, какие поля будут установлены при вставке, и какие поля будут оставлены в покое при обновлении.

моим решением было создать уникальный индекс на critera поля, проанализировать вставить и сразу же после этого выполнить обновление только в поле "обновлено".


резюме

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

примечание, Я предполагаю, PyMongo, изменить в соответствии с вашим языком выбора.

инструкции:

  1. создайте коллекцию с индексом С unique=true, чтобы вы не получали повторяющиеся записи.

  2. перебирайте входные записи, создавая пакеты из 15 000 записей или около того. Для каждой записи в пакете, создать словарь, состоящий из данных, которые вы хотите вставить, предполагая, что каждый будет новый рекорд. Добавьте к ним метки времени "создано" и "обновлено". Выполните это как команду пакетной вставки с флагом "ContinueOnError" =true, поэтому вставка всего остального происходит, даже если там есть дубликат ключа (который, похоже, будет). ЭТО ПРОИЗОЙДЕТ ОЧЕНЬ БЫСТРО. Навальные вставки rock, я получил 15K / second уровни производительности. Дополнительные сведения о ContinueOnError см. http://docs.mongodb.org/manual/core/write-operations/

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

  3. повторите все ваши входные записи снова, создавая пакеты 15K или около того. Извлеките ключи (лучше всего, если есть один ключ, но ничего не поделаешь, если его нет). Вернуть эту связку записей из Mongo с БД.collectionNameBlah.найти ({ поле: { $in: [ 1, 2,3 ...}) запрашивать. Для каждой из этих записей определите, есть ли обновление, и если да, выполните обновление, включая обновление метки времени "updated".

    к сожалению, следует отметить, что MongoDB 2.4 и ниже не включают операцию массового обновления. Они работают над этим.

Ключевые Точки Оптимизации:

  • вставки значительно ускорят поднимите свои операции навалом.
  • получение записей в массовом порядке также ускорит процесс.
  • индивидуальные обновления-единственный возможный маршрут сейчас, но 10Gen работает над ним. Предположительно, это будет в 2.6, хотя я не уверен, будет ли он закончен к тому времени, есть много вещей, которые нужно сделать (я следил за их системой Jira).

В общем, использование update лучше в MongoDB, поскольку он просто создаст документ, если он еще не существует, хотя я не уверен, как работать с вашим адаптером python.

во-вторых, если вам нужно только знать, существует ли этот документ, count (), который возвращает только число, будет лучшим вариантом, чем find_one, который предположительно передает весь документ из вашего MongoDB, вызывая ненужный трафик.