Databáze plná překvapení: Příběhy z migrací

Databáze je srdce každé aplikace. A jako u skutečného srdce — když začne vynechávat, je to problém. Za roky praxe jsme migrovali, opravovali a zachraňovali databáze, které by neměly existovat. A přesto existovaly, v produkci, s reálnými daty reálných lidí.
Toto jsou příběhy, po kterých jsme potřebovali kávu. Někdy i něco silnějšího.
Tabulka s pěti sty sloupci
Normalizace je jeden ze základních principů databázového designu. Existují učebnice, kurzy, certifikace. A přesto jsme narazili na tabulku, která měla přesně 487 sloupců. Jednu tabulku. Pro všechno.
Jmenovala se data. Ne users, ne orders, ne products. Prostě data. A obsahovala sloupce jako: name, name2, name3, address_home, address_work, address_other, address_other2, phone1 až phone5, note1 až note20, a náš favorit: temp_column_do_not_use_7.
Dočasný sloupec, který se neměl používat. Sedmý v řadě. Což znamená, že bylo minimálně šest předchozích dočasných sloupců, které se také neměly používat.
Celá aplikace stavěla na jedné obrovské tabulce místo relačního modelu. Přidání nového typu informace znamenalo přidání dalšího sloupce. Hledání dat bylo jako hledání jehly v kupce sena — pokud by ta kupka měla 487 jehel a vy nevěděli, která je ta správná.
Datumy v dvanácti formátech
Datum je datum, říkáte si. Jednoduché. A přesto jsme v jedné databázi našli sloupec datum_objednavky (typ VARCHAR, samozřejmě) s těmito hodnotami:
2023-01-1515.1.202315/01/2023Jan 15, 202315. ledna 20231/15/2320230115pondělí 15. lednaminulý týdenbrzy?- prázdný řetězec
Dvanáct různých formátů (pokud počítáme "brzy" a "?" jako formáty, což s jistou kreativitou můžeme). Důvod? Pole v aplikaci bylo prostý textový input bez jakékoliv validace. Uživatelé prostě psali datum, jak je napadlo. A systém to s radostí uložil.
Migrace do správného datumového typu trvala dva týdny. Dva týdny parsování, hádání a občasného volání klientovi s otázkou: "Víte, co znamená objednávka s datem 'brzy' z roku 2021?"
Pole pro adresu jako univerzální zápisník
V jedné starší aplikaci existovalo pole adresa, které mělo sloužit — překvapivě — k ukládání adres. V praxi obsahovalo:
- Skutečné adresy (asi 60 % záznamů)
- Telefonní čísla ("602123456 — volat odpoledne")
- E-mailové adresy
- Poznámky k objednávkám ("Nechte u sousedů, zazvoňte 3x")
- Nákupní seznamy (doslova: "máslo, chleba, salám")
- Existenciální úvahy jednoho operátora ("Proč tady vlastně pracuju?")
Když jsme se klienta zeptali, jak se to stalo, odpověděl: "Operátoři si stěžovali, že nemají kam psát poznámky. Tak jsme jim řekli, ať používají pole adresa, že to pak vyřešíme." To bylo v roce 2018. Nikdo to nevyřešil.
Sloupec isDeleted a kvantová fyzika
Soft delete — označení záznamu jako smazaného místo skutečného smazání — je rozumný přístup. Ale vyžaduje konzistenci. V jednom projektu jsme našli sloupec isDeleted, který obsahoval tyto hodnoty:
0(zřejmě ne)1(zřejmě ano)true(textový řetězec)false(textový řetězec)yesnoYNmaybe(opravdu)NULLdeletednot_deleted- prázdný řetězec
Sloupec isDeleted se tak stal kvantovým objektem — záznam byl současně smazaný i nesmazaný, dokud ho někdo nepozoroval. A ani pak to nebylo jisté.
Zjistili jsme, že na projektu pracovalo postupně sedm různých vývojářů. Každý měl svou představu o tom, co do sloupce patří. A nikdo se neobtěžoval podívat, co tam dávali ostatní.
Padesát gigabajtů obrázků v databázi
Existuje věčná debata o tom, jestli ukládat obrázky do databáze, nebo na souborový systém. Většina rozumných vývojářů ukládá obrázky na disk (nebo do cloudového úložiště) a do databáze dává jen cestu. Ale ne všichni vývojáři jsou rozumní.
Převzali jsme e-shop, jehož databáze měla 53 GB. Pro e-shop s pár tisíci produkty to bylo podezřelé. Po průzkumu jsme zjistili, že každý produkt měl obrázky uložené jako BLOB přímo v databázi. Ale to nebylo to nejhorší.
Každý obrázek byl uložen v originálním rozlišení z fotoaparátu — 24 megapixelů, asi 8 MB na fotku. Nikdo je nezmenšoval. A ke každému produktu bylo 5-10 fotek. A celá historie — když prodejce nahrál novou fotku, stará se nesmazala, jen se přidal nový záznam.
Záloha databáze trvala čtyři hodiny. Obnovení šest. A MySQL server občas padal, protože padesát tři gigabajtů dat není úplně to, na co je optimalizovaný.
Produkce bez záloh
A teď ten skutečně děsivý příběh. Zavolal nám klient v panice. Jejich webová aplikace nefunguje. Server spadl. Zeptali jsme se na zálohy. Odpověď:
"Zálohy? To dělá hosting automaticky, ne?"
Ne. Nedělá. Respektive — v tomto případě ne. Hosting měl zálohy v ceníku jako placenou službu. Klient si ji neobjednal. A nikdo jiný zálohy neřešil.
Disk serveru měl hardwarovou chybu. Část dat byla poškozená. Databáze se nedala obnovit. Pět let zákaznických dat, objednávek, faktur — pryč.
Tuhle situaci jsme bohužel nezvládli vyřešit mávnutím kouzelného proutku. Podařilo se zachránit asi 70 % dat z poškozeného disku specializovanou firmou. Za cenu, která mnohonásobně převyšovala roční náklady na zálohovací službu.
Proč se to děje
Špatný databázový design obvykle nevzniká najednou. Je to postupný proces. Někdo vytvoří "dočasné" řešení, které se stane permanentním. Někdo přidá sloupec místo nové tabulky, protože je to rychlejší. Někdo vypne validaci, protože "teď nemáme čas to řešit."
A každá tahle zkratka je v pořádku, pokud se k ní vrátíte a opravíte ji. Problém je, že se k ní nikdo nevrátí. A za dva roky máte databázi, kde sloupec "adresa" obsahuje nákupní seznamy.
- ✗Žádné indexy na tabulkách s miliony řádků
- ✗Jedna obří tabulka pro všechna data
- ✗Žádné zálohy nebo netestované zálohy
- ✗Žádná validace — databáze jako hloupé úložiště
- ✗Ruční úpravy schématu v produkci
- ✗Obrázky a soubory uložené jako BLOB v DB
- ✓Indexování klíčových sloupců
- ✓Normalizace datového modelu
- ✓Automatické zálohy s testováním obnovy
- ✓Validace na úrovni databáze (NOT NULL, CHECK)
- ✓Migrace verzované v kódu
- ✓Binární data na souborovém systému
Jak tomu předejít
Pár základních pravidel, která ušetří hodiny bolesti:
Navrhněte datový model předem. Před prvním řádkem kódu. Nakreslete schéma, definujte vztahy, zvolte správné datové typy.
Používejte migrace. Verzované, opakovatelné, reverzibilní. Žádné ruční úpravy databáze v produkci.
Validujte na úrovni databáze. NOT NULL, UNIQUE, CHECK constrainty, cizí klíče. Nedělejte z databáze hloupé úložiště.
Zálohujte. Automaticky, pravidelně, a ověřujte, že se zálohy dají obnovit. Záloha, kterou jste nikdy netestovali, není záloha.
Neukládejte binární data do databáze. Obrázky, PDF, videa patří na souborový systém nebo do objektového úložiště.
Zálohujte databázi minimálně jednou denně a zálohy pravidelně testujte obnovou na testovacím prostředí. Při migraci vždy nejdřív zálohujte, pak migrujte na staging a teprve po ověření na produkci. Jedna netestovaná záloha je horší než žádná — dává vám falešný pocit bezpečí.
Poučení na závěr
Databáze je základ, na kterém stojí celá aplikace. Když jsou základy špatné, dříve nebo později se to projeví — a oprava stojí řádově víc, než by stálo udělat to správně od začátku.
Morál příběhu: Datová architektura není něco, co se dá odložit na "potom." Investujte do ní od prvního dne. Váš budoucí já — nebo vývojář, který po vás projekt zdědí — vám poděkuje. A nebudete muset nikomu vysvětlovat, proč sloupec isDeleted obsahuje hodnotu "maybe."


