Дизајн на бази на податоци: Инженерска предвидливост под оптоварување
Повеќето инциденти со бази на податоци не започнуваат со драматичен отказ. Тие започнуваат со навидум безопасно барање, пригоден тип на колона или крајна точка што функционира совршено со мал сет податоци. Потоа расте сообраќајот, се насобираат редови, пристигнуваат истовремени барања и базата на податоци станува местото каде што малите дизајнерски кратенки се претвораат во непредвидлива латентност.
Добриот дизајн на база на податоци не значи бркање идеален дијаграм на шема. Станува збор за инженеринг на предвидливо однесување под реално оптоварување: познати патеки на барања, јасна сопственост на податоците, ограничена работа по барање и режими на откажување што се разбирливи кога системот е зафатен.
Дизајнирајте според моделите на пристап, а не само според ентитетите
Односите меѓу ентитетите се важни, но перформансите во продукција се обликувани од начинот на кој апликацијата чита и запишува податоци. Шемата може да изгледа прекрасно нормализирана, а сепак секое важно барање да го принудува системот на скапи спојувања, широки скенирања или повторени повратни патувања.
Почнете со именување на барањата што вашиот систем мора да ги извршува. За API за нарачки, тоа може да вклучува:
- Преземање една нарачка според нејзиниот јавен идентификатор.
- Прикажување на неодамнешните нарачки на клиентот со страницирање.
- Наоѓање нарачки што чекаат обработка на плаќање.
- Атомско евидентирање на нарачка и нејзините ставки.
Секое барање подразбира ограничувања за клучевите, индексите, подредувањето и страницирањето. Ако барањето најчесто филтрира според customer_id и подредува според created_at, индекс усогласен со таа патека обично е покорисен од одделни индекси избрани само затоа што секоја колона се појавува во филтер.
SELECT id, status, total_amount, created_at
FROM orders
WHERE customer_id = :customer_id
AND created_at < :cursor
ORDER BY created_at DESC
LIMIT 50;
Поддржувачкиот индекс треба да ги одразува филтерот и редоследот што ги користи барањето. Точната синтакса на индексите и однесувањето на оптимизаторот се разликуваат според моторот на базата на податоци, затоа проверете го планот со моторот што го користите наместо да третирате кое било правило како универзално.
Избирајте идентификатори намерно
Примарните клучеви се инфраструктура. Тие влијаат врз спојувањата, големината на индексите, локалноста на запишувањата, URL-адресите, миграциите и границите на интеграција. Нумерички интерен клуч е компактен и ефикасен за многу релациски оптоварувања. Генериран јавен идентификатор може да спречи лесно набројување во надворешни API-ја. Овие потреби не мора да се натпреваруваат: често е разумно да се задржи интерен примарен клуч и посебно индексиран јавен ID.
Најважна е доследноста. Ако една услуга третира ID како цел број, а друга го складира како низа, грешките тивко се пренесуваат во валидацијата, серијализацијата и спојувањата. Одлучете каде се создаваат идентификаторите, која претстава ги преминува API-границите и дали повикувачите смеат да се потпираат на нивниот редослед.
Нормализацијата е алатка, а не ритуал
Нормализацијата ја штити точноста со тоа што на секој факт му дава едно авторитативно место. Е-поштата на клиентот не треба да се копира во секоја нарачка само затоа што е погодно да се прикаже. Ако се промени, дуплираните податоци стануваат проблем за поправка.
Но, дел од дуплирањето е намерно. Нарачката може да зачува снимка од адресата за испорака бидејќи историската точност е поважна од одразувањето на тековниот профил на клиентот. Табела за извештаи може да чува однапред пресметани вредности бидејќи нивното повторно пресметување при секое барање би било прескапо.
Корисното прашање не е „дали ова е нормализирано?“ Туку „која инваријанта ја зачувува оваа претстава и кој ја одржува?“ Денормализација без стратегија за ажурирање е одложена корупција. Денормализација со јасен извор на вистина, трансакциска патека за ажурирање и план за усогласување може да биде прагматична одлука за перформансите.
Направете ги запишувањата изречно атомски
Многу грешки во заднинските системи се грешки со делумно запишување. Апликацијата внесува нарачка, го ажурира инвентарот и создава запис за плаќање; третиот чекор откажува, оставајќи ги првите два потврдени. Успешната патека може да изгледа исправно со месеци, додека патеката за опоравување останува недефинирана.
Користете трансакции на база на податоци за промени што мора да успеат или да откажат заедно. Одржувајте ги кратки: валидирајте ги влезовите пред да ја отворите трансакцијата, избегнувајте мрежни повици во неа и потврдете штом ќе се воспостави инваријантата.
$pdo->beginTransaction();
try {
$orderId = createOrder($pdo, $customerId, $items);
reserveInventory($pdo, $items);
recordOrderEvent($pdo, $orderId, 'created');
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
Ова не ја прави секоја дистрибуирана операција атомска. На пример, испраќањето е-пошта или повикувањето давател на услуги за плаќање не може безбедно да се вклучи во локална трансакција на база на податоци. Наместо тоа, зачувајте ја промената на состојбата и настан за подоцнежна испорака, а потоа направете го потрошувачот идемпотентен. Повторувањето станува побезбедно кога обработката на истиот настан двапати не создава две наплати или две испораки.
Индексите имаат буџет
Индексот забрзува некои читања со додавање складиштен простор, притисок врз меморијата и работа при запишување. Секое внесување, ажурирање или бришење можеби ќе треба да го одржува секој релевантен индекс. Затоа одговорот на бавно барање не е „индексирајте ја секоја колона“. Тој е да го измерите барањето, да го прегледате неговиот план за извршување и да го додадете најмалиот индекс што поддржува значаен модел на пристап.
Внимавајте на вообичаените замки:
- Филтрирање според функција применета врз колона, што може да спречи ефикасно користење на индекс.
- Користење широки барања
SELECT *кога на крајната точка ѝ се потребни само неколку полиња. - Страницирање со отстапување за длабоки множества резултати, при што базата на податоци мора сè повеќе да прескокнува работа.
- Индекси што никогаш не се користат, но сепак ги забавуваат запишувањата.
Страницирањето засновано на курсор често е подобро за големи, подредени листи. Тоа ја прави следната страница продолжување од познат ред, наместо барање за отфрлање сè поголем префикс. Курсорот мора да користи стабилно подредување, најчесто временска ознака плус единствен разрешувач на изедначувања.
Заштитете ја базата на податоци од апликацијата
Базата на податоци е споделен капацитет. Една крајна точка со неограничено барање може да им наштети на неповрзани барања. Поставете ограничувања на границата на апликацијата: ограничете ја големината на страниците, валидирајте ги полињата за сортирање според листа на дозволени вредности, поставете соодветни временски ограничувања и избегнувајте прифаќање произволни изрази за филтрирање од клиенти.
Важен е и начинот на ракување со конекциите. Отворањето премногу истовремени конекции може да ја преоптовари базата на податоци пред процесорот или складирањето да станат видливо тесно грло. Конфигурирајте ги работниците на апликацијата и базените на конекции имајќи ги предвид ограничувањата на базата на податоци. Во контејнеризирани распоредувања, запомнете дека скалирањето на репликите на апликацијата ги множи потенцијалните конекции, освен ако базенирањето не е дизајнирано во исто време.
Исто така, разликувајте бавна база на податоци од бавно барање. Запишувајте корисна телеметрија за времетраењето на барањата, класата на грешка, повторувањата на трансакции и времето на чекање за конекција. По правило, избегнувајте евидентирање чувствителни параметри. Кога ќе се појави регресија, планот за извршување и репрезентативното барање обично се повредни од шпекулации.
Миграциите се продукциски код
Миграцијата на шема не е само датотека што успешно се извршува на лаптоп. Во зафатен систем, додавањето индекс, менувањето тип или пополнувањето нова колона може да заклучи табели, да троши В/И или да трае доволно долго за да ја наруши нормалната работа. Планирајте ги миграциите како операции што може да се распоредат, имајќи ги предвид враќањето назад и набљудливоста.
Претпочитајте компатибилни, поетапни промени. Додајте колона што дозволува null пред да ја направите задолжителна. Распоредете код на апликацијата што може да ракува и со старите и со новите облици. Пополнувајте наназад во ограничени серии. Додавајте ограничувања само откако постојните податоци ги исполнуваат. Отстранувајте застарени колони само откако сите распоредени потрошувачи повеќе не зависат од нив.
Предвидливоста е вистинската карактеристика
Најсилните дизајни на бази на податоци ретко делуваат досетливо. Тие ги прават обичните операции здодевни: барањето има познат облик на барање, запишувањето има јасна граница на трансакција, индексот има причина да постои, а миграцијата има безбедна патека низ продукцијата.
Таа дисциплина се исплаќа кога оптоварувањето се зголемува или барањата се менуваат. Базата на податоци престанува да биде мистериозно тесно грло и станува сигурен дел од системот: дел чии трошоци, ограничувања и однесување тимот може да ги објасни пред следниот инцидент да мора да ги објасни наместо нив.