NastavljajuÄi temu unosa velikih koliÄina podataka, pokrenutu , u ovom Äemo razmotriti naÄine na koje se može smanjiti "fiziÄka" veliÄina pohranjenih u PostgreSQL-u i njihov utjecaj na performanse poslužitelja.
Govorit Äemo o TOAST postavkama i poravnanju podataka. "U prosjeku" ovi naÄini Äe omoguÄiti uÅ”tedu ne previÅ”e resursa, ali ā bez modifikacije koda aplikacije.

MeÄutim, naÅ”e iskustvo pokazalo se vrlo produktivnim u tom smislu, jer skladiÅ”te gotovo svakog monitora po svojoj prirodi jest veÄinom append-only s obzirom na zapisane podatke. A ako vas zanima kako uÄiti bazu da piÅ”e na disk umjesto 200MB/s dvostruko manje ā molim, pod cut.
Mali tajni trikovi velikih podataka
Po profilu rada , redovito primamo iz logova tekstualne pakete.
A buduÄi da , Äije baze podataka pratimo, predstavlja viÅ”ekomponentni proizvod s kompleksnim strukturama podataka, zahtijevaju se za postizanje maksimalne uÄinkovitosti priliÄno takvi . Tako da i obujam svake pojedinaÄne instance upita ili rezultantnog plana izvrÅ”enja u dolaznom logu ispadne "u prosjeku" dovoljno velik.
Pogledajmo strukturu jedne od tablica, u koju upisujemo "sirove" podatke ā odnosno baÅ” originalni tekst iz zapisa loga:
CREATE TABLE rawdata_orig(
pack -- PK
uuid NOT NULL
, recno -- PK
smallint NOT NULL
, dt -- kljuÄ sekcije
date
, data -- najvažnije
text
, PRIMARY KEY(pack, recno)
);TipiÄna takva tablica (veÄ sekcionirana, naravno, dakle ovo je ā predložak sekcije), gdje je najvažnije ā tekst. Ponekad dovoljno obiman.
Podsjetimo se da "fiziÄka" veliÄina jednog zapisa u PG ne može zauzeti viÅ”e od jedne stranice podataka, no "logiÄka" veliÄina ā sasvim je druga stvar. Da bi se zabilježio obiman vrijednost u polju (varchar/text/bytea) koristi se :
PostgreSQL koristi fiksnu veliÄinu stranice (obiÄno 8 KB) i ne dozvoljava tupplama zauzimanje viÅ”e stranica. Stoga nije moguÄe izravno pohraniti vrlo velike vrijednosti polja. Kako bismo premostili ovu prepreku, velike vrijednosti polja se komprimiraju i/ili podijele na nekoliko fiziÄkih redaka. Ovo se dogaÄa neprimjetno za korisnika i samo neznatno utjeÄe na veÄi dio server koda. Ova metoda poznata je kao TOAST ā¦
Zapravo, za svaku tablicu s "potencijalno velikim" poljima automatski svake "velike" zapisa u segmente od 2KB:
TOAST(
chunk_id
integer
, chunk_seq
integer
, chunk_data
bytea
, PRIMARY KEY(chunk_id, chunk_seq)
); To jest, ako moramo zabilježiti redak s "velikom" vrijednoÅ”Äu data, tada Äe se stvarni zapis dogoditi ne samo u glavnoj tablici i njenom PK, veÄ i u TOAST-u i njenom PK.
Smanjenje TOAST utjecaja
Ali veÄina zapisa kod nas zapravo nije toliko velika, u 8KB bi se trebala uklapati ā kako na tome uÅ”tedjeti?..
Ovdje nam pomaže atribut u stupcu tablice:
- EXTENDED dozvoljava i kompresiju i odvojeno pohranjivanje. Ovo je standardna opcija za veÄinu vrsta podataka kompatibilnih s TOAST-om. Prvo se pokuÅ”ava izvrÅ”iti kompresija, zatim ā pohrana izvan tablice, ako redak i dalje ostaje prevelik.
- MAIN dozvoljava kompresiju, ali ne i odvojeno pohranjuvanje. (Zapravo, odvojeno pohranjivanje, ipak, bit Äe izvrÅ”eno za takve stupce, ali samo kao krajnja mjera, kada nema drugog naÄina da se smanji redak tako da stane na stranicu.)
Zapravo, to je upravo ono Å”to nam treba za tekst ā maksimalno komprimirati, a ako se baÅ” ne može smjestiti ā premjestiti u TOAST. Ovo se može uÄiniti odmah "u letu", jednom naredbom:
ALTER TABLE rawdata_orig ALTER COLUMN data SET STORAGE MAIN;Kako ocijeniti uÄinak
BuduÄi da se svakodnevni tijek podataka mijenja, ne možemo usporeÄivati apsolutne brojke, ali u relativnim, Å”to manji postotak zapisujemo u TOAST ā to je bolje. Ali ovdje postoji opasnost ā Å”to je veÄi "fiziÄki" volumen svakog pojedinaÄnog zapisa, to "Å”iri" postaje indeks, jer se mora pokriti veÄi broj stranica podataka.
Sekcija prije promjena:
heap = 37GB (39%)
TOAST = 54GB (57%)
PK = 4GB ( 4%)
Sekcija poslije promjena:
heap = 37GB (67%)
TOAST = 16GB (29%)
PK = 2GB ( 4%)Zapravo, mi u TOAST-u piÅ”emo 2 puta rjeÄe, Å”to je olakÅ”alo ne samo disk, veÄ i CPU:


