Každý nový projekt u nás začína na Postgrese s prakticky rovnakým úvodným setupom. Za roky sa ustálil na jednej veci, ktorú zapneme vždy ako prvú — a na troch veciach, ktoré sme kedysi robili nábožne a dnes ich zámerne nerobíme. Toto je krátky zápis o oboch stranách tej rovnice.
Píšeme to z praxe vývoja webových aplikácií, kde je Postgres skoro vždy default databáza. Nie je to návod „ako na to”. Je to zoznam rozhodnutí, ktoré sme zmenili potom, čo nás staré rozhodnutia raz alebo dvakrát stáli večer.
Zmena, ktorú robíme vždy: pg_stat_statements od prvého dňa
Skôr než napíšeme prvý produkčný dotaz, zapneme rozšírenie `pg_stat_statements`. Je súčasťou Postgresu, stojí zlomok percenta výkonu a od prvého dňa ti presne povie, ktorý dotaz spotreboval najviac času — agregovane, nie z jedného náhodného EXPLAIN-u. Raz týždenne sa naň pozrieme: top 10 dotazov podľa celkového času, nie podľa najpomalšieho jedného behu.
Znie to nudne. Je to ale jediný dôvod, prečo skoro nikdy nehádame, kde je výkon. Pred tromi rokmi sme to zapínali až vtedy, keď už niečo horelo. Rozdiel medzi „zapni a pozri sa” a „forenzná analýza pod tlakom o tretej ráno” je presne ten istý ako medzi monitoringom, ktorý beží, a monitoringom, ktorý si dopisuješ počas výpadku.
Vec č. 1, ktorú sme prestali: indexovať každý cudzí kľúč reflexom
Kedysi sme na každý foreign key automaticky pridali index. Znelo to ako poctivosť. V skutočnosti to na zápisovo ťažkých tabuľkách pridávalo réžiu pri každom INSERT-e a UPDATE-e a polovica tých indexov sa nikdy nepoužila pri čítaní. Dnes index pridáme až vtedy, keď nám `pg_stat_statements` ukáže pomalý JOIN alebo filter, ktorý ho naozaj potrebuje. Na jednom projekte sme takto zmazali deväť z devätnástich indexov a zápisy zrýchlili zhruba o 15 %, bez jediného pomalšieho čítania.
Vec č. 2, ktorú sme prestali: dávať random UUID ako primárny kľúč defaultne
Roky sme automaticky siahali po `uuid` v4 ako primárnom kľúči — vyzeralo to moderne a bezpečne proti hádaniu ID. Lenže náhodné UUID rozhadzujú zápisy po celom indexe, takže B-tree sa fragmentuje a INSERT-y na veľkej tabuľke sa spomalia. Na jednej tabuľke s desiatkami miliónov riadkov nám prechod z `uuid` v4 na `bigint` generated identity zrýchlil hromadné importy o vyše 30 % a zmenšil index. Keď naozaj potrebujeme neuhádnuteľné ID v URL, použijeme UUID v7 (časovo usporiadané) alebo samostatný verejný identifikátor — nie náhodné v4 ako primárny kľúč.
Vec č. 3, ktorú sme prestali: dávať všetko do JSONB „pre flexibilitu”
JSONB je výborný — na veci, ktoré sú naozaj nepravidelné. Naša chyba bola, že sme doň hádzali aj polia, ktoré mali jasnú štruktúru, „aby sme to nemuseli meniť neskôr”. O rok neskôr sme nad tými poľami filtrovali a radili, a každý taký dotaz potreboval buď GIN index s čudnou syntaxou, alebo sekvenčné prehľadávanie celej tabuľky. Pravidlo, ktoré z toho ostalo: ak pole budeme filtrovať, radiť alebo joinovať, je to stĺpec. JSONB je na zvyšok — na to, čo iba ukladáme a čítame ako celok.
Veľa „štandardných” databázových rozhodnutí je len sediment — robíme ich, lebo sme ich robili minule. To isté sme si priznali pri Redise. Rozdiel je, že pri Postgrese nás to učí jeden súbor: pg_stat_statements.
Žiadne z týchto rozhodnutí nie je dogma. Sú projekty, kde má random UUID zmysel, a tabuľky, kde je JSONB presne správna voľba. Ale default sa za roky obrátil: namiesto „pridaj pre istotu” je dnes „pridaj, keď ti dáta povedia, že to treba”. Ak staviate appku na Postgrese a chcete druhý pár očí na schému skôr, než narastie na milióny riadkov, ozvite sa — pol dňa nad schémou teraz ušetrí týždeň migrácií neskôr.