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() - mmmhhh.... немного точный.... (http://www.mongodb.org/display/DOCS/Updating)

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

Ответ 1

Похоже, вы хотите сделать "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" и оставьте все остальное нетронутым.

Ответ 2

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

Задайте 'insertion_date', используя $setOnInsert и 'last_update_date', используя $set в вашей команде upsert.

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

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

Ответ 3

Вы всегда можете создать уникальный индекс, который заставит 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 }

Ответ 4

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

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

Ответ 5

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

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

ПРИМЕЧАНИЕ. Этот метод переопределяет весь документ при обнаружении (В документах)

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.а. Используйте $set

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

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

Отправьте его как...

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

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

Ответ 6

Я не думаю, что mongodb поддерживает этот тип выборочного восстания. У меня та же проблема, что и LeMiz, и использование обновлений (критерии, 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. После обновления документ теперь будет таким же, как результат в сценарии №1. Там нет способа указать в upsert, какие поля должны быть установлены, если вставлять, и какие поля остаются в покое при обновлении.

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

Ответ 7

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

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

Ответ 8

Резюме

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

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

Инструкция:

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

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

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

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

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

Точки оптимизации ключа:

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