Razvoj

Beyond the Prompt: Architecting Databases AI Can't Deceive

Iza upita: projektiranje baza podataka koje AI ne može obmanuti

AI može izraditi uvjerljivu shemu baze podataka u nekoliko sekundi. Može dobro imenovati tablice, samouvjereno predložiti indekse i objasniti kompromise jezikom koji zvuči kao da potječe iz pažljivog arhitektonskog pregleda. To je korisno. Također je opasno.

Problem nije u tome što je AI model jedinstveno nepouzdan. Problem je u tome što baze podataka pretpostavke kažnjavaju strože nego većina dijelova aplikacije. Shema koja izgleda uvjerljivo može godinama prihvaćati nevaljane podatke. Nedostajuće ograničenje može prolaznu pogrešku API-ja pretvoriti u trajno narušavanje podataka. Praktična denormalizacija može postati posao usklađivanja za koji nitko nije zadužen.

Cilj nije navesti AI da piše manje SQL naredbi. Cilj je osmisliti sustav u kojem ni kod koji je generirao AI ni užurbano napisan ljudski kod ne mogu neprimjetno prekršiti pravila koja su važna.

Premjestite poslovnu istinu ispod aplikacijskog sloja

Validacija u aplikaciji vrijedna je, ali nije pouzdana konačna granica. Zahtjevi mogu stizati putem web-kontrolera, potrošača reda čekanja, skripte za održavanje, uvoza, administratorskog alata ili buduće usluge koja zaobilazi današnji put validacije.

Ako neko pravilo uvijek mora biti istinito, kodirajte ga ondje gdje se svaki zapisivač mora s njime suočiti: u bazi podataka.

Razmotrite stavku narudžbe. Ne bi trebala postojati bez narudžbe. Njezina količina trebala bi biti pozitivna. Isti se proizvod ne bi smio slučajno pojaviti dvaput kada poslovni model kaže da jedan red predstavlja ukupnu količinu tog proizvoda.

CREATE TABLE order_lines (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    unit_price_cents INTEGER NOT NULL CHECK (unit_price_cents >= 0),
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_order_lines_order
        FOREIGN KEY (order_id) REFERENCES orders(id),

    CONSTRAINT fk_order_lines_product
        FOREIGN KEY (product_id) REFERENCES products(id),

    CONSTRAINT uq_order_lines_order_product
        UNIQUE (order_id, product_id)
);

Ta ograničenja nisu dokumentacija. Ona su izvršiva politika. AI može zaboraviti ograničenje provjere, pogrešno razumjeti rubni slučaj ili ponuditi elegantan prečac. Baza podataka i dalje može odbiti nemoguće stanje.

Modelirajte invarijante prije tablica

Timovi često počinju s entitetima: korisnicima, računima, pretplatama, porukama. Snažnije polazište su invarijante: činjenice koje moraju ostati istinite bez obzira na to koja krajnja točka, zadatak ili programer mijenja podatke.

Za svaki važan tijek rada postavite pitanja kao što su:

  • Može li ovaj zapis postojati bez svog nadređenog zapisa?
  • Koje su vrijednosti obavezne, konačne, jedinstvene ili nepromjenjive?
  • Može li se odnos duplicirati?
  • Koji su prijelazi stanja valjani?
  • Što mora ostati istinito pri istodobnim zahtjevima?
  • Koji se podaci smiju izbrisati, a koji se moraju zadržati?

Neke se invarijante prirodno uklapaju u NOT NULL, UNIQUE, strane ključeve i ograničenja provjere. Druge zahtijevaju transakcije, pažljivo osmišljene naredbe ažuriranja ili malu količinu logike baze podataka na poslužitelju. Važno je izričito ih utvrditi umjesto da se nadate kako ih generirani ORM model slučajno obuhvaća.

Prijelazi stanja zaslužuju posebnu pozornost. Generički stupac status lako je dodati i lako pogrešno upotrijebiti. Ako račun može prijeći iz draft u issued, a zatim u paid, odlučite može li se uređivati nakon izdavanja, može li se otkazati nakon plaćanja i kojem je akteru dopušteno izvršiti svaku promjenu. Te odluke pripadaju dizajnu, a ne upitu kojim se od AI-ja traži da „obradi statuse računa”.

Upotrijebite bazu podataka kao granicu istodobnosti

Mnoge pogreške u podacima nisu pogreške validacije. To su uvjeti utrke prerušeni u poslovni jezik.

Zamislite krajnju točku za rezervaciju zaliha. Dva zahtjeva oba pročitaju dostupnu količinu jedan, oba zaključe da je mogu rezervirati i oba upišu rezervaciju. Svaki pojedinačni upit može izgledati razumno. Slijed nije.

Sigurniji pristup uvjet čini dijelom upisa:

UPDATE inventory
SET available_quantity = available_quantity - :requested
WHERE product_id = :product_id
  AND available_quantity >= :requested;

Aplikacija zatim provjerava broj zahvaćenih redaka. Jedan zahvaćeni redak znači da je rezervacija uspjela; nula znači da zaliha nije bila dostupna. Time se utrka čitanja pa pisanja pretvara u jednu atomsku odluku. Ako rezervacija stvara i povezane zapise, smjestite rad u transakciju i definirajte što se događa kada bilo koji korak ne uspije.

