ИТ развој

System Design: Making Your Database the Unsung Hero of Performance

Дизајн на системот: Како вашата база на податоци да стане неопеан херој на перформансите

Повеќето проблеми со перформансите не започнуваат во прелистувачот, балансерот на оптоварување или апликациската рамка. Тие започнуваат тивко во базата на податоци: барање што скенира премногу податоци, индекс што ја поддржува вчерашната шема на пристап, трансакција што останува отворена додека кодот чека на нешто друго.

Затоа дизајнот на базата на податоци заслужува централно место во системскиот дизајн. Вашата апликација може да има чисти PHP сервиси, разумни API граници и контејнери што се стартуваат за секунди, но база на податоци што извршува непотребна работа на крај ќе направи сите тие предности да изгледаат неважни.

Перформансите обично се проблем на пристап до податоци

Перформансите на задниот дел суштински се однесуваат на количината корисна работа извршена за секое барање. Кога на API крајна точка ѝ треба еден клиент и неговите неодамнешни нарачки, идеалниот пат е директен: ефикасно да се пронајде клиентот, ефикасно да се преземат потребните нарачки, да се врати намерно мал одговор.

Скапиот пат е помалку очигледен. Може да ги вчита сите нарачки, да филтрира во PHP, да прави дополнителни барања за секој поврзан запис, да сериски обработува непотребни колони и да држи трансакција отворена во текот на процесот. Секоја поединечна одлука може да изгледа безопасно. Заедно, тие создаваат бавни барања, зафатени врски со базата на податоци и нестабилна латентност при оптоварување.

Корисно дизајнерско прашање е: кои податоци му се потребни на оваа операција и како базата на податоци ќе ги пронајде? Поставувањето на ова прашање пред пишувањето на крајна точка често спречува многу подоцнежно прилагодување.

Моделирајте според барањата што треба да ги извршувате

Нормализацијата останува силен стандард бидејќи го штити интегритетот на податоците и го олеснува расудувањето за ажурирањата. Но, шемата треба да одразува и како системот чита и запишува податоци. Совршено нормализиран модел сепак може да има слаби перформанси ако вообичаените барања немаат соодветни индекси или бараат прекумерни спојувања за едноставно барање.

Да претпоставиме дека API за историја на нарачки вообичаено ги презема најновите нарачки на клиентот:

SELECT id, status, total_amount, created_at
FROM orders
WHERE customer_id = ?
ORDER BY created_at DESC
LIMIT 20;

Индекс што започнува со customer_id и вклучува created_at ја поддржува шемата на филтрирање и подредување:

CREATE INDEX idx_orders_customer_created_at
ON orders (customer_id, created_at DESC);

Точното однесување на индексот зависи од механизмот на базата на податоци, па треба да се провери со алатките за план на барања на тој механизам. Пошироката поука останува стабилна: индексирајте ги предикатите и шемите на подредување што се важни, наместо со надеж да ја индексирате секоја колона.

Секој индекс има цена. Тој троши простор за складирање, додава работа при вметнувања и ажурирања и може да ги усложни оперативните избори. Индексот е оправдан кога поддржува позната и важна шема на пристап. Тој не е замена за разбирање на барањето.

Читајте ги плановите за барања пред да нагаѓате

Кога барањето е бавно, проверете го неговиот план за извршување. Барајте целосни скенирања на големи табели, спојувања извршени во неочекуван редослед, сортирања над многу повеќе редови отколку што враќа крајната точка и проценки што значително се разликуваат од реалноста. Плановите за барања ги претвораат дискусиите за перформансите од интуиција во докази.

Проверете го и самото барање. Избирањето * е практично, но често расипничко. API обработувачите треба да ги бараат колоните што навистина им се потребни, особено кога табелите содржат големи текстуални полиња, JSON документи или полиња наменети само за внатрешни работни текови.

Држете ја работата со базата на податоци блиску до базата на податоци

Честа грешка во задниот дел е преземање широк сет податоци и примена на филтрирање, групирање или страницирање во PHP. PHP е одличен за апликациски правила и составување одговори. Базата на податоци обично е подобра во филтрирање засновано на множества, агрегација, подредување и спојување.

На пример, страницирањето обично треба да се случува во SQL, со детерминистичко подредување. За длабоки сетови резултати, страницирањето засновано на курсор често е попредвидливо од големо поместување бидејќи му дозволува на следното барање да продолжи од позната позиција.

