Статьи

Чтение из наборов реплик MongoDB с помощью PyMongo

Настройки чтения — это новая функция в MongoDB 2.2, которая позволяет вам точно контролировать, как запросы направляются членам набора реплик. С тонким контролем приходит сложность, но не бойтесь: я объясню, как использовать настройки чтения для маршрутизации ваших запросов с помощью PyMongo.

(Я помог написать спецификацию 10gen для настроек чтения и сделал реализацию для PyMongo 2.3.)

Эта проблема

Какой элемент набора реплик должен использовать PyMongo для findкоманды типа «только для чтения», например count? Должен ли он запросить первичный или вторичный? Если он запрашивает вторичный, какой из них он должен использовать? Как вы можете контролировать этот выбор?

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

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

Читать настройки

Предпочтение чтения состоит из трех частей:

Режим . Это определяет, следует ли читать с первичного или вторичного. Есть пять режимов:

  • PRIMARY: Режим по умолчанию. Всегда читайте с основного. Если первичное повышение не вызвано, исключение: «AutoReconnect: первичная реплика не установлена ​​для запроса».

  • SECONDARY: Чтение из вторичной , если есть один, в противном случае вызвать исключение: AutoReconnect: No replica set secondary available for query. PyMongo предпочитает вторичные с коротким временем пинга.

  • PRIMARY_PREFERRED: читать с первичного, если есть, в противном случае вторичный.

  • SECONDARY_PREFERRED: чтение из вторичного, если есть, в противном случае первичное. Опять же, второстепенные с низкой задержкой предпочтительнее

  • NEAREST: читать с любого члена с низкой задержкой.

Наборы тегов . Если вы пометили участников набора реплик , вы можете использовать теги, чтобы указать, с каких элементов читать. Допустим, вы пометили своих участников в соответствии с тем, в каких центрах данных они находятся. Ваша конфигурация набора реплик выглядит так:

{
    _id : "someSet",
    members : [
        {_id : 0, host : "A", tags : {"dc": "ny"}},
        {_id : 1, host : "B", tags : {"dc": "ny"}},
        {_id : 2, host : "C", tags : {"dc": "sf"}},
        {_id : 3, host : "D", tags : {"dc": "sf"}},
        {_id : 4, host : "E", tags : {"dc": "uk"}}
    ]
}

Вы можете настроить PyMongo для использования этого массива наборов тегов:

[{'dc': 'ny'}, {'dc':'sf'}, {}]

Драйвер просматривает массив, от первого набора тегов до последнего, в поисках набора тегов, который соответствует одному или нескольким элементам. Таким образом, если в сети есть какие-либо совпадающие участники {'dc': 'ny'}, водитель выбирает их, предпочитая тех, у кого меньше времени пинга. Если ни один из членов не совпадает {'dc': 'ny'}, PyMongo ищет подходящих участников {'dc':'sf'}и так далее в списке.

Последний пустой набор тегов {}означает «читать из любого члена независимо от тегов». Это отказоустойчиво. Если вы предпочитаете вызывать исключение, а не читать элемент, который не соответствует набору тегов, пропустите пустой набор в конце массива:

[{'dc': 'ny'}, {'dc':'sf'}]

В этом случае, если все участники в Нью-Йорке и Сан-Франциско не работают, PyMongo выдаст исключение вместо того, чтобы судить участника в Великобритании.

Вы можете иметь несколько тегов в наборе. Участник должен соответствовать всем тегам. Например, если ваш массив наборов тегов выглядит так:

[{'dc': 'ny', 'disk': 'ssd'}]

… тогда только член помечено как с 'dc': 'ny'и 'disk': 'ssd'совпадение. Дополнительные теги члена, вроде бы 'rack': 2, не имеют никакого эффекта.

Каждый режим взаимодействует немного по-разному с наборами тегов:

  • PRIMARY: Вы не можете комбинировать наборы тегов с PRIMARY. В конце концов, есть только один первичный, поэтому бессмысленно просить первичный с определенными тегами.
  • PRIMARY_PREFERRED: Если основной файл активен, считывайте его независимо от того, как он помечен. Если основной не работает, прочитайте из вторичного соответствия соответствующие теги. Если такого вторичного элемента нет, выведите ошибку.
  • SECONDARY: Чтение из вторичного объекта, который соответствует первому набору тегов, для которого есть совпадения.
  • SECONDARY_PREFERREDНравится SECONDARY, или если нет соответствующих вторичных серверов, как PRIMARY.
  • NEARESTНравится SECONDARY, но относиться к первичным так же, как к вторичным.

secondary_acceptable_latency_ms . PyMongo отслеживает время пинга каждого участника (см. Мониторинг ниже) и запрашивает только «ближайшего» участника или любого случайного члена, не более чем на 15 мсек «дальше» от него.

Скажем, у вас есть участники, которые находятся на расстоянии 10, 20 и 30 миллисекунд:

Серверы

