ИТ развој

Beyond the Prompt: Architecting Databases AI Can't Deceive

Надвор од поттикот: Архитектирање бази на податоци што ВИ не може да ги измами

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

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

Целта не е ВИ да пишува помалку SQL-изјави. Целта е да се дизајнира систем во кој ниту кодот генериран од ВИ ниту набрзина напишаниот човечки код не можат тивко да ги прекршат правилата што се важни.

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

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

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

Разгледајте ставка од нарачка. Таа не треба да постои без нарачка. Нејзината количина треба да биде позитивна. Истиот производ не треба случајно да се појави двапати кога деловниот модел вели дека еден ред ја претставува вкупната количина за тој производ.

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)
);

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

Моделирајте ги инваријантите пред табелите

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

За секој важен работен тек, поставете прашања како:

  • Може ли овој запис да постои без својот родител?
  • Кои вредности се задолжителни, конечни, единствени или непроменливи?
  • Може ли некоја врска да се дуплира?
  • Кои премини на состојби се валидни?
  • Што мора да остане вистинито при истовремени барања?
  • Кои податоци може да се избришат, а кои мора да се задржат?

Некои инваријанти природно се вклопуваат во NOT NULL, UNIQUE, надворешни клучеви и ограничувања за проверка. Други бараат трансакции, внимателно дизајнирани изјави за ажурирање или мала количина логика на база на податоци од серверска страна. Важната разлика е тие да се идентификуваат експлицитно, наместо да се надевате дека генерираниот ORM-модел ќе ги опфати случајно.

Премините на состојби заслужуваат особено внимание. Генеричка колона status лесно се додава и лесно се злоупотребува. Ако фактурата може да премине од draft во issued, а потоа во paid, одлучете дали може да се уредува по издавањето, дали може да се откаже по плаќањето и на кој актер му е дозволено да ја направи секоја промена. Тие одлуки припаѓаат во дизајнот, а не во потсетник што бара од ВИ да „ракува со статусите на фактурите“.

Користете ја базата на податоци како граница за конкурентност

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

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

Побезбеден пристап го прави условот дел од запишувањето:

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

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

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

Дизајнирајте API-ја за повторни обиди, а не за идеални мрежи

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

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

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)
);

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

Направете ги промените на шемата преживливи

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

Претпочитајте адитивни, фазни промени. Прво додадете nullable колона. Распоредете код што ги запишува и старото и новото претставување. Пополнете наназад во контролирани серии. Додадете ограничувања дури откако постојните редови ги исполнуваат. Префрлете ги читачите. Отстранете ги застарените полиња во подоцнежно издание.

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

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

Дајте ѝ на ВИ тесни задачи и проверливи влезови

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

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

Добрата контролна листа за преглед е едноставна: Што спречува невалидни редови? Што спречува дупликатни ефекти? Што се случува ако оваа операција се изврши двапати? Што се случува ако не успее на половина пат? Што се случува кога две барања се извршуваат одеднаш? Што се случува за време на постепено распоредување?

Трајната предност е отпорноста на грешки

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

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

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

Mihajlo

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