Suure andmehulkade keerulise töötlemise korral (erinevad : impordid, konvertimised ja sĂŒnkroonimised vĂ€lise allikaga) tekib sageli vajadus ajutiselt "mĂ€letada" ja kohe kiiresti töödelda midagi mahukat.
Kliiniline ĂŒlesanne sellistel juhtudel kĂ”lab tavaliselt umbes nii: "Siin viimased sisse laekunud maksed, tuleb need kiiresti saidile ĂŒles laadida ja siduda kontodega"
Kuid kui selle "midagi" maht hakkab mÔÔtma sadu megabaite ning teenus peab sellegipoolest töötama andmebaasiga reĆŸiimis 24Ă7, siis tekib palju kĂ”rvalmĂ”jusid, mis rikuvad teie elu.

Et nendega toime tulla PostgreSQL-is (ja mitte ainult seal), saab kasutada teatud optimeerimise vÔimalusi, mis vÔimaldavad kÔik kiiremini töödelda ja vÀhem ressursse tarbida.
1. Kuhu laadida?
Esiteks, mÀÀratleme, kuhu me saame laadida andmeid, mida me tahame "töödelda".
1.1. Ajutised tabelid (TEMPORARY TABLE)
PÔhimÔtteliselt on PostgreSQL-is ajutised tabelid samasugused tabelid nagu kÔik teised. Seega on vale arvamus, et «seal kÔik on ainult mÀlus, ja see vÔib otsa lÔppeda». Kuid on ka mÔned olulised erinevused.
Iga andmebaasiĂŒhenduse jaoks oma «nimetust»
Kui kaks ĂŒhendust ĂŒritavad samaaegselt teostada CREATE TABLE x, siis keegi peab kindlasti saama unikaalsuse viga andmebaasiobjektide osas.
Aga kui mĂ”lemad ĂŒritavad teostada CREATE AJUTINE TABEL x, siis mĂ”lemad teevad seda normaalselt, ja igaĂŒks saab oma eksemplari tabelist. Ja neil ei ole omavahel midagi ĂŒhist.
«Enesetapu» disconnecti puhul
Ăhenduse sulgemisel kustutatakse kĂ”ik ajutised tabelid automaatselt, seega pole mĂ”tet «kĂ€sitsi» teostada DROP TABLE x vĂ€lja arvatudâŠ
Kui te töötate pgbounceri tehingureĆŸiimis, siis andmebaas arvab ikkagi, et see ĂŒhendus on endiselt aktiivne, ja selles ajutises tabelis on endiselt olemas.
Seega toob selle uuesti loomine, juba teisest pgbounceri ĂŒhendusest, kaasa veateate. Kuid seda saab ringiga minna, kasutades LOO TEMPORARY TABLE KUI EI OLE x.
TĂ”si, parem oleks seda siiski mitte teha, kuna vĂ”id hiljem âjĂ€rskuâ avastada seal endiselt âeelmise omanikuâ andmed. Selle asemel on palju parem lugeda juhendit ja nĂ€ha, et tabeli loomisel on vĂ”imalik juurde kirjutada. KOHUSTUSEGA DROP â see tĂ€hendab, et tehingu lĂ”petamisel tabel eemaldatakse automaatselt.
Mitte-replikatsioon
Ajutiste tabelite replikatsiooni ei toimu, kuna need kuuluvad ainult kindlale ĂŒhendusele. Kuid see vabastab vajadusest andmete kahekordse salvestamise jĂ€rele heap + WAL, seetĂ”ttu on INSERT/UPDATE/DELETE selles tunduvalt kiirem.
Kuna ajutine tabel on siiski âpeaaegu tavalineâ tabel, ei saa seda ka replikatsiooni luua. VĂ€hemalt praegu, kuigi vastav patch on juba ammu olemas.
1.2. Mitte-logitud tabelid (UNLOGGED TABLE)
Aga mis siis, kui teil on mĂ”ni mahukas ETL-protsess, mida ei Ă”nnestu teostada ĂŒhe tehingu raames, ja teil on tegelikult pgbounceri tehingureĆŸiimis?..
VĂ”i andmevoog on nii suur, et ĂŒhe ĂŒhenduse lĂ€bilaskevĂ”ime ei piisa andmebaasi (loe: ĂŒhe CPU protsessi)?..
VĂ”i osa operatsioonidest toimub asĂŒnkroonselt erinevates ĂŒhendustes?..
Siin on vaid ĂŒks vĂ”imalus â ajutiselt luua mitte-ajutisi tabeleid. MĂ€nguline vĂ€ljend, eks. See tĂ€hendab:
- loodud âomaâ tabelitega maksimaalselt juhuslike nimedega, et mitte kellegagi kokku puutuda
- Ekstraheerimine: laaditi neisse andmed vÀlisest allikast
- Transformeerimine: muundatud, tÀideti vÔtme sidumise vÀljad
- Load: valmitud andmed kanti sihttabelitesse
- kustutas âomaâ tabelid
Aga nĂŒĂŒd â tĂ”rvatilk. Tegelikult, kogu salvestus PostgreSQL-is toimub kahel korral â , seejĂ€rel juba tabelite/indeksite kehad. KĂ”ik see on tehtud ACID toe ja andmete korrektse nĂ€htavuse toetamiseks COMMITâsisenenud ja RULLI TAGASIâsisenenud tehingute vahel.
Aga me ei vaja seda! Meie kogu protsess kas toimis tĂ€iesti vĂ”i mitte.Pole tĂ€htis, kui palju keskseid tehinguid selle sees on â meid ei huvita âprotsessi jĂ€tkamine keskeltâ, eriti kui ei ole selge, kus see oli.
Selle jaoks tutvustasid PostgreSQL arendajad juba versioonis 9.1 sellist asja nagu :
Selle juhisega luuakse tabel mittelogitud tabelina. Mittelogitud tabelitesse kirjutatavad andmed ei lĂ€bi eeljĂ€lgimislogi (vt peatĂŒkk 29), mille tulemuseks on sellised tabelid töötavad palju kiiremini kui tavaliselt. Kuid nad ei ole kaitstud rike eest; kui server kukub vĂ”i selline katkestus toimub, jÀÀb logimata tabel automaatiliselt lĂŒhendatud. Lisaks sellele, logimata tabeli sisu ei replitseerita aluselserveritesse. KĂ”ik indeksid, mis luuakse logimata tabeli jaoks, muutuvad automaatselt logimata.
LĂŒhidalt, on see palju kiirem, kuid kui andmebaasi server âkukubâ â vĂ”ib see olla ebameeldiv. Kuid kui sageli see juhtub ja kas teie ETL-protsess suudab seda pĂ€rast andmebaasi âelustamistâ Ă”igesti kohandada?...
Kui ei, ja ĂŒlaltoodud juhtum sarnaneb teie omaga â kasutage UNLOGGED, kuid Ă€rge kandke seda attribuuti tĂ”elistele tabelitele, mille andmed on teile kallid.
1.3. ON COMMIT { DELETE ROWS | DROP }
Seda konstruktsiooni saab tabeli loomisel kasutada automaatse kÀitumise mÀÀramiseks tehingu lÔpetamisel.
Umbes KOHUSTUSEGA DROP nagu ma varem mainisin, genereerib ta DROP TABLE, kuid siin on KOHUSTUSEGA KUSTUTA RIDADE huvitavam olukord â siinkohal genereeritakse TRUNCATE TABLE.
Kuna kogu ajutise tabeli metaandmete salvestamise infrastruktuur on tĂ€pselt sama, mis tavalisel tabelil, siis ajutiste tabelite pidev loomine ja kustutamine viib sĂŒsteemitabelite tugeva "paisumiseni" pg_class, pg_attribute, pg_attrdef, pg_depend,âŠ
NĂŒĂŒd kujutage ette, et teil on töötaja, kes on otseĂŒhenduses andmebaasiga, ja avab iga sekundi jĂ€rel uue tehingu, loob, tĂ€idab, töötleb ja kustutab ajutise tabeli⊠SĂŒsteemitabelites koguneb liigset prahti, mis toob kaasa igasuguseid viivitusi iga toimingu juures.
ĂhesĂ”naga, nii ei tohiks teha! Sellisel juhul on palju efektiivsem LOO AJUTINE TABEL x ... KOMITTEERIMISEL KUSTUTA RĂNDAD viia vĂ€lja tehingute tsĂŒklist â nii on igas uues tehingus tabelid juba olemas (sÀÀstame vĂ€ljakutse CREATE), kuid on tĂŒhi, tĂ€nu TRUNCATE (selle vĂ€ljakutse me sÀÀstsime ka) eelmine tehing lĂ”petades.
1.4. LIKE⊠KAAASAARVATUD âŠ
Ma mainisin alguses, et ĂŒks tĂŒĂŒpiline kasutusjuht ajutiste tabelite jaoks on erinevad impordid â ja arendaja vĂ€sib, kopeerides sihttabeli vĂ€lja vĂ€ljade loetelu oma ajutisse tabelisse âŠ
Aga laiskus on edasiviiv jÔud! SeetÔttu uus tabel "mooduli" jÀrgi on palju lihtsam luua:
LOO AJUTINE TABEL import_table(
NAGU sihttabel
);Kuna sellele tabelisse genereerida saab vĂ€ga palju andmeid, nende otsimine ei ole sugugi kiire. Kuid sellele on traditsiooniline lahendus â indeksid! Ja jah, ajutistel tabelitel vĂ”ivad samuti olla indeksid.
Kuna sageli vajavad vajalikud indeksid kokku langema sihttabeli indeksitega, saab lihtsalt kirjutada NAGU target_table KAASAS INDEKSD.
Kui vajate veel ka DEFAULT-vÀÀrtused (nĂ€iteks esmase vĂ”tme vÀÀrtuste tĂ€itmiseks), saab kasutada NAGU target_table KAASAR KOHANDAMINE. VĂ”i lihtsalt â NAGU target_table KAASUTADES KĂIKI â kopeerib vaikeseaded, indeksid, piirangud,âŠ
Kuid siin tuleb juba mĂ”ista, et kui olete loonud imporditabeli kohe indeksitega, siis andmete laadimine vĂ”tab kauem aega, kui kĂ”igepealt kĂ”ik laadida ja alles pĂ€rast seda indekseid lisada â vaadake nĂ€iteks, kuidas seda teeb .
Ăldiselt, !
2. Kuidas kirjutada?
Ătlen lihtsalt â kasutage -voogu, mitte "partiide" INSERT, . Saate isegi otse eelnevalt vormistatud failist.
3. Kuidas töödelda?
Nii, oletame, et meie sisend nÀeb vÀlja ligikaudu nii:
- teil on andmebaasis tabel kliendiandmetega 1M kirje
- iga pÀev saadab klient teile uue tÀieliku "pildi"
- kogemuse pÔhjal teate, et kord-korralt muudetakse mitte rohkem kui 10 000 kirjet
Klassikaline nĂ€ide sellisest olukorrast on â aadresside kogus on suur, kuid igas nĂ€dalas esitatavas muudatuste (asulate nimede muutmine, tĂ€navate ĂŒhendamine, uute majade ilmumine) vĂ€ljavĂ”ttes on neid lĂ€bi kogu riigi vĂ€ga vĂ€he.
3.1. TĂ€issĂŒnkroonimise algoritm
Lihtsuse huvides oletame, et te ei pea andmeid restruktureerima â lihtsalt viige tabel soovitud vormi, see tĂ€hendab:
- kustutage kÔik, mis enam ei eksisteeri
- uuendada kÔik, mis juba olemas oli, ja vajab uuendamist
- sisestage kÔik, mida veel ei olnud
Miks just sellises jÀrjestuses tuleks operatsioone teha? Sest just nii kasvab tabeli suurus minimaalselt ().
DELETE FROM dst
Jah, loomulikult saab hakkama ka ainult kahe operatsiooniga:
- kustutage (
DELETE) ĂŒldiselt kĂ”ik - sisestage kĂ”ik uue kujutise seast
Kuid tĂ€nu MVCC-le tabeli suurus kahekordistub! Saada +1M kirjeid tabelisse 10K uuendamise tĂ”ttu â see on kĂŒllaltki liig.
TRUNCATE dst
Kogenum arendaja teab, et kogu tabeli saab ĂŒsna odavalt puhastada:
- puhastage (
TRUNCATE) kogu tabel - sisestage kÔik uue kujutise seast
TĂ”hus meetod, , aga on ĂŒks probleem⊠1M kirjeid lisades lĂ€heb meil kaua aega, seega ei saa me endale lubada, et tabel jÀÀb kogu selle aja jooksul tĂŒhi (nagu juhtub, kui ei kasuta ĂŒhte tehingut).
See tÀhendab:
- meie jaoks algab pikk tehing
TRUNCATEkehtestab AccessExclusive-lukustuse- me teeme kaua lisamist, samal ajal ei saa kÔik teised isegi
SELECT
Ei nĂ€e vĂ€lja, et see hĂ€sti lĂ€hebâŠ
ALTER TABLE⊠RENAME⊠/ DROP TABLE âŠ
Ăks variant on kĂ”ik eraldi uude tabelisse laadida ja seejĂ€rel lihtsalt vana tabeli kohale ĂŒmber nimetada. Paar ebameeldivat asja:
- see on ikka ka samasugune AccessExclusive, kuigi mÀrgatavalt vÀhem aega
- kÔik pÀringute plaanid/statistika selle tabeli kohta kustutatakse,
- kÔik vÀlisvÔtmed (FK) tabeli suhtes katkestatakse Olemas oli WIP-patch Simon Riggsilt, mis pakkus
ALTER -operatsiooni, et asendada tabeli keha failitasemel, kahjustamata statistikat ja FK, kuid ei saavutanud vajalikku toetust.DELETE, UPDATE, INSERT
Nii et jÀÀme kolme operatsiooni mitte-blokeerimise variandi juurde. Peaaegu kolme⊠Kuidas seda kÔige tÔhusamalt teha?
Nii et, peatume kolme toimingu mitte-blokeeriva valiku peal. Peaaegu kolme⊠Kuidas seda kÔige tÔhusamalt teha?
-- teeme kÔik tehingu raames, et keegi ei nÀeks "vahepealseid" olekuid
BEGIN;
-- loome ajutise tabeli imporditud andmete jaoks
CREATE TEMPORARY TABLE tmp(
LIKE dst INCLUDING INDEXES -- sarnane, koos indeksitega
) ON COMMIT DROP; -- tehingu raames ei ole meil seda vaja
-- kiirelt laadime uue kujundi lÀbi COPY
COPY tmp FROM STDIN;
-- ...
-- .
-- eemaldame puuduvad
DELETE FROM
dst D
USING
dst X
LEFT JOIN
tmp Y
USING(pk1, pk2) -- primaarkeele vÀljad
WHERE
(D.pk1, D.pk2) = (X.pk1, X.pk2) AND
Y IS NOT DISTINCT FROM NULL; -- "anti-join"
-- uuendame jÀÀnud
UPDATE
dst D
SET
(f1, f2, f3) = (T.f1, T.f2, T.f3)
FROM
tmp T
WHERE
(D.pk1, D.pk2) = (T.pk1, T.pk2) AND
(D.f1, D.f2, D.f3) IS DISTINCT FROM (T.f1, T.f2, T.f3); -- ei ole mÔtet uuendada kokku langevaid
-- lisame puuduvad
INSERT INTO
dst
SELECT
T.*
FROM
tmp T
LEFT JOIN
dst D
USING(pk1, pk2)
WHERE
D IS NOT DISTINCT FROM NULL;
COMMIT;
3.2. Postitöötlus impordist
Samas KADRi kĂ”iki muudetud kirjeid tuleb lisaks lĂ€bi viia postitöötlus â normaliseerida, eristada mĂ€rksĂ”nu, viia vajalikesse struktuuridesse. Aga kuidas teada â mida tĂ€pselt on muudetud, keerukust suurendamata sĂŒnkroniseerimise koodis, ideaalis, mitte puutuda sellesse ĂŒldse?
Kui kirjutamisĂ”igus on sĂŒnkroniseerimise ajal ainult teie protsessil, saate kasutada kĂ€ivitusmehhanismi, mis kogub kĂ”ik muudatused kokku:
-- siht tabelid
CREATE TABLE kladr(...);
CREATE TABLE kladr_house(...);
-- muudatuste ajalooga tabelid
CREATE TABLE kladr$log(
ro kladr, -- siin on vanade/uutest kirjetest tÀis kujutised
rn kladr
);
CREATE TABLE kladr_house$log(
ro kladr_house,
rn kladr_house
);
-- muudatuste logimise ĂŒldine funktsioon
CREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$
DECLARE
dst varchar = TG_TABLE_NAME || '$log';
stmt text = '';
BEGIN
-- kontrollime, kas logimine on vajalik kirje uuendamisel
IF TG_OP = 'UPDATE' THEN
IF NEW IS NOT DISTINCT FROM OLD THEN
RETURN NEW;
END IF;
END IF;
-- loome logikirje
stmt = 'INSERT INTO ' || dst::text || '(ro,rn) VALUES(';
CASE TG_OP
WHEN 'INSERT' THEN
EXECUTE stmt || 'NULL,$1)' USING NEW;
WHEN 'UPDATE' THEN
EXECUTE stmt || '$1,$2)' USING OLD, NEW;
WHEN 'DELETE' THEN
EXECUTE stmt || '$1,NULL)' USING OLD;
END CASE;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
NĂŒĂŒd saame enne sĂŒnkroniseerimise alustamist kĂ€ivitusmehhanismid aktiveerida (vĂ”i lubada nende kaudu ALTER TABLE ... ENABLE TRIGGER ...):
CREATE TRIGGER log
AFTER INSERT OR UPDATE OR DELETE
ON kladr
FOR EACH ROW
EXECUTE PROCEDURE diff$log();
CREATE TRIGGER log
AFTER INSERT OR UPDATE OR DELETE
ON kladr_house
FOR EACH ROW
EXECUTE PROCEDURE diff$log();
Ja vÔime rahulikult logitabelitest vÀlja vÔtta kÔik vajalikud muudatused ja edastada need tÀiendavatele töötlejatele.
3.3. Seotud komplektide importimine
Ălalpool kĂ€sitlesime juhtumeid, kus sisendi ja vĂ€ljundi andmestruktuurid on samad. Kuid mis juhtub, kui vĂ€listest sĂŒsteemidest saadud eksport on struktuurilt erinev meie andmebaasi salvestamisest?
VĂ”tame nĂ€iteks klientide ja nende arvete sĂ€ilitamise, klassikalise âpalju-ĂŒheleâ variandi:
CREATE TABLE client(
client_id
serial
PRIMARY KEY
, inn
varchar
UNIQUE
, name
varchar
);
CREATE TABLE invoice(
invoice_id
serial
PRIMARY KEY
, client_id
integer
REFERENCES client(client_id)
, number
varchar
, dt
date
, sum
numeric(32,2)
);Kuid vĂ€listest allikatest saadud eksport on meil vormis â kĂ”ik ĂŒhesâ:
CREATE TEMPORARY TABLE invoice_import(
client_inn
varchar
, client_name
varchar
, invoice_number
varchar
, invoice_dt
date
, invoice_sum
numeric(32,2)
);On ilmne, et klientide andmed vĂ”ivad sellisel juhul dubleerida, kusjuures pĂ”hikirje on âarveâ:
0123456789; Vasilij; A-01; 2020-03-16; 1000.00
9876543210; Peeter; A-02; 2020-03-16; 666.00
0123456789; Vasilij; B-03; 2020-03-16; 9999.00
Mudeli jaoks sisestame lihtsalt meie testandmed, kuid peame meeles pidama â COPY efektiivsem!
INSERT INTO invoice_import
VALUES
('0123456789', 'Vassilis', 'A-01', '2020-03-16', 1000.00)
, ('9876543210', 'Petja', 'A-02', '2020-03-16', 666.00)
, ('0123456789', 'Vassilis', 'B-03', '2020-03-16', 9999.00);Esimene samm on tuvastada need 'kategooriad', millele meie 'faktid' viitavad. Meie puhul viitavad arved klientidele:
CREATE TEMPORARY TABLE client_import AS
SELECT DISTINCT ON(client_inn)
-- vÔib kasutada lihtsalt SELECT DISTINCT, kui andmed on eelnevalt kooskÔlas
client_inn inn
, client_name "name"
FROM
invoice_import;Kuna peame arved Ôigesti seostama klientide ID-dega, peame kÔigepealt need identifikaatorid vÀlja selgitama vÔi genereerima. Lisame nende jaoks vÀljad:
ALTER TABLE invoice_import ADD COLUMN client_id integer;
ALTER TABLE client_import ADD COLUMN client_id integer;Kasutame eespool kirjeldatud tabelite sĂŒnkroniseerimise meetodit vĂ€ikese muudatusega â me ei uuenda ega kustuta midagi sihttabelis, kuna klientide importimine on meil 'append-only':
-- impordime tabelisse juba olemasolevate kirje ID-d
UPDATE
client_import T
SET
client_id = D.client_id
FROM
client D
WHERE
T.inn = D.inn; -- unikaalne vÔti
-- lisame puuduvad kirjed ja mÀÀrame nende ID-d
WITH ins AS (
INSERT INTO client(
inn
, name
)
SELECT
inn
, name
FROM
client_import
WHERE
client_id IS NULL -- kui ID pole mÀÀratud
RETURNING *
)
UPDATE
client_import T
SET
client_id = D.client_id
FROM
ins D
WHERE
T.inn = D.inn; -- unikaalne vÔti
-- mÀÀrame klientide ID-d arvete kirjestes
UPDATE
invoice_import T
SET
client_id = D.client_id
FROM
client_import D
WHERE
T.client_inn = D.inn; -- rakendatav vÔti
KokkuvĂ”ttes, kĂ”ik â invoice_import nĂŒĂŒd on meil seotud vĂ€lja tĂ€idetud client_id, millega me ka arve sisestame.
Allikas: habr.com
