Статьи

Новый стандарт PyMongo: безопасная запись!

Я радостно объявляю, что мы меняем все драйверы MongoDB от 10gen для «безопасной записи» по умолчанию. В процессе мы переименовываем все классы соединений в MongoClient, поэтому все драйверы теперь используют один и тот же термин для центрального класса.

PyMongo 2,4, выпущенные сегодня, имеет новые классы , называемые MongoClientи MongoReplicaSetClientкоторые имеют новую настройку по умолчанию, а также новый API для настройки записи-подтверждения под названием «относится запись». Старые Connectionи ReplicaSetConnectionклассы PyMongo остаются нетронутыми для обратной совместимости, но теперь они считаются устаревшими и исчезнут в некоторых будущих выпусках. Изменения были внесены сопровождающим PyMongo (и моим любимым коллегой) Берни Хакеттом.


Содержание:

Фон

Записи MongoDB происходят в два этапа. Сначала водитель направляет серверу insert, updateили removeсообщение. Сервер MongoDB выполняет операцию и отмечает результат: он записывает была ли ошибка, сколько документов были обновлены или удалены, и является ли upsert в результате обновления или вставки.

На следующем этапе драйвер запускает getLastErrorкоманду на сервере и ожидает ответа:

GetLastError

Этот getLastErrorвызов может быть опущен для скорости, и в этом случае драйвер просто отправляет все свои сообщения записи, не ожидая подтверждения. Режим «запусти и забудь», очевидно, очень высокопроизводительный, поскольку он может использовать преимущества пропускной способности сети без влияния ее задержки . Но этот режим не сообщает об ошибках вашему приложению и не гарантирует, что запись завершена до того, как вы сделаете запрос. Это неправильный режим для использования по умолчанию, поэтому мы меняем его сейчас.

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

(В древней истории 10gen, до моего времени, планировалось создать полный стек «платформа как услуга» с MongoDB в качестве слоя данных. Тогда имело смысл getLastErrorбыть отдельной операцией, которая выполнялась явно, и не вызов getLastErrorпо умолчанию автоматически, но MongoDB — это отдельный продукт, и ясно, что значение по умолчанию необходимо изменить.)

Новые значения по умолчанию

В более ранних версиях PyMongo вы могли создать соединение следующим образом:

from pymongo import Connection
connection = Connection('localhost', 27017)

По умолчанию Connectionделал неподтвержденные записи. Вы можете изменить его с помощью safeопции:

connection = Connection('localhost', 27017, safe=True)

Connectionне изменился в PyMongo 2.4, но мы добавили MongoClientпо умолчанию подтвержденные записи:

from pymongo import MongoClient
client = MongoClient('localhost', 27017)

Точно так же ReplicaSetConnectionявляется устаревшим и преемником MongoReplicaSetClient. Вы можете получить старое поведение неподтвержденных записей с новыми классами, используя wопцию:

client = MongoClient('localhost', 27017, w=0)

w=0это новый способ сказать safe=False.

w=1это новый, safe=Trueи теперь это по умолчанию. Другие варианты , как j=Trueждать журнала фиксации , или w=2чтобы ждать репликации , работают так же , как и раньше. Вы все еще можете установить параметры для каждой операции:

client.db.collection.insert({'foo': 'bar'}, w=1)

Пишите проблемы

Старый Connectionкласс позволял вам установить для safeатрибута значение Trueor Falseили вызвать set_lasterror_options()более сложную конфигурацию. Они устарели, и теперь вы должны использовать MongoClient.write_concernатрибут. write_concernявляется ДИКТ, ключи могут включать в себя w, wtimeout, jи fsync:

>>> client = MongoClient()
>>> # default empty dict means "w=1"
>>> client.write_concern
{}
>>> client.write_concern = {'w': 2, 'wtimeout': 1000}
>>> client.write_concern
{'wtimeout': 1000, 'w': 2}
>>> client.write_concern['j'] = True
>>> client.write_concern
{'wtimeout': 1000, 'j': True, 'w': 2}
>>> client.write_concern['w'] = 0 # disable write acknowledgement

Вы можете видеть, что по умолчанию write_concernиспользуется пустой словарь. Это эквивалентно w=1значению «делать регулярные подтвержденные записи».

auto_start_request

Это очень занудно, но мой личный фаворит. Значение по умолчанию для auto_start_requestизменяется с Trueна False.

Краткое объяснение таково: со старым Connectionвы можете записать некоторые данные на сервер без подтверждения, а затем сразу же прочитать эти данные обратно, при условии, что не было ошибки и что вы использовали один и тот же сокет для записи и чтения. , Если вы использовали разные сокеты для этих двух операций, то не было никакой гарантии «прочитайте вашу последовательность записи», потому что запись все еще могла быть поставлена ​​в очередь на одном сокете, в то время как вы завершили чтение на другой.

Вы можете прикрепить текущий поток к одному сокету Connection.start_request(), и фактически по умолчанию для Connectionкаждого запроса вы запускаете запрос для вас. Это auto_start_request. Он предлагает некоторые гарантии согласованности, но требует, чтобы водитель открыл дополнительные розетки.

Теперь то, что MongoClientждет подтверждения каждой записи, auto_start_requestбольше не нужно. Если вы делаете это:

>>> collection = MongoClient().db.collection
>>> collection.insert({'foo': 'bar'})
>>> print collection.find_one({'foo': 'bar'})

… тогда find_oneон не будет работать до тех пор, пока не insertбудет подтвержден, что означает, что ваш документ определенно был вставлен, и вы можете уверенно запрашивать его в любом сокете. Мы отключили auto_start_requestдля повышения производительности и меньше сокетов. Если вы делаете неподтвержденные записи с w=0последующими чтениями, вам следует подумать, следует ли вам звонить MongoClient.start_request(). Подробности (с диаграммами!) Смотрите в моем блоге на запросы от апреля.

миграция

Connectionи ReplicaSetConnectionостанется на некоторое время (не навсегда), поэтому ваш существующий код будет работать так же, и у вас будет время для миграции. Мы работаем над обновлением всей документации и примера кода для использования новых классов. Со временем мы добавим предупреждения об устаревании к старым классам и методам перед их полным удалением.

Если вы поддерживаете библиотеку, построенную на PyMongo, вы можете проверить наличие новых классов с помощью следующего кода:

try:
    from pymongo import MongoClient
    has_mongo_client = True
except ImportError:
    has_mongo_client = False

Как насчет двигателя?

Мотор в бета-версии, поэтому я буду беспощадно нарушать обратную совместимость ради чистоты. В течение следующей недели или двух я объединю официальные изменения PyMongo с моим форком , и я разберусь с ядерными символами MotorConnectionи MotorReplicaSetConnection, чтобы заменить их на MotorClientи MotorReplicaSetClient.

Вдохновляющий вывод

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