Статьи

Проблемы MongoDB, геопространственной индексации и расширенных запросов

Я работал над созданием и перестройкой геопространственной таблицы для работы. Там было много проблем в этом проекте для меня это первый раз, когда я должен был архитектором  дб конструкции включения MongoDB с MySql.

Моно-геопространственный репозиторий заменит несколько таблиц в устаревшей системе mySQL — как вы, возможно, знаете, mongodb поставляется с полной геопространственной поддержкой, поэтому выполнение запросов к коллекции (таблице), созданной таким образом, шокирует с точки зрения скорости его отклика, особенно когда Вы сравниваете эти скорости с традиционными алгоритмами mySQL для извлечения географических точек на основе диапазонов расстояний для координат широта / долгота. Tl; dr для этого абзаца: больше никаких отвратительных тригонометрических запросов MySQL!

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

Давайте использовать стандартный  объект клиента в качестве примера. У каждого клиента есть хотя бы один номер телефона . Большинство, если не все, клиенты  имеют более одного номера телефона . Мы могли бы определить несколько столбцов в  таблице клиентов для телефонных номеров: рабочий телефон, домашний телефон, мобильный телефон, другой телефон и таким образом хранить данные. Проблема заключается в том, что в конечном итоге мы столкнемся со стеной, где у нас будет необходимость хранить номера, для которых у нас нет предопределенных столбцов: факс, скайфон, tddphone, vrsphone и т. Д.

Конструкция СУБД требует нормализации этого дизайна данных 1: M, требуя, чтобы во второй таблице хранились только телефонные  номера для каждого клиента . Таблица телефона будет иметь первичный ключ (id), идентификатор клиента, номер телефона клиента и, возможно, короткое описательное поле, объясняющее назначение этого номера. Чтобы получить телефонные  данные о клиенте , вы просто запросите (или присоединитесь) телефонную  таблицу на основе  идентификатора клиента, чтобы получить все телефонные кортежи для этого клиента.

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

phone : {
home : (408) 123-4567,
work : (415) 123-4567,
cell : (312) 765-4321
}

…и так далее…

Mongo сохраняет документ  для этого или любого пользователя, отменяя нормализацию отношений данных в объекте customer . Эти отношения могут поддерживаться как подмассивы в документе. Поскольку mongo не содержит схем, каждый  объект- клиент не обязан иметь все возможные комбинации телефонных номеров. Таким образом, если вы проведете поиск, в котором вы отзовете всех клиентов с номерами факсов, наш мистер Смит не  появится в этом списке, поскольку в его списке телефонов нет номера факса.

Увидеть?

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

Но вы спрашиваете, что, если вы хотите позже добавить номер факса в коллекцию телефонов Джона Смита? Вы можете сделать это?

Конечно!

Опять же, это неотъемлемая сила mongodb — это без схемы! Добавление другого номера к существующей коллекции телефонных номеров или добавление нового «столбца» к самому документу требует только обновления этого документа. Это оно!

Итак, возвращаясь к геопространственной сборке, я использовал mySQL, извлекаю устаревшие данные и собираю обновленные таблицы каталога в единую базу данных. Затем я построил новые таблицы, в которых (а) были удалены столбцы, которые мне больше не нужны, и (б) была нормализована информация, чтобы каждый кортеж в каждой таблице отражал все данные.

Затем я объединил пять таблиц в одну таблицу под новым значением первичного ключа, а затем импортировал эти данные непосредственно в коллекцию Монго. Это заняло несколько часов, поскольку моя коллекция насчитывает более 3,6 миллиона строк.

Когда у меня была коллекция в монго, я сделал моно-дамп коллекции, чтобы я мог вернуться к этому моменту на случай, если что-нибудь пойдет на юг. (Что это сделал …)