AI može predložiti transakcije, ali ne može zaključiti vaše zahtjeve za dosljednošću samo iz naziva tablica. Tehnički voditelj trebao bi moći pokazati na svaki tijek rada i navesti što je atomsko, što može biti eventualno dosljedno i što mora biti idempotentno.

Dizajnirajte API-je za ponovne pokušaje, a ne za idealne mreže

Mreže mogu zakazati nakon što je poslužitelj potvrdio transakciju, ali prije nego što klijent primi odgovor. Potrošači reda čekanja mogu primiti poruku više puta. Vraćanja implementacije mogu ponoviti rad. To su uobičajeni radni uvjeti, a ne egzotični scenariji kvara.

Za izvana pokrenute operacije upisa ključ idempotentnosti može biti vredniji od pametnog upita. Pohranite ključ koji je dostavio pozivatelj, s dovoljno opsega za identifikaciju operacije, provedite jedinstvenost i vratite izvorni rezultat kada se ista operacija ponovi.

CREATE TABLE payment_requests (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    account_id BIGINT NOT NULL,
    idempotency_key VARCHAR(255) NOT NULL,
    request_hash VARCHAR(64) NOT NULL,
    payment_id BIGINT NULL,

    CONSTRAINT uq_payment_request
        UNIQUE (account_id, idempotency_key)
);

Točna implementacija ovisi o bazi podataka i ugovoru API-ja, ali načelo je stabilno: ponovni pokušaji ne bi smjeli stvarati duplicirane nuspojave. Također usporedite pohranjeni otisak zahtjeva kada je to primjereno. Ponovna upotreba ključa idempotentnosti za bitno drukčiji zahtjev trebala bi proizvesti jasan sukob, a ne neprimjetno vratiti nepovezani prethodni rezultat.

Učinite promjene sheme preživljivima

Generirane migracije često uspiju na praznoj lokalnoj bazi podataka, a ne uspiju u produkciji jer su stvarne baze podataka velike, stare i aktivno primaju upise. Svaku migraciju tretirajte kao artefakt implementacije s operativnim planom.

Prednost dajte aditivnim, postupnim promjenama. Najprije dodajte stupac koji dopušta NULL. Implementirajte kod koji upisuje i staru i novu reprezentaciju. Popunite postojeće podatke u kontroliranim serijama. Dodajte ograničenja tek nakon što ih postojeći reci zadovoljavaju. Prebacite čitače. Uklonite zastarjela polja u kasnijem izdanju.

Ovaj je pristup manje dramatičan od jednokratne migracije, ali pruža mogućnosti vraćanja i ograničava trajanje zaključavanja. Također nameće korisna pitanja: Mogu li stare i nove verzije aplikacije raditi istodobno? Blokira li izgradnja indeksa upise u ovom sustavu baze podataka? Kako će se neuspjelo popunjavanje postojećih podataka sigurno nastaviti?

Docker pomaže učiniti lokalni razvoj ponovljivim, ali sam po sebi ne čini migracije sigurnima. Baza podataka u spremniku izvrsna je za testiranje postavljanja sheme i slijedova migracija. Nije zamjena za testiranje s realističnim opsegom podataka, ponašanjem transakcija i redoslijedom implementacije.

Dajte AI-ju uske zadatke i provjerljive ulaze

AI je najkorisniji kada arhitektura već pruža zaštitne ograde. Zatražite od njega da izradi nacrt migracije nakon što pružite invarijante, ciljnu verziju baze podataka, definicije tablica, očekivani opseg podataka i zahtjeve za vraćanje. Zatražite od njega da pregleda plan indeksa, a zatim provjerite njegove prijedloge planovima upita i reprezentativnim radnim opterećenjima.

Ne tražite od njega da prešutno odluči je li potreban strani ključ, ograničenje jedinstvenosti ili transakcija. To su odluke dizajna s poslovnim posljedicama.

Dobar kontrolni popis za pregled jednostavan je: Što sprječava nevaljane retke? Što sprječava duplicirane učinke? Što se događa ako se ova operacija pokrene dvaput? Što se događa ako ne uspije na pola puta? Što se događa kada se dva zahtjeva pokrenu odjednom? Što se događa tijekom postupne implementacije?

Trajna prednost je otpornost na pogreške

Najbolja arhitektura baze podataka ne pretpostavlja da će svaki programer zapamtiti svako pravilo, da će svaki API biti ispravno pozvan ili da će svaki generirani isječak razumjeti domenu. Ona očekuje pogreške, ponovne pokušaje, djelomične kvarove, istodoban promet i buduće promjene.

To je ono što sustav čini teškim za obmanuti. Ne skepticizam prema AI-ju, nego dizajn u kojem se samopouzdanje nikada ne prihvaća kao dokaz. Smjestite istinu u ograničenja, učinite upise atomskima, dizajnirajte za ponovne pokušaje i razvijajte sheme postupno. Tada AI postaje brži pomoćnik unutar sustava koji i dalje zna reći ne.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.