Надвор од базата на податоци: Архитектура за интегритет на податоците во голем обем
Повеќето неуспеси со податоците не започнуваат со расипана база на податоци. Тие започнуваат порано, кога апликацијата прифаќа двосмислено барање, повторува несакан ефект без контекст или дозволува две валидни операции да се натпреваруваат една со друга. Ограничувањето во базата на податоци може да фати некои од овие грешки, но интегритетот на податоците во голем обем е архитектонско својство: тој произлегува од начинот на кој API-јата, работниците, трансакциите, шемите и практиките за распоредување работат заедно.
Оваа разлика е важна бидејќи системот може да има добро дизајнирана шема, а сепак да создава дупликат нарачки, да губи ажурирања, да прикажува застарени салда или да објавува настани за трансакции што подоцна се поништуваат. Базата на податоци останува суштинска, но не може да биде единственото место каде што постои исправноста.
Започнете со дефинирање на инваријантите
Инваријанта е услов што мора да остане точен без оглед на времето на барањето, повторувањата или неуспесите. „Адресата на е-пошта е единствена“ е познат пример. Покорисните деловни инваријанти често се поконкретни: фактурата може да се плати еднаш, залихата никогаш не смее да падне под нула, а корисникот не смее да добие пристап пред да се евидентира успешно плаќање.
Запишете ги овие правила на едноставен јазик пред да одлучите каде да ги применувате. Потоа поставете го секое правило на најниското сигурно ниво што има доволно контекст за да го спроведе.
- Користете ограничувања во базата на податоци за факти што секогаш мора да бидат точни во зачуваните податоци.
- Користете апликациски услуги за работни текови што опфаќаат повеќе табели или надворешни системи.
- Користете API-договори за да ја направите намерата на клиентот јасна.
- Користете асинхрона обработка за работа што може да се изврши подоцна, притоа зачувувајќи траен запис за тоа што мора да се случи.
Овој слоевит пристап избегнува вообичаена замка: ставање на секое правило во PHP бидејќи изгледа практично. Валидацијата во апликацијата ја подобрува повратната информација за корисникот, но не е замена за единствен индекс, надворешен клуч, check ограничување или трансакција. Другите патеки во кодот на крајот ќе ја заобиколат: увози, административни алатки, позадински задачи или идна услуга.
Дизајнирајте API-ја за повторувања, а не за идеални барања
Мрежите откажуваат на начини што корисниците не можат да ги видат. Клиентот може да испрати барање, серверот може да го заврши, а одговорот може да се изгуби. Ако клиентот повтори барање „создај плаќање“, задниот дел мора да разликува повторување од втора инструкција.
За операции со важни несакани ефекти, прифатете клуч за идемпотентност. Зачувајте го клучот со исходот од барањето под ограничување на единственост, а потоа вратете го првичниот резултат кога повторно се користи истиот клуч. Клучот мора соодветно да биде ограничен по опсег, на пример на сметка и тип на операција, наместо да се третира како глобална низа.
DB::transaction(function () use ($accountId, $key, $payload) {
$existing = IdempotencyRequest::query()
->where('account_id', $accountId)
->where('key', $key)
->lockForUpdate()
->first();
if ($existing) {
return $existing->response;
}
$payment = Payment::create($payload);
IdempotencyRequest::create([
'account_id' => $accountId,
'key' => $key,
'response' => ['payment_id' => $payment->id],
]);
return ['payment_id' => $payment->id];
});
Точната имплементација зависи од рамката и базата на податоци, но важната идеја е атомарноста. Пребарување проследено со вметнување надвор од трансакција може да им овозможи на истовремени барања и двете да заклучат дека не постои запис. Базата на податоци сè уште мора да го одбие дупликатниот клуч, а апликацијата мора да се справи со тоа одбивање со вчитување на веќе евидентираниот резултат.
Заштитете се од изгубени ажурирања
Логиката читај-измени-запиши е измамливо опасна. Разгледајте намалување на залихите. Два работника можат да прочитаат количина од еден, и двата да одлучат дека е достапна, и двата да запишат нула. Базата на податоци гледа валидни вредности; бизнисот гледа препродаден производ.
Претпочитајте условно ажурирање кога правилото може директно да се изрази во SQL:
UPDATE inventory
SET quantity = quantity - 1
WHERE sku = :sku
AND quantity > 0;
Ако бројот на засегнати редови е нула, немало достапна залиха. Ова обично е поедноставно и побезбедно отколку прво да се прочита редот во PHP. За посложени работни текови, користете трансакција со соодветна стратегија за заклучување, одржувајте ја трансакцијата кратка и избегнувајте мрежни повици додека се држат заклучувања.
Оптимистичкото заклучување е друга корисна опција кога конкуренцијата е мала. Додадете колона за верзија, ажурирајте само кога верзијата се совпаѓа и нека повикувачот повтори или го разреши конфликтот кога не се совпаѓа. Тоа претвора тивко презапишување во експлицитна одлука.
Направете ја асинхроната работа трајна
Испраќањето е-пошта, наплатувањето преку давател на услуги, индексирањето документи за пребарување и известувањето други услуги ретко треба да се случуваат во трансакцијата што ги менува примарните податоци. Надворешните повици се бавни, можат непредвидливо да откажат и не можат да бидат поништени од вашата база на податоци.
Шемата на трансакциско сандаче за појдовни пораки ѝ дава трајна форма на оваа граница. Во истата трансакција што создава или менува деловен запис, вметнете ред во сандачето за појдовни пораки што го опишува настанот. Работникот подоцна го доставува настанот и ја евидентира неговата состојба на достава. Ако процесот падне по потврдувањето, записот во сандачето за појдовни пораки останува достапен за повторување.
И потрошувачите мора да бидат идемпотентни. Системите за испорака можат да создадат дупликати, особено по истекување на време или рестартирање на работник. Потрошувачот треба да евидентира стабилен идентификатор на настанот или на друг начин да направи повторената обработка да биде безопасна. Доставата „најмалку еднаш“ станува управлива кога секој обработувач е дизајниран за неа.
Користете ја базата на податоци како партнер, а не како складиште за сè
Ограничувањата во базата на податоци се извршна документација. Надворешниот клуч кажува дека запис без родител е невалиден. Единствениот индекс кажува дека дупликатите не се само непрактични; тие се забранети. Колона што не дозволува null кажува дека отсуството не е валидна состојба. Овие гаранции го прават кодот надолу по текот поедноставен бидејќи помалку невозможни состојби можат да влезат во системот.
Сепак, избегнувајте складиштените процедури, тригерите и непрозрачното однесување на базата на податоци да ги претворите во единствен дом на деловните работни текови. Тие може да бидат соодветни за строго локални правила за интегритет, но логиката што координира API-ја, редици, авторизација и видливи неуспеси обично е полесна за тестирање и одржување во апликацискиот слој. Целта не е да се минимизира логиката во базата на податоци; туку секое правило да биде видливо, применливо и во сопственост на вистинската граница.
Изградете набљудливост околу неуспесите на интегритетот
Инцидентите со интегритетот често се откриваат индиректно: поддршката пријавува дупликатна наплата, задача за усогласување наоѓа несовпаѓање или заостатокот во редицата открива заглавен работен тек. Третирајте ги како оперативни сигнали од прв ред.
- Евидентирајте стабилни идентификатори на барања, ентитети и настани.
- Мерете прекршувања на ограничувања, стапки на повторувања, задачи испратени во dead-letter редица и старост на сандачето за појдовни пораки.
- Чувајте ревизорски полиња што објаснуваат кој или што променил запис и кога.
- Извршувајте проверки за усогласување на критични изведени податоци, како што се салда или состојби на надворешни плаќања.
Ревизијата не е изговор да се толерира корупција. Нејзината вредност е дијагностичка: кога инваријантата е доведена во прашање, инженерите имаат потреба од доволно докази за да ја разберат низата и безбедно да ја поправат.
Интегритетот е навика во дизајнот на системи
Во голем обем, исправноста не се постигнува со една совршена трансакција или една внимателно именувана табела. Таа произлегува од претпоставката дека барањата ќе се дуплираат, работниците ќе се рестартираат, распоредувањата ќе се преклопуваат и истовремените корисници ќе поставуваат валидни барања во исто време.
Прво дизајнирајте ја инваријантата. Применете ја на најсилната практична граница. Направете ги несаканите ефекти повторливо извршливи, направете ги потрошувачите идемпотентни и направете ги неуспесите видливи. Тогаш базата на податоци станува тоа што треба да биде: моќна основа во систем намерно изграден да ги исполнува своите ветувања.