Совладување со перформансите на базите на податоци: Надвор од едноставното подесување, кон предвидувачка архитектура
Повеќето забавувања на базата на податоци не започнуваат како проблеми со базата на податоци. Тие започнуваат како навидум безопасни избори во апликацијата: крајна точка што вчитува една дополнителна релација по ред, барање за контролна табла што расте со секој нов клиент, политика за повторни обиди што краток прекин го претвора во бура од конекции. Додавањето индекс или зголемувањето на меморијата може да помогне, но самото подесување е реактивна дисциплина. Предвидливите перформанси доаѓаат од архитектура што ја прави скапата работа видлива, ограничена и намерна.
Оваа разлика е важна бидејќи базата на податоци се наоѓа во центарот на многу спротивставени потреби. API-јата имаат потреба од ниска латентност. Позадинските задачи имаат потреба од пропусен опсег. Известувањето има потреба од широки читања. Запишувањата имаат потреба од точност. Целта не е база на податоци што е „брза“ во апстрактна смисла; туку систем чиј обем на работа останува разбирлив додека се менуваат сообраќајот, обемот на податоци и сложеноста на производот.
Започнете со обликот на обемот на работа
Пред да промените барање, опишете што системот бара базата на податоци да направи. Кои барања се чувствителни на латентност? Кои операции може да се извршуваат асинхроно? Дали читањата се претежно директни пребарувања, филтрирани листи, агрегати или пребарувања на цел текст? Дали еден закупец има радикално повеќе податоци од друг?
Ова спречува честа грешка: оптимизирање на барање без да се разбере дали се наоѓа на критичната патека. Извештај што се извршува еднаш во текот на ноќта има различни барања од барање за наплата што држи отворена трансакција. И двете заслужуваат точност, но не треба да се натпреваруваат за истата цел за одзивност.
Корисните прашања вклучуваат:
- Која е очекуваната големина на резултатот за оваа крајна точка?
- Како се менува трошокот на барањето кога табелата ќе порасне десеткратно?
- Дали барањето филтрира пред спојувањето или прво создава голем меѓурезултат?
- Може ли оваа работа да се кешира, однапред пресмета, страницира или премести во работник?
- Што се случува кога зависност е бавна или привремено недостапна?
Овие прашања ги претвораат перформансите од збирка трикови во навика за преглед на дизајнот.
Користете индекси за поддршка на шемите за пристап
Индексот не е општ прекинувач за брзина. Тој е структура на податоци што помага при конкретни предикати, спојувања и подредувања. Додавањето индекси без разгледување на вистинските шеми на барања може да го зголеми трошокот на запишување, да троши простор за складирање и да ја остави бавната патека недопрена.
Разгледајте API што ги прикажува неодамнешните нарачки на клиент. Шемата за пристап обично е „филтрирај по клиент, подреди по време на создавање, врати ограничена страница“. Композитниот индекс треба да ја одразува таа шема, во согласност со оптимизаторот на моторот за базата на податоци и точниот облик на барањето.
SELECT id, status, total, created_at
FROM orders
WHERE customer_id = :customer_id
ORDER BY created_at DESC
LIMIT :limit;
Важниот дел не е меморирањето рецепт за индекс. Тоа е проверка на планот за извршување и потврдување дека избраниот индекс ја намалува работата што ја извршува базата на податоци. Плановите откриваат дали моторот скенира многу повеќе редови од очекуваното, врши скапо подредување или избира редослед на спојување што повеќе не одговара на распределбата на податоците.
На индексите им е потребна и дисциплина во одржувањето. Кога крајна точка ги менува филтрите или подредувањето, повторно разгледајте ги релевантните индекси. Кога се предлага нов индекс, прашајте кои запишувања сега ќе плаќаат за него. Здрава шема е усогласена со тековниот обем на работа, а не украсена со секој индекс што некогаш звучел веродостојно.
Спречете експлозии на барања на ниво на апликација
Многу PHP апликации го доживуваат своето прво сериозно тесно грло во базата на податоци преку шаблонот на барања N+1. Кодот презема страница со родителски записи, а потоа издава едно дополнително барање по запис за да вчита релација. Може да изгледа чисто во ORM, но листа од 100 ставки може да стане 101 повратно патување до базата на податоци.
Решението обично е нетрпеливо вчитување, внимателно дизајнирано спојување или групирано пребарување. Вистинскиот избор зависи од тоа колку поврзани податоци се потребни и дали добиеното барање дуплира редови. Принципот е едноставен: бројот на операции со базата на податоци нека расте со барањето, а не со секоја ставка вратена од барањето.
$orders = Order::query()
->where('customer_id', $customerId)
->with('lineItems')
->orderByDesc('created_at')
->limit(50)
->get();
Нетрпеливото вчитување не е автоматски бесплатно. Вчитувањето голема релација за секој ред може едноставно да замени многу мали барања со еден преголем резултат. Изберете ги само потребните колони, наметнете ограничувања на границата на API-то и избегнувајте враќање неограничени колекции. Перформансите и одржливоста често се подобруваат заедно кога една крајна точка има прецизен договор.
Дизајнирајте ги трансакциите како кратки, намерни граници
Трансакциите ги штитат инваријантите, но долготрајните трансакции може да држат заклучувања, да задржуваат стари верзии на редови и да блокираат корисна работа. Сигурно правило е делот од трансакцијата што се однесува на базата на податоци да биде тесен. Каде што е можно, валидирајте ги влезовите пред да влезете во неа и не повикувајте далечински услуги додека трансакцијата е отворена.
На пример, создавањето нарачка може да бара резервирање залиха и евидентирање намера за плаќање. Трансакцијата во базата на податоци треба да ги заштити локалните премини на состојби. Комуникацијата со надворешен давател на плаќања припаѓа во работен тек дизајниран за повторни обиди и усогласување, а не во трансакција што чека на мрежата.
Ова природно води кон идемпотентност. API-јата и редиците за задачи прават повторни обиди бидејќи неуспесите се случуваат откако барањето е испратено, но пред да се прими одговор. Дајте им на операциите за запишување клуч за идемпотентност или друг стабилен деловен идентификатор, спроведете единственост каде што е соодветно и направете повторената испорака да биде безбедна. Повторен обид не треба да создаде втора нарачка само затоа што првиот одговор бил изгубен.
Одделете ги оперативните читања од аналитичкиот апетит
Трансакциските бази на податоци се одлични за опслужување на непосредната состојба на производот. Често се лошо место за извршување широки, чести аналитики врз активни табели. „Едноставен“ агрегат може да се натпреварува со сообраќајот од клиенти штом основните податоци станат доволно големи.
Започнете со умерено раздвојување: кеширајте скапи, но стабилни резимеа, одржувајте бројачи или модели за читање кога барањата за конзистентност го дозволуваат тоа и закажете тешка работа за известување надвор од критичните патеки на барањата. Како што растат потребите, може да биде оправдан посебен процес за известување или аналитичко складиште. Одлуката треба да следи докази за обемот на работа, а не мода.
Истото размислување важи и за кеширањето. Кеширајте вредности со јасна стратегија за поништување и дефиниран сопственик. Не користете кеш за да скриете неограничено барање или нејасен модел на податоци. Кешот треба да намалува позната, повторлива работа; не треба да стане втора база на податоци чија точност никој не може да ја објасни.
Направете го однесувањето во продукција набљудливо
Предвидливата архитектура зависи од повратни информации. Следете ја латентноста на барањата, употребата на конекции со базата на податоци, бавните барања, стапките на грешки, длабочината на редиците и времето што задачите го поминуваат чекајќи наспроти извршувајќи се. Каде што вашите алатки го дозволуваат тоа, поврзете ја активноста на базата на податоци со имиња на крајни точки, типови задачи и верзии на распоредување.
Управувањето со конекциите заслужува посебно внимание во распоредувања со контејнери. Секој PHP работник или долготраен процес може да држи конекции, а скалирањето на контејнерите на апликацијата без да се земе предвид ограничувањето на конекции на базата на податоци може брзо да го исцрпи. Димензионирајте ги базените конзервативно, правилно затворајте или враќајте ги конекциите и осигурете се дека базата на податоци се третира како конечен споделен ресурс, а не како еластична локална услуга.
Во средини засновани на Docker, стартувањето на апликацијата и подготвеноста на базата на податоци се исто така различни настани. Процесот во контејнерот може да започне пред базата на податоци да прифаќа конекции. Апликациите треба да обработуваат привремени неуспеси на конекцијата со ограничени повторни обиди и одложување, додека распоредувањата треба да ја проверуваат подготвеноста преку соодветни здравствени проверки. Бескрајните повторни обиди не се отпорност; тие може да го одложат закрепнувањето и да го засилат оптоварувањето за време на прекин.
Перформансите се производ на границите
Најтрајните подобрувања на базата на податоци ретко доаѓаат од една драматична промена на конфигурацијата. Тие доаѓаат од граници: ограничени множества резултати, кратки трансакции, експлицитно однесување при повторни обиди, наменски индекси, одделени обеми на работа и набљудливи тесни грла.
Тоа е преминот од подесување кон архитектура. Подесувањето прашува: „Како ова барање може да се извршува побрзо денес?“ Предвидливата архитектура прашува: „Што спречува овој обем на работа да стане изненадувачки утре?“ Второто прашање создава системи што не се само побрзи под притисок, туку и полесни за следниот развивач да ги разбере, управува и менува со доверба.