SELECT id, status, created_at
FROM orders
WHERE customer_id = ?
  AND (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Ова бара подредување што е стабилно дури и кога повеќе редови го делат истиот временски печат. Парот created_at, id ја прави границата експлицитна. Соодветниот индекс треба да биде дизајниран и проверен за базата на податоци што се користи.

Избегнете ја скриената цена на N+1 барањата

N+1 барања се појавуваат кога кодот вчитува колекција, а потоа издава уште едно барање за секоја ставка во таа колекција. Крајната точка може да изгледа брза со три записи во развојна околина и да стане мачно бавна со стотици во продукција.

Во PHP, побезбеден стандард е поврзаните податоци да се преземат со планирано барање, ограничено спојување или мал број групирани барања. Подготвените изрази сè уште треба да се користат за вредности доставени во време на извршување:

$statement = $pdo->prepare(
    'SELECT id, status, total_amount, created_at
     FROM orders
     WHERE customer_id = :customer_id
     ORDER BY created_at DESC
     LIMIT 20'
);

$statement->execute(['customer_id' => $customerId]);
$orders = $statement->fetchAll(PDO::FETCH_ASSOC);

Ова ги подобрува и безбедноста и јасноста. Исто така, на тимот му дава едно видливо барање за мерење, објаснување и оптимизација.

Трансакциите треба да бидат кратки и целисходни

Трансакциите ја штитат конзистентноста, но не се општа обвивка за целата обработка на барањето. Трансакција што вклучува далечински HTTP повици, обработка на датотеки, долготрајни пресметки или интеракција со корисникот може да држи заклучувања подолго од потребното. При истовременост, тоа создава конкуренција и може да го претвори нормалниот сообраќај во заостаток.

Дефинирајте ја најмалата единица работа што мора да успее или да не успее заедно. Потврдете го влезот пред да ја отворите трансакцијата кога е можно. Извршете ги само потребните промени во базата на податоци во неа, потврдете веднаш и обработете ги неуспесите со јасна патека за поништување.

$pdo->beginTransaction();

try {
    $update->execute(['status' => 'paid', 'id' => $orderId]);
    $insertEvent->execute(['order_id' => $orderId, 'type' => 'paid']);

    $pdo->commit();
} catch (Throwable $exception) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    throw $exception;
}

За операции што може повторно да се обидат, дизајнирајте за идемпотентност. Повторен обид по истекување на време не смее случајно да создаде дупликати нарачки, плаќања или настани. Уникатните ограничувања и внимателно избраните клучеви за идемпотентност често се посигурни од претпоставките на ниво на апликација.

Направете ги оперативните ограничувања дел од дизајнот

Docker го олеснува извршувањето на апликација и база на податоци заедно, но контејнерите не ги отстрануваат ограничувањата на базата на податоци. Ограничувањата на врски, латентноста на дискот, притисокот врз меморијата, резервните копии, миграциите и заостанувањето на репликите остануваат прашања на системски дизајн.

Управувањето со врски е особено важно за PHP апликации. Ако секој апликациски процес може да создава врски без јасно ограничување, скок во сообраќајот може да ја преоптовари базата на податоци пред искористеноста на CPU да изгледа алармантно. Димензионирајте ја истовременоста на апликацијата според капацитетот на врски на базата на податоци, оставете простор за административен пристап и мерете ги активните врски при реалистично оптоварување.

Кеширањето може да ги намали повторените читања, но треба да следи по разбирањето, а не да го замени. Кеширајте податоци со јасен модел на сопственост, стратегија за истекување и пристап за поништување. Кеш што не може безбедно да застари не е едноставен кеш; тој е уште еден систем за конзистентност што мора соодветно да се дизајнира.

Дизајнирајте за промени, не само за денешниот репер

Најтрајните одлуки за базата на податоци ги прават идните промени побезбедни. Користете миграции што може да се применуваат повеќепати и да се прегледуваат како апликациски код. Додавајте ограничувања таму каде што деловното правило припаѓа во моделот на податоци. Следете ги бавните барања и стапките на грешки. Тестирајте со репрезентативни обеми на податоци, не само со празни локални бази на податоци.

Базата на податоци не е пасивен слој за складирање на крајот од архитектонски дијаграм. Таа е активен механизам за извршување, граница на конзистентност и често највредниот извор на вистина во системот. Однесувајте се кон неа со тоа ниво на грижа, и таа ќе стане неопеаниот херој: ќе извршува помалку непотребна работа, ќе ја зачувува исправноста под притисок и ќе му дава простор на остатокот од апликацијата да остане брз.

Портрет на автор на блогот

Mihajlo

Јас сум Михајло - развивач поттикнат од љубопитност, дисциплина и постојаната желба да создадам нешто значајно. Споделувам увиди, упатства и бесплатни услуги за да им помогнам на другите да ја поедностават својата работа и да растат во постојано развивачкиот свет на софтверот и вештачката интелигенција.