Съгласен съм с общото схващане на други отговори, че това е гранична линия релационен проблем.
Ключът към моделите на данни MongoDB е тежестта при запис, но това може да бъде сложно за този случай на употреба, най-вече поради счетоводството, което би било необходимо, ако искате да свържете потребителите директно към елементи (промяна в група, която е последвана от партиди от потребители ще понесе огромен брой записвания и имате нужда от някой работник, който да направи това).
Нека да проучим дали моделът за четене е неприложим тук, или правим преждевременна оптимизация.
Подходът за тежко четене
Вашата основна грижа е следният случай на употреба:
истински проблем с производителността може да бъде, когато искам да получа всички групи, които потребителят следва за конкретен елемент [...], защото тогава трябва да намеря всички групи, които потребителят следва, и от това да намеря всички item_groups с group_id
$in
и идентификатора на елемента.
Нека да разчленим това:
-
Вземете всички групи, които потребителят следва
Това е проста заявка:
db.followers.find({userId : userId})
. Ще ни трябва индекс наuserId
което ще направи времето за изпълнение на тази операция O(log n) или изключително бързо дори за големи n. -
от това намерете всички item_groups с group_id
$in
и идентификатора на артикулаСега това е по-сложната част. Да предположим за момент, че е малко вероятно елементите да са част от голям брой групи. След това съставен индекс
{ itemId, groupId }
би работил най-добре, защото можем да намалим драстично набора кандидат чрез първия критерий - ако даден елемент е споделен само в 800 групи и потребителят следва 220 групи, mongodb трябва само да намери пресечната точка на тях, което е сравнително лесно, тъй като и двете комплектите са малки.
Все пак ще трябва да отидем по-дълбоко от това:
Структурата на вашите данни е вероятно тази на сложна мрежа . Сложните мрежи се предлагат в много разновидности, но има смисъл да приемем, че графиката на последователите ви е почти без мащаб, което също е почти най-лошият случай. В мрежа без мащаб, много малък брой възли (знаменитости, супер купа, Wikipedia) привличат много „внимание“ (т.е. имат много връзки), докато много по-голям брой възли имат проблеми с получаването на същото количество внимание комбиниран .
Малките възли не са причина за притеснение , заявките по-горе, включително двупосочни пътувания до базата данни, са в диапазон от 2 мс на моята машина за разработка на набор от данни с десетки милиони връзки и> 5GB данни. Сега този набор от данни не е огромен, но без значение каква технология изберете, ще бъде обвързан с RAM, тъй като индексите трябва да са в RAM във всеки случай (местността на данните и разделимостта в мрежите обикновено са лоши), а зададеният размер на пресичане е малък по дефиниция. С други думи:този режим е доминиран от хардуерни тесни места.
Какво ще кажете за супервъзлитете все пак?
Тъй като това би било предположение и много се интересувам от мрежови модели, си позволих да внедря драматично опростен мрежов инструмент, базиран на вашия модел на данни, за да направя някои измервания. (Съжалявам, че е на C#, но генерирането на добре структурирани мрежи е достатъчно трудно на езика, който владея най-добре...)
Когато отправям заявка към свръхвъзлите, получавам резултати в диапазона от 7 ms върхове (това е на 12 милиона записа в 1,3 GB db, като най-голямата група има 133 000 елемента в нея и потребител, който следва 143 групи.)
Предположениета в този код е, че броят на групите, следвани от потребител, не е голям, но това изглежда разумно тук. Ако не е, бих избрал подхода с тежко писане.
Чувствайте се свободни да играете с кода. За съжаление ще има нужда от малко оптимизация, ако искате да опитате това с повече от няколко GB данни, защото просто не е оптимизирано и прави някои много неефективни изчисления тук и там (особено бета-претегленото произволно разбъркване може да бъде подобрено ).
С други думи:все още не бих се притеснявал за ефективността на подхода с тежко четене . Проблемът често не е толкова, че броят на потребителите расте, а че потребителите използват системата по неочаквани начини.
Подходът за тежко писане
Алтернативният подход вероятно е да обърнете реда на свързване:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Това е може би най-мащабируемият модел на данни, но не бих се съгласил, освен ако не говорим за ОГРОМНИ количества данни, където разделянето е ключово изискване. Ключовата разлика тук е, че вече можем ефективно да разделим данните, като използваме userId като част от ключа на фрагмента. Това помага за паралелизиране на заявките, ефективно разделяне и подобряване на локалността на данните в сценарии с множество центрове за данни.
Това може да се тества с по-сложна версия на тестовото стенд, но все още не намерих време и, честно казано, мисля, че е излишно за повечето приложения.