PyMongo распределяет запросы равномерно между 10- и 20-миллисекундным членом. Он оправдывает 30-миллисекундный член, потому что он более чем на 15 мс дальше, чем ближайший член. Вы можете переопределить значение по умолчанию 15 мс, установив опцию snappily-named secondary_acceptable_latency_ms.

Алгоритм

PyMongo выбирает элемент, используя три части предпочтения чтения в качестве трехэтапного фильтра, удаляя неподходящих членов на каждом этапе. Поскольку PRIMARYдрайвер просто выбирает первичное, или, если нет первичного, вызывает исключение. Для SECONDARYи NEAREST:

  1. Примените режим. Для SECONDARY, отфильтруйте основной и продолжить. Ибо NEAREST, сохраните всех участников и продолжайте.

  2. Примените наборы тегов. Если настроенные наборы тегов не настроены, тогда передайте все элементы на следующий этап. В противном случае выполните поиск в массиве наборов тегов в поисках набора тегов, который соответствует некоторым элементам, и передайте эти элементы на следующий этап.

  3. Применить пинг раз. Сначала найдите ближайшего участника, который до сих пор пережил фильтрацию. Затем отфильтруйте участников более чем на 15 мс дальше.

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

PRIMARY_PREFERREDиспользует основной, если он есть, в противном случае он запускает SECONDARYалгоритм.

SECONDARY_PREFERREDсначала запускает SECONDARYалгоритм, и если в конце нет члена, он использует основной.

Я слышу ваши возражения: «Это сложно», — говорите вы. Это является довольно сложным, но мы выбрали этот алгоритм , потому что мы считаем , что он может быть настроен для работы в любом потребительном случае , если вы бросаете на нее. (См. Варианты использования ниже.) «Это дорого», — возражаете вы. Алгоритм дешевле, чем кажется, потому что он вообще не вводит / выводит. Он просто использует то, что он уже знает о вашем наборе реплик из периодического мониторинга .

Наконец, некоторый код

Давайте на самом деле использовать настройки чтения с PyMongo. Самый простой способ — настроить MongoReplicaSetClient. По умолчанию режим таков, PRIMARYнаборы тегов пусты и secondary_acceptable_latency_msсоставляют 15 мс:

from pymongo.mongo_replica_set_client import MongoReplicaSetClient

rsc = MongoReplicaSetClient('host1,host2,host3', replicaSet='foo')

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

from pymongo.mongo_replica_set_client import MongoReplicaSetClient
from pymongo.read_preferences import ReadPreference

rsc = MongoReplicaSetClient('host1,host2,host3', replicaSet='foo',
    read_preference=ReadPreference.SECONDARY_PREFERRED,
    tag_sets=[{'dc': 'ny'}, {}],
    secondary_acceptable_latency_ms=50)

(Обратите внимание, что то, что я называю «режимом», настраивается с помощью read_preferenceопции.)

Если вы инициализируете MongoReplicaSetClient, как это, то все чтения используют настроенный вами режим, наборы тегов и задержку. Вы также можете переопределить любой из этих трех вариантов post-hoc:

rsc = MongoReplicaSetClient('host1,host2,host3', replicaSet='foo')
rsc.read_preference = ReadPreference.NEAREST
rsc.tag_sets = [{'disk': 'ssd'}]
rsc.secondary_acceptable_latency_ms = 1000

Вы можете сделать то же самое при доступе к базе данных из MongoReplicaSetClient:

db = rsc.my_database
db.read_preference = ReadPreference.SECONDARY

Или коллекция:

collection = db.my_collection
collection.tag_sets = [{'dc': 'cloud'}]

Вы даже можете установить свои предпочтения для отдельных вызовов методов:

results = list(
    collection.find({}, secondary_acceptable_latency_ms=0))

document = collection.find_one(
    {'field': 'value'}, read_preference=ReadPreference.NEAREST)

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

(Дальнейшее чтение: настройки чтения PyMongo , MongoReplicaSetClient PyMongo .)

Помните slave_okay?

У старого ReplicaSetConnection была slave_okayопция. Это устарело сейчас, но все еще работает. Это рассматривается как SECONDARY_PREFERRED.

команды

Некоторые команды любят findAndModifyзаписывать данные, другие — countтолько читать. Команды только для чтения подчиняются вашим предпочтениям чтения, остальные всегда отправляются первичному. Вот команды, которые подчиняются предпочтениям чтения:

  • подсчитывать
  • отчетливый
  • группа
  • совокупный
  • встроенная карта
  • collStats, dbStats
  • geoNear, geoSearch, geoWalk

Если вы хотите, вы можете переопределить предпочтение чтения при выполнении отдельной команды:

stats = rsc.my_database.command(
    'dbStats', read_preference=ReadPreference.SECONDARY)

Sharding

Когда вы работаете findв изолированном кластере наборов реплик, PyMongo отправляет ваши предпочтения чтения в mongos. Например, если вы делаете запрос вроде:

collection.find(
    {'field': 'value'},
    read_preference=ReadPreference.SECONDARY_PREFERRED,
    tag_sets=[{'dc': 'ny'}, {}])

