ИТ развој

Designing Databases for Resilience: Architecting Predictability Under Strain

Проектирање бази на податоци за отпорност: Архитектирање предвидливост под оптоварување

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

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

Започнете со ветувањата што вашите податоци мора да ги исполнат

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

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

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

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

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

Премногу ригидните шеми ја прават промената скапа. Премногу лабавите шеми ја прават исправноста скапа. Отпорноста доаѓа од намерен избор на структура.

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

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

Направете ги запишувањата безбедни за повторување

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

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

CREATE TABLE payment_requests (
  id BIGINT PRIMARY KEY,
  idempotency_key VARCHAR(255) NOT NULL,
  customer_id BIGINT NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  status VARCHAR(32) NOT NULL,
  UNIQUE (idempotency_key)
);

Ограничувањето за уникатност е суштинско. Пристапот „прво провери, па вметни“ сам по себе е ранлив на истовремени барања. Во PHP, фатете ја грешката на базата на податоци за дупликат-клуч, вчитајте го постојното барање и вратете го неговиот познат исход. Не обидувајте се повторно широко за секој исклучок; повторните обиди се соодветни за минливи неуспеси, не за невалиден внес или прекршени деловни правила.

Одржувајте ги трансакциите кратки и целисходни

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

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

Користете outbox за сигурни несакани ефекти

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

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

Дизајнирајте барања за непријатниот ден

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

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

SELECT id, created_at, status
FROM orders
WHERE customer_id = :customer_id
  AND id < :last_seen_id
ORDER BY id DESC
LIMIT 50;

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

Планирајте за конкуренција, доцнење и деградирани зависности

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

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

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

Вежбајте опоравување пред да ви затреба

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

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

Предвидливоста е вистинската карактеристика на перформансите

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

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

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

Mihajlo

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