Отличната производителност на базата данни е важна, когато разработвате приложения с MongoDB. Понякога цялостният процес на обслужване на данни може да се влоши поради редица причини, някои от които включват:
- Неподходящи модели за проектиране на схеми
- Неправилно използване или неизползване на стратегии за индексиране
- Неподходящ хардуер
- Закъснение при репликация
- Слабо ефективни техники за заявки
Някои от тези неуспехи може да ви принудят да увеличите хардуерните ресурси, докато други може да не. Например, лошите структури на заявката могат да доведат до това, че обработката на заявката отнема много време, причинявайки забавяне на репликата и може би дори загуба на данни. В този случай човек може да си помисли, че може би паметта за съхранение не е достатъчна и че вероятно има нужда от мащабиране. Тази статия обсъжда най-подходящите процедури, които можете да използвате, за да увеличите производителността на вашата база данни MongoDB.
Дизайн на схема
По принцип двете най-често използвани схеми са...
- Един към няколко
- Едно към много
Въпреки че най-ефективният дизайн на схемата е връзката „едно към много“, всяка от тях има своите предимства и ограничения.
Един към няколко
В този случай за дадено поле има вградени документи, но те не са индексирани с идентичност на обекта.
Ето един прост пример:
{
userName: "Brian Henry",
Email : "[email protected]",
grades: [
{subject: ‘Mathematics’, grade: ‘A’},
{subject: English, grade: ‘B’},
]
}
Едно от предимствата на използването на тази връзка е, че можете да получите вградените документи само с една заявка. Въпреки това, от гледна точка на заявката, не можете да получите достъп до един вграден документ. Така че, ако няма да препращате към вградените документи отделно, ще бъде оптимално да използвате този дизайн на схемата.
Едно към много
За тази връзка данните в една база данни са свързани с данни в друга база данни. Например, можете да имате база данни за потребители и друга за публикации. Така че, ако потребител направи публикация, тя се записва с потребителски идентификатор.
Схема на потребителите
{
Full_name: “John Doh”,
User_id: 1518787459607.0
}
Схема на публикации
{
"_id" : ObjectId("5aa136f0789cf124388c1955"),
"postTime" : "16:13",
"postDate" : "8/3/2018",
"postOwnerNames" : "John Doh",
"postOwner" : 1518787459607.0,
"postId" : "1520514800139"
}
Предимството на този дизайн на схемата е, че документите се считат за самостоятелни (могат да бъдат избрани отделно). Друго предимство е, че този дизайн позволява на потребителите с различни идентификационни номера да споделят информация от схемата на публикациите (оттук и името „Един към много“) и понякога може да бъде схема „N-to-N“ – основно без да използват присъединяване към таблицата. Ограничението с този дизайн на схемата е, че трябва да направите поне две заявки, за да извлечете или изберете данни във втората колекция.
Следователно начинът на моделиране на данните ще зависи от модела на достъп на приложението. Освен това трябва да вземете предвид дизайна на схемата, който обсъдихме по-горе.
Техники за оптимизация за проектиране на схеми
-
Използвайте възможно най-много вграждане на документи, тъй като то намалява броя на заявките, които трябва да изпълните за конкретен набор от данни.
-
Не използвайте денормализация за документи, които често се актуализират. Ако anfield ще се актуализира често, тогава ще има задача да се намерят всички екземпляри, които трябва да бъдат актуализирани. Това ще доведе до бавна обработка на заявките, следователно ще надхвърли дори достойнствата, свързани с денормализацията.
-
Ако има нужда от извличане на документ отделно, тогава не е необходимо да се използва вграждане, тъй като сложни заявки, като обобщен конвейер, отнемат повече време за изпълнение.
-
Ако масивът от документи, които трябва да бъдат вградени, е достатъчно голям, не ги вграждайте. Растежът на масива трябва да има поне ограничена граница.
Правилно индексиране
Това е по-критичната част от настройката на производителността и изисква от човек да има изчерпателно разбиране на заявките на приложението, съотношението на четене и записване и колко свободна памет има вашата система. Ако използвате индекс, тогава заявката ще сканира индекса, а не колекцията.
Отличен индекс е този, който включва всички полета, сканирани от заявка. Това се нарича комбиниран индекс.
За да създадете единичен индекс за полета, можете да използвате този код:
db.collection.createIndex({“fields”: 1})
За съставен индекс, за да създадете индексирането:
db.collection.createIndex({“filed1”: 1, “field2”: 1})
Освен по-бързото запитване чрез използване на индексиране, има допълнително предимство на други операции като сортиране, проби и ограничение. Например, ако проектирам моята схема като {f:1, m:1}, мога да извърша допълнителна операция, освен да намеря като
db.collection.find( {f: 1} ).sort( {m: 1} )
Четенето на данни от RAM е по-ефективно от четенето на същите данни от диск. Поради тази причина винаги се препоръчва да се уверите, че вашият индекс се вписва изцяло в RAM паметта. За да получите текущия размер на индекса на вашата колекция, изпълнете командата :
db.collection.totalIndexSize()
Ще получите стойност като 36864 байта. Тази стойност също не трябва да заема голям процент от общия размер на RAM, тъй като трябва да се погрижите за нуждите на целия работен комплект на сървъра.
Ефективната заявка също трябва да подобри селективността. Селективността може да се дефинира като способността на заявка да стеснява резултата с помощта на индекса. За да бъдат по-секантни, вашите заявки трябва да ограничат броя на възможните документи с индексираното поле. Селективността се свързва най-вече със съставен индекс, който включва поле с ниска селективност и друго поле. Например, ако имате тези данни:
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }
Заявката {a:7, b:“cd”} ще сканира 2 документа, за да върне 1 съответстващ документ. Ако обаче данните за стойността a са равномерно разпределени, т.е.
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }
Заявката {a:7, b:“cd”} ще сканира 1 документ и ще върне този документ. Следователно това ще отнеме по-кратко време от първата структура от данни.
ClusterControlЕдинична конзола за цялата ви инфраструктура на базата данни Открийте какво още е новото в ClusterControlИнсталирайте ClusterControl БЕЗПЛАТНООсигуряване на ресурси
Неадекватната памет за съхранение, RAM и други работни параметри могат драстично да влошат производителността на MongoDB. Например, ако броят на потребителските връзки е много голям, това ще попречи на способността на сървърното приложение да обработва заявките своевременно. Както е обсъдено в Ключови неща за наблюдение в MongoDB, можете да получите преглед на това с кои ограничени ресурси разполагате и как можете да ги мащабирате, за да отговарят на вашите спецификации. За голям брой едновременни заявки за приложения, системата на базата данни ще бъде претоварена, за да се справи с търсенето.
Закъснение при репликация
Понякога може да забележите, че някои данни липсват от вашата база данни или когато изтриете нещо, то се появява отново. Колкото и да имате добре проектирана схема, подходящо индексиране и достатъчно ресурси, в началото приложението ви ще работи безпроблемно без никакви проблеми, но след това в един момент забелязвате споменатите проблеми. MongoDB разчита на концепцията за репликация, при която данните се копират излишно, за да отговарят на някои критерии за проектиране. Предположение при това е, че процесът е мигновен. Въпреки това може да възникне известно забавяне, може би поради повреда на мрежата или необработени грешки. Накратко, ще има голяма разлика между времето, с което дадена операция се обработва на първичния възел, и времето, когато тя ще бъде приложена във вторичния възел.
Неуспехи с забавяне на реплика
-
Непоследователни данни. Това е особено свързано с операциите за четене, които са разпределени между вторични.
-
Ако разликата в изоставането е достатъчно голяма, тогава много нерепликирани данни може да са на първичния възел и ще трябва да бъдат съгласувани във вторичния възел. В даден момент това може да е невъзможно, особено когато основният възел не може да бъде възстановен.
-
Неуспехът при възстановяване на основния възел може да принуди човек да стартира възел с данни, които не са актуални и следователно може да изпусне цялата база данни, за да накара основния да се възстанови.
Причини за повреда на вторичния възел
-
Превъзхожда първичната мощност спрямо вторичната по отношение на спецификациите на процесора, дисковия IOPS и мрежовия вход/изход.
-
Сложни операции за запис. Например команда като
db.collection.update( { a: 7} , {$set: {m: 4} }, {multi: true} )
Първичният възел ще запише тази операция в oplog достатъчно бързо. Въпреки това, за вторичния възел, той трябва да извлече тези операции, да прочете в RAM всеки индекс и страници с данни, за да изпълни някои спецификации на критерии като идентификатора. Тъй като той трябва да направи това достатъчно бързо, за да поддържа скоростта, като основната възела изпълнява операцията, ако броят на операциите е достатъчно голям, тогава ще има очаквано забавяне.
-
Заключване на вторичния при правене на резервно копие. В този случай може да забравим да деактивираме основния, следователно ще продължим с неговите операции както обикновено. В момента, когато заключването ще бъде освободено, забавянето на репликацията ще бъде голямо, особено когато се работи с огромно количество архивиране на данни.
-
Изграждане на индекс. Ако във вторичния възел се натрупа индекс, тогава всички други операции, свързани с него, се блокират. Ако индексът е дълготраен, тогава ще се натъкне на забавяне на репликацията.
-
Несвързан вторичен. Понякога вторичният възел може да се повреди поради прекъсване на връзката с мрежата и това води до забавяне на репликацията при повторно свързване.
Как да сведем до минимум забавянето на репликацията
-
Използвайте уникални индекси, освен вашата колекция с полето _id. Това е, за да се избегне пълен провал на процеса на репликация.
-
Помислете за други видове архивиране, като моментни снимки и снимки на файловата система, които не изискват непременно заключване.
-
Избягвайте да създавате големи индекси, тъй като те причиняват блокиране на фона.
-
Направете вторичната достатъчно мощна. Ако операцията по запис е лека, тогава използването на вторични устройства с недостатъчно мощност ще бъде икономично. Но при тежки натоварвания при запис вторичният възел може да изостава от основния. За да бъде по-секатен, вторичният трябва да има достатъчно честотна лента, за да помогне за четенето на oplogs достатъчно бързо, за да поддържа скоростта си с основния възел.
Ефективни техники за заявка
Освен създаването на индексирани заявки и използването на селективност на заявки, както беше обсъдено по-горе, има и други концепции, които можете да използвате, за да закрепите и направите заявките си ефективни.
Оптимизиране на вашите заявки
-
Използване на покрита заявка. Покритата заявка е тази, която винаги е напълно удовлетворена от индекс, следователно не е необходимо да се проучва никакъв документ. Следователно покритата заявка трябва да има всички полета като част от индекса и следователно резултатът трябва да съдържа всички тези полета.
Нека разгледаме този пример:
{_id: 1, product: { price: 50 }
Ако създадем индекс за тази колекция като
{“product.price”: 1}
Като се има предвид операция за намиране, тогава този индекс ще покрие тази заявка;
db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0} )
и върнете само полето product.price и стойност.
-
За вградени документи използвайте нотацията с точки (.). Точковата нотация помага при достъп до елементи от масив и полета на вградения документ.
Достъп до масив:
{ prices: [12, 40, 100, 50, 40] }
За да посочите например четвъртия елемент, можете да напишете тази команда:
“prices.3”
Достъп до масив от обекти:
{ vehicles: [{name: toyota, quantity: 50}, {name: bmw, quantity: 100}, {name: subaru, quantity: 300} }
За да посочите полето за име в масива превозни средства, можете да използвате тази команда
“vehicles.name”
-
Проверете дали дадена заявка е покрита. За да направите това, използвайте db.collection.explain(). Тази функция ще предостави информация за изпълнението на други операции - напр. db.collection.explain().aggregate(). За да научите повече за функцията за обяснение, можете да разгледате objasni().
Като цяло, върховната техника, що се отнася до заявките, е използването на индекси. Запитването само на индекс е много по-бързо от заявката за документи извън индекса. Те могат да се поберат в паметта, следователно са налични в RAM, а не в диск. Това го прави достатъчно лесно и бързо, за да ги извлече от паметта.