Затем PyMongo отправляет запрос в монго, например:

{
    $query: {field: 'value'},
    $readPreference: {
        mode: 'secondaryPreferred',
        tags: [{'dc': 'ny'}, {}],
    }
}

Mongos интерпретирует это $readPreferenceполе и применяет логику предпочтений чтения для каждого набора реплик в изолированном кластере.

Есть два ограничения:

  1. Монгос посылает все команды праймериз; вам придется подождать, пока версия 2.4 направит команды только для чтения к вторичным серверам. (См. SERVER-7423 .)

  2. Вы не можете переопределить mongos secondary_acceptable_latency_ms, только его режим и наборы тегов.

Случаи применения

Я хочу максимальной последовательности. Под «последовательностью» вы подразумеваете, что ни при каких обстоятельствах не хотите устаревших чтений. Как только вы изменили некоторые данные, вы хотите, чтобы все ваши чтения отражали изменения. В этом случае используйте PRIMARYи помните, что когда у вас нет первичного сервера (например, во время выборов или если большинство наборов реплик отключены), каждый запрос вызовет исключение.

Я хочу максимальной доступности. Вы хотите иметь возможность запрашивать, если это возможно. Использование PRIMARY_PREFERRED: при наличии первичного вы будете получать согласованные чтения, но если нет первичного, вы можете запросить вторичные. Мне нравится эта опция, потому что она позволяет вашему приложению оставаться в сети, только для чтения, во время отработки отказа. Будьте внимательны, чтобы проверить, что ваше приложение работает хорошо в этих условиях, очевидно.

Я хочу минимальную задержку. Использование NEAREST. Водитель или монго будет читать с самого быстрого участника и тех, кто находится в пределах 15 мс от него. Имейте в виду, что вы рискуете противоречивостью: если ближайший участник вашего сервера приложений является вторичным с некоторой задержкой репликации , вы можете прочитать устаревшие данные. Также обратите внимание, что NEARESTпросто минимизируется задержка в сети, а не чтение из элемента с наименьшим IO или нагрузкой на процессор.

Я использую наборы реплик для распространения моих данных. Если у вас есть набор реплик с членами, разбросанными по всему земному шару, вы можете пометить их, как в примере наборов тегов выше. Затем настройте серверы приложений для запроса ближайших участников. Например, ваши серверы приложений в Нью-Йорке:

rsc.read_preference = ReadPreference.NEAREST
rsc.tag_sets = [{'dc': 'ny'}, {}]

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

Я хочу максимальной пропускной способности. Используйте NEARESTи устанавливайте secondary_acceptable_latency_msочень высоко, например, 500 мс. Это распределяет нагрузку запроса поровну между всеми участниками, таким образом (при большинстве обстоятельств), обеспечивая максимальную пропускную способность чтения.

Если вы хотите переместить загрузку чтения с вашего основного, используйте режим SECONDARY. Это заманчиво в использовании SECONDARY_PREFERRED, но если ваш основной сервер не может принять всю нагрузку чтения, вы, вероятно, предпочтете, чтобы ваши запросы не выполнялись, чем переносить всю нагрузку на основной, когда ваши дополнительные серверы недоступны.

Мониторинг

PyMongo нужно много знать о состоянии вашего набора реплик, чтобы знать, какие элементы использовать для ваших предпочтений чтения. Если вы создаете MongoReplicaSetClient, как:

rsc = MongoReplicaSetClient('host1,host2,host3', replicaSet='foo')

… затем MongoReplicaSetClient пытается подключиться к каждому серверу в произвольном порядке, пока не найдет тот, который работает. Он запускает isMasterкоманду на этом сервере. В ответе сервера сообщается MongoReplicaSetClient, какие члены находятся в наборе реплик, как они помечены и кто является основным. Затем MongoReplicaSetClient вызывает isMasterкаждого участника в наборе и записывает задержку. Это то, что я назвал « время пинга » выше.

Как только все это будет завершено, MongoReplicaSetClient запускает фоновый поток, который называется Monitor. Монитор просыпается каждые 30 секунд и обновляет представление набора реплик: он isMasterснова запускается на всех участниках, помечает как «отключенные» все элементы, к которым он не может подключиться, и отмечает новых участников, которые присоединились. Он также обновляет измерение задержки для каждого элемента. Для отслеживания задержки каждого члена используется скользящее среднее из 5 выборок.

Если участник выходит из строя, MongoReplicaSetClient не займет 30 секунд, чтобы заметить. Как только он получает сетевую ошибку при попытке запросить члена, который, как он думал, был активен, он запускает монитор для обновления как можно скорее.

В заключении

Здесь много вариантов и деталей. Если вы просто хотите сделать запрос к первичному серверу, примите значение по умолчанию и, если вы просто хотите перенести загрузку на ваши вторичные серверы, используйте SECONDARY. Но если вы относитесь к тому, что вам необходимо оптимизировать для обеспечения согласованности, доступности, задержки или пропускной способности при каждом запросе, настройки чтения дают вам полный контроль.