LINUX.ORG.RU

mongodb pagination

 , , ,


0

2

Имеется таблица содержащая 912,842 записей. При обращении к первым страницам всё ok. Однако при обращении к последней странице виснет весь комп на несколько минут. Вопрос - что делать? Заказчик требует доступа к произвольной странице.

$products = $db->products->find()->limit($itemsPerPage)->skip(($page - 1) * $itemsPerPage);

В Job помогут, раз самому лень хоть какой-то анализ провести.

Deleted ()

Заказчик требует доступа к произвольной странице.

И обязательно по номеру? Гутлак.

bj ()

Какой комп виснет?

Siado ★★★★★ ()
Ответ на: комментарий от Vit

Да пробовал уже все индексы. Там ещё и поиск по этой базе требуется. Но даже боюсь представить что будет с поиском если просто на отображение последней страницы уходит несколько минут. Я был лучшего мнения о современных DB а оказывается они даже миллион записей не могут осилить. Может есть какое то готовое решение для такой задачи? Добавление новых данных происходит нечасто так может как то при добавлении оптимизировать что бы ускорить отображение?

mongo ()
Ответ на: комментарий от Siado
$ cat /proc/cpuinfo 
processor	: 0
vendor_id	: AuthenticAMD
cpu family	: 15
model		: 107
model name	: AMD Athlon(tm) 64 X2 Dual Core Processor 5400+
stepping	: 2
cpu MHz		: 2800.190
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 2
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 1
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good nopl extd_apicid pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy 3dnowprefetch lbrv
bogomips	: 5600.38
TLB size	: 1024 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management: ts fid vid ttp tm stc 100mhzsteps

processor	: 1
vendor_id	: AuthenticAMD
cpu family	: 15
model		: 107
model name	: AMD Athlon(tm) 64 X2 Dual Core Processor 5400+
stepping	: 2
cpu MHz		: 2800.190
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 1
cpu cores	: 2
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 1
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good nopl extd_apicid pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy 3dnowprefetch lbrv
bogomips	: 5600.08
TLB size	: 1024 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management: ts fid vid ttp tm stc 100mhzsteps

KiB Mem: 4,945,752 KiB Swap: 524,284

mongo ()
Ответ на: комментарий от Deleted

В Job помогут, раз самому лень хоть какой-то анализ провести.

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

mongo ()
Ответ на: комментарий от bj

И обязательно по номеру? Гутлак.

А как ещё? Ну например у гугла гораздо больше данных и они как то осуществляют поиск по всему тексту этого огромного количества документов каждый раз. Известно что они используют? Какое БД? Или какой то супер оптимизированный custom code на низкоуровневом языке?

mongo ()
Ответ на: комментарий от mongo

Пагинация в монге работает. Я ж дал ссылку, как смотреть что тормозит.

Если напрямую не хочет, значит надо выбрать только IDs по coverage index, а потом уже конкретные посты целиком по полученному списку.

Сначала добейтесь, чтобы в explain не было сканов, и только потом, если со скоростью будут проблемы, приходите жаловаться.

Vit ★★★★★ ()
Ответ на: комментарий от mongo

Ну например у гугла гораздо больше данных

У гугла нельзя прыгнуть на действительно произвольную страницу, как и у яндекса. А те страницы, которые доступны, там даже со skip-ом работать будут.

risenshnobel ★★★ ()
Ответ на: комментарий от Vit

Существует ли какая то конфигурация в mongodb как например помнится в MySQL при аналогичной проблеме после увеличения какого то параметра (something_limit) в конфигах индекс начал умещатся в памяти и проблема решилась при ~2 миллионах записей.

mongo ()
Ответ на: комментарий от Vit

Вот такой запрос выполняется практически моментально:

db.products.find({facets: {$elemMatch: {name: 'all', Lvalue: 'pede'}}}).skip(15600).limit(100)

А вот с sort'ом:

db.products.find({facets: {$elemMatch: {name: 'all', Lvalue: 'pede'}}}).sort({dealerPrice:1})

занимает несколько минут.

Индексы:

{ "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "feeds.products" }
{ "v" : 1, "key" : { "Warehouse" : NumberLong(1) }, "name" : "Warehouse_1", "ns" : "feeds.products" }
{ "v" : 1, "key" : { "uniqueIdentifier" : NumberLong(1) }, "name" : "uniqueIdentifier_1", "ns" : "feeds.products" }
{ "v" : 1, "key" : { "source" : NumberLong(1) }, "name" : "source_1", "ns" : "feeds.products" }
{ "v" : 1, "key" : { "facets.name" : NumberLong(1), "facets.Lvalue" : NumberLong(1) }, "name" : "facets_name_1_facets_Lvalue_1", "ns" : "feeds.products" }
{ "v" : 1, "key" : { "dealerPrice" : NumberLong(1) }, "name" : "dealerPrice_1", "ns" : "feeds.products" }

Как видите index на dealerPrice есть. В чём может быть причина? MongoDb 2.7 написано что может использовать несколько индексов в одном запросе.

> db.products.find({facets: {$elemMatch: {name: 'all', Lvalue: 'pede'}}}).sort({dealerPrice:1}).explain()

{
	"cursor" : "BtreeCursor dealerPrice_1",
	"isMultiKey" : false,
	"n" : 15651,
	"nscannedObjects" : 764654,
	"nscanned" : 764654,
	"nscannedObjectsAllPlans" : 774131,
	"nscannedAllPlans" : 774131,
	"scanAndOrder" : false,
	"indexOnly" : false,
	"nYields" : 11251,
	"nChunkSkips" : 0,
	"millis" : 137068,
	"indexBounds" : {
		"dealerPrice" : [
			[
				{
					"$minElement" : 1
				},
				{
					"$maxElement" : 1
				}
			]
		]
	},
	"server" : "localhost:27017",
	"filterSet" : false
}
> db.products.find({facets: {$elemMatch: {name: 'all', Lvalue: 'pede'}}}).skip(15600).limit(100).explain()
{
	"cursor" : "BtreeCursor facets_name_1_facets_Lvalue_1",
	"isMultiKey" : true,
	"n" : 51,
	"nscannedObjects" : 15651,
	"nscanned" : 15651,
	"nscannedObjectsAllPlans" : 15651,
	"nscannedAllPlans" : 15651,
	"scanAndOrder" : false,
	"indexOnly" : false,
	"nYields" : 122,
	"nChunkSkips" : 0,
	"millis" : 97,
	"indexBounds" : {
		"facets.name" : [
			[
				"all",
				"all"
			]
		],
		"facets.Lvalue" : [
			[
				"pede",
				"pede"
			]
		]
	},
	"server" : "localhost:27017",
	"filterSet" : false
}

Можно ли что то сделать если в задании сказано что вывод должен быть отсортирован по dealerPrice или выходить на переговоры с заказчиком и говорить что так невозможно сделать не боясь оказаться не компетентным?

mongo ()
Ответ на: комментарий от Vit

Так я уже перепробовал все индексы и их комбинации.

mongo ()
Ответ на: комментарий от Vit

Почему «nScannedObjects» : 764,654 т.е. все имеющиеся в таблице? Ведь sort должен выполняться после find а find находит всего 15,651.

mongo ()
Ответ на: комментарий от mongo

db.products.find({facets: {$elemMatch: {name: 'all', Lvalue: 'pede'}}}).sort({dealerPrice:1}) занимает несколько минут.

добавь в индекс, кот обслуживает facets, индекс по dealerPrice: facets_name_1_facets_Lvalue_1_dealerPrice_1

VladimirMalyk ★★★★★ ()
Ответ на: комментарий от VladimirMalyk

db.products.ensureIndex({«facets.name»:1,«facets.Lvalue»:1,«dealerPrice»:1}) ? Уже пробовал, не помоло вообще.

mongo ()
Ответ на: комментарий от mongo

В explain cursor вообще какой то QueryOptimizerCursor о котором ни слова нет в документации. Ещё появились какие то «clauses» c «cursor»: «BasicCursor».

> db.products.find().sort({"facets.1.Lvalue":1}).skip(10000).limit(100).explain()
{
	"clauses" : [
		{
			"cursor" : "BasicCursor",
			"isMultiKey" : false,
			"n" : 10100,
			"nscannedObjects" : 764654,
			"nscanned" : 764654,
			"scanAndOrder" : true,
			"indexOnly" : false,
			"nChunkSkips" : 0
		},
		{
			"cursor" : "BasicCursor",
			"isMultiKey" : false,
			"n" : 0,
			"nscannedObjects" : 0,
			"nscanned" : 0,
			"scanAndOrder" : true,
			"indexOnly" : false,
			"nChunkSkips" : 0
		}
	],
	"cursor" : "QueryOptimizerCursor",
	"n" : 100,
	"nscannedObjects" : 764654,
	"nscanned" : 764654,
	"nscannedObjectsAllPlans" : 764654,
	"nscannedAllPlans" : 764654,
	"scanAndOrder" : false,
	"nYields" : 7711,
	"nChunkSkips" : 0,
	"millis" : 61427,
	"server" : "localhost:27017",
	"filterSet" : false
}
mongo ()
Ответ на: комментарий от mongo

Да и чего уж там говорить о поиске если даже просто find() без параметров при использовании .sort({dealerPrice:1}).skip(10000).limit(100) тормозит жутко (и это при относительно небольшом смещении 10,000 по сравнению с последней страницей). Индекс { «v» : 1, «key» : { «dealerPrice» : NumberLong(1) }, «name» : «dealerPrice_1», «ns» : «feeds.products» } есть. Так что ещё можно сделать? Вот товарищ выше намекнул на некий coverage index но толком не рассказал, такой опции к ensureIndex в документации не нашёл. Экспериментировать очень сложно т.к. при каждом движении приходится долго ждать пока тормозная база данных закончит обработку (создаст индекс, выполнит explain()).

mongo ()
Ответ на: комментарий от mongo

skip сам по себе очень дорогая операция — потому что все эти 10000 надо и отобрать и отсортировать.

VladimirMalyk ★★★★★ ()
Ответ на: комментарий от VladimirMalyk

Вот я сейчас попробовал db.products.find().sort({dealerPrice:1}).limit(2).skip(100000).explain(). Первый раз это заняло 74 секунды. Но повторно выдалось практически мгновенно. skip(100100) так же выдался практически мгновенно. Можно ли как то сохранить этот эффект?

Я непонимаю для чего тогда индексы вообще если всё равно всё сканируется?

А если я сохраню в файл просто все записи типа {dealerPrice,id} упорядоченные по dealerPrice? Установлю какой то постоянный размер записи, например 100 byte и буду дополнять \0 если запись меньше что бы записи были одинакового размера. Тогда мне для обращения к старнице n надо будет просто открыть файл, сделать lseek на позицию (n - 1) * $itemsPerPage и считать $itemsPerPage*100 byte. Что будет практически моментально. Так придётся делать такой костыль или в базах данных уже есть такой функционал который я не знаю как задействовать? Я думал индексы как раз примерно это и делают.

mongo ()
Ответ на: комментарий от mongo

Меня никода не интересовало, как работает база с кривыми индексами.

Vit ★★★★★ ()
Ответ на: комментарий от mongo

Видимо надо матчасть учить, вместо того чтобы наугад пробовать. У меня почему-то пагинация работает без сканов. Глава по coverage index есть в руководстве по mysql.

Vit ★★★★★ ()
Последнее исправление: Vit (всего исправлений: 1)
Ответ на: комментарий от Vit

Меня никода не интересовало, как работает база с кривыми индексами.

В каком месте они кривые? Обычный индекс db.products.ensureIndex({dealerPrice:1}); Каких то опций не хватает или что? Ну покажите прямые индексы если знаете.

mongo ()
Ответ на: комментарий от Harald

enjoy your NoSQL :P

За эту неделю я уже пробовал перенести базу в MySQL. Так там всё ещё хуже. Парсер добавляет ~800,000 записей в mongo за пару часов, а в MySQL за всю ночь пока я спал только ~300,000 добавилось из имеющихся ~800,000. SELECT тормозил не меньше уже при таком количестве записей. И это был простой SELECT без JOIN.

mongo ()
Ответ на: комментарий от Vit

Видимо надо матчасть учить, вместо того чтобы наугад пробовать. У меня почему-то пагинация работает без сканов. Глава по coverage index есть в руководстве по mysql.

Вы либо придумали свой термин либо что то путаете т.к. никакой главы с таким названием там найти не удалось а гугл вообще по запросу «coverage index» выдает какие то данные по социологии.

mongo ()
Ответ на: комментарий от mongo

В каком месте они кривые?

Они не решают поставленную задачу

Ну покажите прямые индексы если знаете.

Откуда ж мне знать, если вы схему данных не расписали? Я вам и так уже сказал, куда смотреть и что читать, чтобы угадайками не заниматься. И сказал, как ограничения монги обойти (вначала выбирать id, потом по ним документы), если дело в специфику упрется.

Сортировка, кстати, не обязана быть в конце. Иногда выгоднее в середине сортировать. Но все это при условии, что используется coverage index.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Ну вот например простейший запрос:

> db.products.find().sort({_id:1}).skip(100000).limit(100).explain()
{
	"cursor" : "BtreeCursor _id_",
	"isMultiKey" : false,
	"n" : 100,
	"nscannedObjects" : 100100,
	"nscanned" : 100100,
	"nscannedObjectsAllPlans" : 100100,
	"nscannedAllPlans" : 100100,
	"scanAndOrder" : false,
	"indexOnly" : false,
	"nYields" : 921,
	"nChunkSkips" : 0,
	"millis" : 3612,
	"indexBounds" : {
		"_id" : [
			[
				{
					"$minElement" : 1
				},
				{
					"$maxElement" : 1
				}
			]
		]
	},
	"server" : "localhost:27017",
	"filterSet" : false
}

Индекс на _id создаётся вообще самой БД автоматически, т.е. я свои кривые руки к его созданию вообще не прикладывал.

mongo ()
Ответ на: комментарий от mongo

«covered index» поищите, в сочетании с названиями баз.

Vit ★★★★★ ()
Ответ на: комментарий от mongo

Потому что в `find()` сказано выбирать документы целиком. Вот монга и выбирает, вместо того чтобы из индекса значение вытащить.

Если указать там только _id, будет быстро.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Откуда ж мне знать, если вы схему данных не расписали? Я вам и так уже сказал, куда смотреть и что читать, чтобы угадайками не заниматься. И сказал, как ограничения монги обойти (вначала выбирать id, потом по ним документы), если дело в специфику упрется.

Сортировка, кстати, не обязана быть в конце. Иногда выгоднее в середине сортировать. Но все это при условии, что используется coverage index.

Ну вот вам упрощённая схема:

db.products.ensureIndex({dealerPrice:1});
for (var i = 0; i < 1000000; i++) {
  db.products.save({dealerPrice:i});
}
db.products.find().sort({dealerPrice:1}).skip(100000).limit(100);

Так увижу я наконец ваш загадочный coverage index?

mongo ()
Ответ на: комментарий от Vit

Потому что в `find()` сказано выбирать документы целиком. Вот монга и выбирает, вместо того чтобы из индекса значение вытащить.

Если указать там только _id, будет быстро.

Вот это действительно помогло:

> db.products.find({},{_id:1}).sort({_id:1}).skip(100000).limit(100).explain()
{
	"cursor" : "BtreeCursor _id_",
	"isMultiKey" : false,
	"n" : 100,
	"nscannedObjects" : 0,
	"nscanned" : 100100,
	"nscannedObjectsAllPlans" : 0,
	"nscannedAllPlans" : 100100,
	"scanAndOrder" : false,
	"indexOnly" : true,
	"nYields" : 782,
	"nChunkSkips" : 0,
	"millis" : 140,
	"indexBounds" : {
		"_id" : [
			[
				{
					"$minElement" : 1
				},
				{
					"$maxElement" : 1
				}
			]
		]
	},
	"server" : "localhost:27017",
	"filterSet" : false
}

Ну тогда можно сказать проблема решена, правда я так и не понял почему это не сделано автоматически - сначала выбрать только _id потом остальное по _id. В любом случае это элементарно реализовать.

mongo ()
Ответ на: комментарий от mongo

правда я так и не понял почему это не сделано автоматически - сначала выбрать только _id потом остальное по _id.

Наверное у оптимизатора запросов libastral не прокачан. Прямое указание полей помогает почти всегда.

Ориентируйтесь по статьям для mysql про оптимизацию индексов, там основы расписаны намного лучше. Потом смотрите монговские рекомендации, на предмет того, что монга не умеет (бывают косяки на range и сортировке). На index merge вообще не расчитывайте, это костыль для ситуаций когда все совсем плохо.

Вцелом, пагинация работает, но условий выборки должно быть не очень много (иначе на записи все может просесть), и часто приходится в 2 захода делать - сначала id, потом документы.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Вот про это в документации ни слова! Ну или так спрятано что я за неделю не нашёл. Это очевидно баг документации.

mongo ()
Ответ на: комментарий от mongo

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

Vit ★★★★★ ()
Ответ на: комментарий от Vit

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

mongo ()
Ответ на: комментарий от mongo

С монги индексы осваивать - фиговая идея. Нормальная основа - сиквель. Там навалом туториалов и индексы расписаны вдоль и поперек. Разберетесь с сиквелем - потом не будет проблем с расковыриванием монговских эксплейнов. У вас же и в сиквеле пагинация тухло работала.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Что за сиквель?

Си́квел[1] (англ. sequel [siːkwəl], от лат. sequo — продолжаю, следую за) — книга, фильм или любое другое произведение искусства, по сюжету являющееся продолжением другого произведения.

https://ru.wikipedia.org/wiki/Сиквел

mongo ()
Ответ на: комментарий от mongo

sql.

А конкретно - mysql, как самый популярный.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Различные ORM (типа ActiveRecord, etc.) идут на такие ухищрения (выбрать сначала только _id, потом .find({_id:{$in:ids}}))?

mongo ()
Ответ на: комментарий от mongo

Без понятия, я не использую ORM - слишком медленно и слишком высоки шансы нагенерить хреновых запросов. Мне хватает DSL.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

А какой именно DSL? Какие существуют решения для этой проблемы?

mongo ()
Ответ на: комментарий от mongo

А какой именно DSL?

mongoose с опцией { lean: true }

Vit ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.