Napominjem da sada i 'Äitamo' disk manje, ne samo 'piÅ”emo' ā jer prilikom umetanja zapisa u neku tablicu moramo 'proÄitati' i dio stabla svakog od indeksa kako bismo odredili njegovu buduÄu poziciju u njima.
Onima koji žive dobro s PostgreSQL 11
Nakon nadogradnje na PG11 odluÄili smo nastaviti s 'tuningom' TOAST-a i obratili pažnju da je od ove verzije dostupan za konfiguraciju parametar :
Kod obrade TOAST-a aktivira se samo kada je vrijednost retka koja se treba pohraniti u tablici veÄa od TOAST_TUPLE_THRESHOLD bajtova (obiÄno je to 2 kB). TOAST kod Äe komprimirati i/ili prenositi vrijednosti polja izvan tablice sve dok vrijednost retka ne postane manja od TOAST_TUPLE_TARGET bajtova (varijabilna veliÄina, takoÄer obiÄno 2 kB) ili dok ne bude moguÄe smanjiti obujam.
OdluÄili smo da su naÅ”i podaci obiÄno ili 'vrlo kratki' ili odmah 'vrlo dugi', pa smo odluÄili ograniÄiti se na minimalnu moguÄu vrijednost:
ALTER TABLE rawplan_orig SET (toast_tuple_target = 128);Pogledajmo kako su nova podeÅ”avanja utjecala na optereÄenje diska nakon ponovnog podeÅ”avanja:

Nije loÅ”e! ProsjeÄna Äekanja na disk smanjena je otprilike 1,5 puta, a 'zauzetost' diska ā za 20%! Ali možda je to nekako utjecalo na CPU?