Я выполнил скрипт PHP, который написал, чтобы отсканировать таблицу mySQL, получить кортеж по вновь созданному первичному ключу, а затем создать подмассив в коллекции mongo для геопространственных данных. Смотрите, чтобы наложить геопространственный индекс, ваши данные широты и долготы должны быть подмассивом в первичной коллекции. Я еще не обнаружил способа импортировать данные из плоского (CSV) файла или напрямую из MySQL, чтобы он автоматически создавал ваш подмассив. Следовательно, PHP-скрипт для домашнего просмотра для анализа записей mySQL и создания (вставки) подмассива во вновь созданную коллекцию mongodb.

(Примечание: я старался поддерживать максимальные значения мантиссы для полей lat / lon путем первоначального импорта в поля mySQL as varchar (255) — это сохранило мои 13-значные мантиссы. Когда я импортировал данные в mongodb, mongo преобразовал эти значения делаются на двойные  и сохраняют точность. Однако моя PHP-программа преобразует эти значения либо в (плавающее), либо (двойное) преобразовывает (округляет) матрицу в 7-значную точность. Подходит для задачи? Да. Раздражает потеря этих данных ? Да. Если у вас есть решение, пожалуйста, оставьте мне комментарий в конце этой статьи. Спасибо!   :-П)

Следующим шагом было добавление геопространственного индекса в коллекцию:

    > db.geodata_geo.ensureIndex( { loc : “2d” } );
    point not in interval of [ -180, 180 )

Какая?

Это сообщение об ошибке сообщало мне, что мои данные были вне диапазона допустимых значений широты / долготы!

Я попытался найти виновников данных:

    > db.geodata_geo.find( { "loc" : { $exists : true }}).count();
    3685667
    > db.geodata_geo.find({"loc.lon" : {$lt : -180}}).count();
    0
    > db.geodata_geo.find({"loc.lon" : {$gt : 180}}).count();
    0
    > db.geodata_geo.find({"loc.lat" : {$gt : 180}}).count();
    0
    > db.geodata_geo.find({"loc.lat" : {$lt : -180}}).count();
    0

Эти запросы говорили мне, что, хотя я проиндексировал более 3,6 миллиона записей, ни одна из них не выходит за пределы -180,180.

    > db.geodata_geo.find({"loc.lat" : {$gt : -180}, "loc.lon" : {$lt : 180}}).count();
    3685663

    > db.geodata_geo.find( { "loc" : { $exists : true }}).count();
    3685667

Эти запросы говорят мне, что у меня есть дельта из 4 записей, которая существует вне границы -180, 180.

Подождите … ВТ?

Я сосредотачиваюсь на $ gt / $ lt и задаюсь вопросом, есть ли у меня «крайний» случай. Учитывая, что я потерял 6 цифр моей мантиссы, мне интересно, округлила ли монго мои данные до моих краевых случаев 180:

> db.geodata_geo.find({"loc.lon" : 180 });

И я получаю ровно четыре записи, которые имеют значение lon ровно 180:

    "loc" : { "lon" : 180, "lat" : -16.1499996 }

Мне кажется, это ошибка в том, как mongodb индексирует геопространственные данные. Если 180 — допустимое значение для широты / долготы, то зачем выдавать ошибку, когда вы гарантируете индекс? Я решил решить эту проблему округления, расширив допустимые пределы моего запроса:

> db.geodata_geo.ensureIndex({ "loc" : "2d" }, { min : -180, max : 181 });
> db.geodata_geo.getIndexes();
[
	{
		"v" : 1,
		"key" : {
			"_id" : 1
		},
		"ns" : "dev_honeybadger.geodata_geo",
		"name" : "_id_"
	},
	{
		"v" : 1,
		"key" : {
			"loc" : "2d"
		},
		"ns" : "dev_honeybadger.geodata_geo",
		"name" : "loc_",
		"min" : -180,
		"max" : 181
	}
]

И я вижу, что мой геопространственный индекс создан. Теперь, чтобы проверить:

    > db.geodata_geo.find( { loc : {$near : [-50,50] } } ).limit(5);

И он немедленно  возвращает пять записей (Elliston, Bonavista, Elliston Station, Catalina and Port Union, Division # 7, в Канаде), которые я запросил.

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