ИТ развој

Beyond the Queue: Why Database Design Dictates Your App's Speed

Надвор од редицата: Зошто дизајнот на базата на податоци ја одредува брзината на вашата апликација

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

Затоа дизајнот на базата на податоци диктира толку многу од воочената брзина на една апликација. Тој ја обликува цената на API-барање, пропусниот опсег на работата во заднина, сигурноста на повторните обиди и оперативната граница на целиот систем. Редиците се вредни алатки, но не можат неефикасниот пристап до податоци да го претворат во ефикасна работа.

Редицата е механизам за испорака, а не стратегија за перформанси

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

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

Првото прашање не треба да биде „Колку работници ни се потребни?“ Треба да биде „Колку чини една единица работа?“

Индексите ги кодираат прашањата што ги поставува вашата апликација

Индексот не е генеричко копче за брзина. Тој е структура на податоци изградена за одредени обрасци на пристап. Најкорисните индекси произлегуваат од набљудување на барањата што апликацијата навистина ги извршува: филтрирање, спојување, подредување и избирање на следната ставка за обработка.

На пример, работник што постојано презема задачи што чекаат може да се потпира на барање со ваков облик:

SELECT id
FROM outbound_messages
WHERE status = 'pending'
  AND available_at <= NOW()
ORDER BY available_at, id
LIMIT 100;

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

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

На композитните индекси им треба размислување според обликот на барањето

Редоследот на колоните е важен во композитен индекс. Индекс дизајниран за WHERE tenant_id = ? AND status = ? ORDER BY created_at треба да одразува како барањето ги стеснува и подредува резултатите. На поинакво барање што започнува со опсег на датуми може да му треба поинаков дизајн. Копирањето индекс од друга табела без споредување на обликот на барањето е вообичаен пат до лажна сигурност.

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

Моделирањето на податоците ја одредува конкуренцијата

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

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

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

Дизајнирајте задачи за повторни обиди пред неуспехот да го наметне прашањето

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

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

CREATE TABLE report_requests (
  id BIGINT PRIMARY KEY,
  account_id BIGINT NOT NULL,
  request_key VARCHAR(100) NOT NULL,
  status VARCHAR(30) NOT NULL,
  UNIQUE (account_id, request_key)
);

Уникатното ограничување е важно затоа што ја заштитува инваријантата таму каде што е најтешко да се заобиколи: во базата на податоци. Проверките на ниво на апликација сè уште се корисни за пријателски одговор, но два истовремени процеси може и двата да поминат проверка „дали ова постои?“ пред кој било од нив да запише. Ограничувањето во базата на податоци му дава на системот конечен, заеднички одговор.

API-дизајнот може или да ја заштити или да ја казни базата на податоци

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

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

Во PHP-апликации, објектно-релациското мапирање може да го направи преминувањето низ врски пријатно читливо. Исто така може да скрие повторени барања во циклуси. Нестрпливото вчитување е корисно кога се совпаѓа со обликот на одговорот, но не треба да стане рефлекс. Преземете ги податоците потребни за операцијата, проверете го генерираниот SQL кога перформансите се важни и одржувајте го видливо мапирањето помеѓу однесувањето на крајната точка и однесувањето на барањето.

Измерете ја работата пред да ја скалирате машинеријата

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

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

Градете за здодевен брз пат

Најсилните бекенд-системи ја прават вообичаената работа незабележлива. Барањето пронаоѓа мал индексиран сет редови. Задачата презема ограничена серија. Повторниот обид го препознава претходниот напредок. Трансакцијата ја штити само состојбата што навистина мора да се промени заедно. API-то ги прави скапите операции намерни наместо случајни.

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

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

Mihajlo

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