U svakom sluÄaju, sigurno nije postalo gore. Iako, teÅ”ko je suditi, kada Äak i takvi volumeni ne mogu podiÄi prosjeÄno optereÄenje CPU iznad 5%.
Od promjene mjesta sastojaka zbroj⦠se mijenja!
Kako je poznato, jedan novÄiÄ Å”tedi rubalj, a pri naÅ”im volumenu pohrane od 10TB/mjesec Äak i mala optimizacija može donijeti dobru korist. Stoga smo obratili pažnju na fiziÄku strukturu svojih podataka ā konkretno kako su 'ureÄeni' polja unutar zapisa svake od tablica.
Zato Ŕto zbog to izravno :
Mnoge arhitekture predviÄaju poravnanje podataka po granicama strojnih rijeÄi. Na primjer, na 32-bitnom sistemu x86, cijeli brojevi (tip integer, zauzima 4 bajta) bit Äe poravnati po granici 4-bajtnih rijeÄi, kao i brojevi s pomiÄnom toÄkom duple preciznosti (tip double precision, 8 bajtova). Na 64-bitnom sustavu, double vrijednosti Äe biti poravnate po granici 8-bajtnih rijeÄi. To je joÅ” jedan razlog nespojivosti.
Zbog poravnanja, veliÄina tabliÄnog reda ovisi o redoslijedu postavljanja polja. ObiÄno taj efekt nije jako primjetan, ali u nekim sluÄajevima može dovesti do znaÄajnog poveÄanja veliÄine. Na primjer, ako se polja tipa char(1) i integer mijeÅ”aju, obiÄno Äe izmeÄu njih propadati 3 bajta bez potrebe.
ZapoÄnimo sa sintetiÄkim modelima:
SELECT pg_column_size(ROW(
'0000-0000-0000-0000-0000-0000-0000-0000'::uuid
, 0::smallint
, '2019-01-01'::date
));
-- 48 bajtova
SELECT pg_column_size(ROW(
'2019-01-01'::date
, '0000-0000-0000-0000-0000-0000-0000-0000'::uuid
, 0::smallint
));
-- 46 bajtovaOdakle su doÅ”la dva suviÅ”na bajta u prvom sluÄaju? Sve je jednostavno ā 2-bajtni smallint se poravnava na 4-bajtnoj granici ispred sljedeÄeg polja, dok kada je posljednje ā nema potrebu za poravnanjem.
U teoriji ā sve je dobro i može se preureÄivati polja kako god želite. Provjerimo na stvarnim podacima uz primjer jedne od tablica, Äija dnevna sekcija zauzima oko 10-15GB.
Izvorna struktura:
CREATE TABLE public.plan_20190220
(
-- NaslijeÄeno iz tablice plan: pack uuid NOT NULL,
-- NaslijeÄeno iz tablice plan: recno smallint NOT NULL,
-- NaslijeÄeno iz tablice plan: host uuid,
-- NaslijeÄeno iz tablice plan: ts timestamp with time zone,
-- NaslijeÄeno iz tablice plan: exectime numeric(32,3),
-- NaslijeÄeno iz tablice plan: duration numeric(32,3),
-- NaslijeÄeno iz tablice plan: bufint bigint,
-- NaslijeÄeno iz tablice plan: bufmem bigint,
-- NaslijeÄeno iz tablice plan: bufdsk bigint,
-- NaslijeÄeno iz tablice plan: apn uuid,
-- NaslijeÄeno iz tablice plan: ptr uuid,
-- NaslijeÄeno iz tablice plan: dt date,
CONSTRAINT plan_20190220_pkey PRIMARY KEY (pack, recno),
CONSTRAINT chck_ptr CHECK (ptr IS NOT NULL),
CONSTRAINT plan_20190220_dt_check CHECK (dt = '2019-02-20'::date)
)
INHERITS (public.plan)Sekcija nakon promjene redoslijeda kolona ā toÄno iste polja, samo u drugaÄijem redoslijedu:
CREATE TABLE public.plan_20190221
(
-- NaslijeÄeno iz tablice plan: dt date NOT NULL,
-- NaslijeÄeno iz tablice plan: ts timestamp with time zone,
-- NaslijeÄeno iz tablice plan: pack uuid NOT NULL,
-- NaslijeÄeno iz tablice plan: recno smallint NOT NULL,
-- NaslijeÄeno iz tablice plan: host uuid,
-- NaslijeÄeno iz tablice plan: apn uuid,
-- NaslijeÄeno iz tablice plan: ptr uuid,
-- NaslijeÄeno iz tablice plan: bufint bigint,
-- NaslijeÄeno iz tablice plan: bufmem bigint,
-- NaslijeÄeno iz tablice plan: bufdsk bigint,
-- NaslijeÄeno iz tablice plan: exectime numeric(32,3),
-- NaslijeÄeno iz tablice plan: duration numeric(32,3),
CONSTRAINT plan_20190221_pkey PRIMARY KEY (pack, recno),
CONSTRAINT chck_ptr CHECK (ptr IS NOT NULL),
CONSTRAINT plan_20190221_dt_check CHECK (dt = '2019-02-21'::date)
)
INHERITS (public.plan) Ukupna veliÄina sekcije odreÄuje se brojem "dogaÄaja" i ovisi iskljuÄivo o vanjskim procesima, stoga Äemo podijeliti veliÄinu heap (pg_relation_size) o broju zapisa u njemu ā Å”to Äemo dobiti prosjeÄnu veliÄinu stvarno pohranjenog zapisa:

Minus 6% volumena, odliÄan!
Ali, naravno, nije sve tako ružiÄasto ā jer u indeksima ne možemo promijeniti redoslijed polja, a stoga je 'ukupno' (pg_total_relation_size)ā¦

⦠ipak i ovdje uÅ”tedjeli 1.5%, ne promijenivÅ”i niti jedan redak koda. TaÄno!

Napominjem da prethodna varijanta rasporeda polja ā nije nužno najsvjetlija. Jer neke grupe polja ne želimo 'razdvajati' Äak ni iz estetskih razloga ā na primjer, par (pack, recno), koji je PK za ovu tablicu.
U cjelini, definiranje 'minimalnog' rasporeda polja ā to je priliÄno jednostavan 'povratni' zadatak. Stoga možete dobiti rezultate koji su Äak bolji od naÅ”ih na svojim podacima ā probajte!
Izvor: habr.com
