DBA: korraldame sĂŒnkroniseerimisi ja importimist oskuslikult

Kui keerukalt töötleda suuri andmekogusid (erinevad ETL-protsessid: impordid, konverteerimised ja sĂŒnkroonimised vĂ€lise allikaga) tekib sageli vajadus ajaliselt "mĂ€luda" ja korraga kiiresti töödelda mĂ”nda suurust.

Selline tĂŒĂŒpiline ĂŒlesanne kĂ”lab tavaliselt nii: "Siin on raamatupidamine eksportinud kliendipangast viimased laekunud maksed, tuleb need kiiresti veebilehele laadida ja siduda kontodega"

Kuid kui selle "millegi" maht hakkab mÔÔtma sadu megabait, samas kui teenus peab töötama andmebaasiga reĆŸiimis 24×7, tekib palju kĂ”rvalmĂ”jusid, mis hakkavad teie elu halvendama.
DBA: korraldame sĂŒnkroniseerimisi ja importimist oskuslikult
Nende vastu vÔitlemiseks PostgreSQL-is (ja mitte ainult seal) saab kasutada teatavaid optsioone optimeerimiseks, mis vÔimaldavad kÔike töötleda kiiremini ja vÀiksema ressursikulu.

1. Kuhu laadida?

Esmalt laske meil mÔista, kuhu me saame laadida andmed, mida soovime "töödeldaks".

1.1. Ajutised tabelid (TEMPORARY TABLE)

PÔhimÔtteliselt on ajutised tabelid PostgreSQL-is samasugused tabelid nagu kÔik teised. SeetÔttu ei ole vale uskumine, et "seal hoitakse kÔike ainult mÀlus ja see vÔib otsa saada". Kuid on ka mitmeid olulisi erinevusi.

Igal DB ĂŒhendusel on oma "nimetamise ruum"

Kui kaks ĂŒhendust ĂŒritavad samal ajal kĂ€ivitada CREATE TABLE x, siis keegi saab kindlasti andmebaasi objektide ainulaadsuse viga. Aga kui mĂ”lemad ĂŒritavad kĂ€ivitada

, siis mĂ”lemad teevad seda normaalselt ja igaĂŒks saab CREATE AJUTINE TABEL xoma eksemplari tabelist. Ja neil ei ole ĂŒksteisega midagi ĂŒhist. "EnesehĂ€vitamine" sulgemisel

Ühenduse sulgemisel kustutatakse kĂ”ik ajutised tabelid automaatselt, seega pole "kĂ€si" teostada

DROP TABLE x mingit mÔtet, vÀlja arvatud... Kui töötate

pgbouncer'i tehingureĆŸiimis , siis andmebaas arvab endiselt, et see ĂŒhendus on endiselt aktiivne, ja selles eksisteerib see ajutine tabel endiselt.Seega toob pĂŒĂŒe seda uuesti luua, juba teise ĂŒhenduse kaudu pgbouncerile, kaasa vea. Kuid seda saab vĂ€ltida, kasutades

Kuid parem on seda ikkagi mitte teha, sest siis vÔite "ootamatult" avastada seal "eelmistest omanikest" jÀÀne andmed. Selle asemel on palju parem lugeda juhendit ja nÀha, et tabeli loomisel on vÔimalus juurde kirjutada LOO TEMPORARY TABLE KUI EI OLE x.

TĂ”epoolest, parem on seda mitte teha, kuna siis vĂ”ib "ĂŒhtĂ€kki" avastada seal endiselt olemasolevaid andmeid "eelmiselt omanikult". Selle asemel on palju parem lugeda manuaali ja nĂ€ha, et tabeli loomisel on vĂ”imalus tĂ€iendavaid andmeid sisestada. REKISTERIMISES DROP — see, when the transaction is completed, the table will be automatically deleted.

Non-replication

Due to being assigned to a specific connection only, temporary tables are not replicated. However, this eliminates the need for double data writing in heap + WAL, hence INSERT/UPDATE/DELETE into it is significantly faster.

But since a temporary table is still "almost a regular" table, it can't be created on a replica either. At least, not yet, although the corresponding patch has been around for a long time.

1.2. Unlogged tables (UNLOGGED TABLE)

But what if, for example, you have some bulky ETL process that can't be implemented within a single transaction, and you indeed , siis andmebaas arvab endiselt, et see ĂŒhendus on endiselt aktiivne, ja selles eksisteerib see ajutine tabel endiselt.?..

Or the data stream is so large that the bandwidth of a single connection to the DB (read, one process on the CPU)?..

Or part of the operations go asynchronously in different connections?..

In this case, there is only one option — temporarily create a non-temporary table. A pun, indeed. That is:

  • created "my" tables with maximally-random names to avoid any overlap
  • Extract: loaded data from an external source into them
  • Transform: transformed, filled key linking fields
  • Laadi: poured the prepared data into target tables
  • deleted "my" tables

And now — a teaspoon of tar. Essentially, all writing in PostgreSQL happens twice — first in the WAL, then in the bodies of tables/indices. All this is done to support ACID and correct data visibility between COMMITinner and ROLLBACKouter transactions.

But we don’t need this! Our entire process must either have completely succeeded or not at all.It doesn’t matter how many intermediate transactions there will be — we are not interested in "continuing the process from the middle", especially when it’s not clear where it was.

For this, PostgreSQL developers introduced a feature as early as version 9.1 called unlogged (UNLOGGED) tables.:

With this indication, the table is created as unlogged. Data written to unlogged tables does not go through the write-ahead log (see Chapter 29), resulting in such tables working much faster than regular ones.However, they are not crash-safe; in case of a crash or unexpected server shutdown, the unlogged table is automatically truncated.Moreover, the content of an unlogged table is not replicated. juhitud serverite jaoks. Iga indeksi, mis luuakse logimata tabeli jaoks, muutub automaatselt logimata.

LĂŒhidalt, on see oluliselt kiirem, kuid kui andmebaasi server "kukkub", on see ebameeldiv. Kuid kui sageli see juhtub ja kas teie ETL-protsess suudab seda Ă”igesti "keskelt" taastada pĂ€rast andmebaasi "taaselustamist"?..

Kui ei, ja eespool mainitud olukord sarnaneb teie omaga — kasutage UNLOGGED, kuid kunagi Ă€rge lĂŒlitage seda atribuuti sisse reaalsetele tabelitele, mille andmed on teile olulised.

1.3. ON COMMIT { KUSTUTA RIDA | KUSTUTA }

See konstruktsioon vÔimaldab tabeli loomisel mÀÀrata automaatse kÀitumise tehingu lÔpetamisel.

KĂŒsimus REKISTERIMISES DROP nagu ma juba eespool mainisin, genereerib see DROP TABLE, kuid siin on REKISTERIMISES KUSTUTA VREAD olukord huvitavam — siin genereeritakse TRUNCATE TABLE.

Kuna ajutise tabeli metaandmete salvestus infrastruktuur on sama, mis tavalistel, siis ajutiste tabelite pidev loomine-kustutamine toob kaasa sĂŒsteemsete tabelite tugeva "paisumise" pg_class, pg_attribute, pg_attrdef, pg_depend,


Kujutage nĂŒĂŒd ette, et teil on töötaja, kes on otseĂŒhenduses andmebaasiga, ja avab igal sekundil uue tehingu, loob, tĂ€idab, töötleb ja kustutab ajutise tabeli
 SĂŒsteemsetesse tabelitesse koguneb liiga palju prĂŒgi, mis toob kaasa tarbetud lagged igas operatsioonis.

Üldiselt, Ă€rge tehke nii! Sel juhul on palju tĂ”husam LOO AJUTINE TABEL x ... ON COMMIT KUSTUTA RIDA toimetada tehingu tsĂŒklist vĂ€lja — siis on iga uue tehingu alguseks tabelid juba olemas (sÀÀstame kutset CREATE), kuid olevad tĂŒhjad, tĂ€nu TRUNCATE (me sÀÀstsime ka selle kutse) eelmise tehingu lĂ”petamisel.

1.4. LIKE
 SISALDAB 


Mainisin alguses, et ĂŒks tĂŒĂŒpiline ajutiste tabelite kasutusjuht on erinevad impordid — ja arendaja kopeerib vĂ€sinult sihttabeli vĂ€ljade nimekirja oma ajutise kuulutusse


Kuid laiskus on progressi mootor! SeetĂ”ttu uue tabeli loomine „mudeleid pidi“ on palju lihtsam:

LOO AJUTINE TABEL import_table(
  LIKE target_table
);

Kuna sellesse tabelisse on vĂ”imalik hiljem genereerida palju andmeid, siis otsingud selles muutuvad ÀÀrmiselt aeglaseks. Kuid selle vastu on traditsiooniline lahendus — indeksid! Ja jah, ajutistel tabelitel vĂ”ivad samuti olla indeksid.

Kuna sageli jÀÀvad vajalikud indeksid kokku sihtpĂ€randi tabeli indeksitega, vĂ”ib lihtsalt kirjutada NAGU target_table KAASAM NÄIDIKUD.

Kui vajate veel ka DEFAULT-vÀÀrtusi (nĂ€iteks primaarsete vĂ”tmete vÀÀrtuste tĂ€itmiseks), saab kasutada NAGU target_table KAASIED SÄHRIT. Noh, vĂ”i lihtsalt — NAGU target_table KAAS KÕIK — kopeerib vaikeseaded, indeksid, piirangud,


Aga siin tuleb juba aru saada, et kui olete loonud impordi-tabeli kohe indeksitega, siis andmete ĂŒleslaadimine kestab kauem, kui kui kĂ”igepealt kogu sisu ĂŒles laadida ja alles seejĂ€rel indeksid rakendada — vaadake nĂ€iteks, kuidas seda teeb pg_dump.

ÜhesĂ”naga, RTFM!

2. Kuidas kirjutada?

Ütlen lihtsalt — kasutage COPY-voogu asemel „partiid“ INSERT, kiirus mitu korda. VĂ”ite isegi otse eelnevalt vormistatud failist.

3. Kuidas töödelda?

Oletame, et meie sisend nÀeb vÀlja umbes nii:

  • teil on andmebaasis tabel kliendiandmetega 1M kirjet
  • iga pĂ€ev saadab klient teile uue tĂ€ieliku „pildi“
  • kogenud inimesed teavad, et iga kord muutub vaid 10K kirjet

Klassikaline nĂ€ide taolisest olukorrast on KLDAR andmebaas — aadresse on palju, kuid iganĂ€dalasel andmevoolul on muutusi (asulate nimede muutmine, tĂ€navate ĂŒhendamine, uute majade ilmumine) vĂ€he isegi kogu riigi ulatuses.

3.1. TĂ€ieliku sĂŒnkroonimise algoritm

Lihtsuse huvides oletame, et andmete struktureerimist pole vaja — lihtsalt viige tabel soovitud vormi, st:

  • kustutada kĂ”ik, mida enam ei ole
  • uuendada kĂ”ik, mis oli, ja tuleb uuendada
  • sisestada kĂ”ik, mida polnud veel

Miks tuleb neid toiminguid tÀpselt niimoodi teha? Sest just nii kasvab tabeli suurus minimaalsetena (peab meeles MVCC!).

DELETE FROM dst

Ei, loomulikult saab hakkama ka vaid kahe toiminguga:

  • kustutada (DELETE) tegelikult kĂ”ik
  • sisestada kĂ”ik uuest pildist

Aga samas, MVCC tĂ”ttu, tabeli suurus kahekordistub! Saada +1M rekordite pilte tabelisse 10K uuendamise tĂ”ttu — pole just ideaalne ĂŒleliigsus


TRUNCATE dst

Kogenum arendaja teab, et kogu tabelit on vÔimalik rahaliselt odavalt puhastada:

  • puhta (TRUNCATE) kogu tabel
  • sisestada kĂ”ik uuest pildist

Meetod on tĂ”hus, mĂ”nikord tĂ€iesti rakendatav, kuid see toob kaasa probleemi
 Me joonistame 1M kirjet ĂŒle vĂ€ga kaua, seega ei saa me endale lubada, et tabel oleks kogu selle aja tĂŒhjaks jĂ€etud (nagu juhtuks ilma ĂŒheainsa tehingu katmiseneta).

Seega:

  • me alustame pikk tehing
  • TRUNCATE kehtestab AccessExclusive-lukku
  • teeme inserte pikka aega, kuid kĂ”ik teised selle ajal ei saa isegi SELECT

Midagi ei tundu hÀsti tulevat


ALTER TABLE
 RENAME
 / DROP TABLE 


VĂ”imalusena vĂ”iks kĂ”ik ĂŒle kanda uude eraldi tabelisse ja seejĂ€rel lihtsalt vana tabeli nime muuta. Paar ebameeldivat pisiasja:

  • ka need AccessExclusive, kuigi oluliselt vĂ€hem ajakulu
  • kustuvad kĂ”ik pĂ€ringute plaanid/statistika selle tabeli kohta, peab kĂ€ivitama ANALYZE
  • katkeb kĂ”ik vĂ€lised vĂ”tmed (FK) sellele tabelile

Oli WIP-patch Simon Riggsilt, mis pakkus vÀlja ALTER-operatsiooni tabeli sisu asendamiseks failitasandil, mÔjutamata statistikat ja FK-d, kuid ei kogunud kvoori.

DELETE, UPDATE, INSERT

Nii et jÀÀme kolme operatsiooni mitteblokeeriva variandi juurde. 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 andmetega
CREATE TEMPORARY TABLE tmp(
  LIKE dst INCLUDING INDEXES -- sarnaselt, koos indeksitega
) ON COMMIT DROP; -- tehingu raames ei ole see meile vajalik

-- kiirelt viime uue sisu sisse lÀbi COPY
COPY tmp FROM STDIN;
-- ...
-- .

-- kustutame puuduvad
DELETE FROM
  dst D
USING
  dst X
LEFT JOIN
  tmp Y
    USING(pk1, pk2) -- primaarklahvi 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 kattuvaid

-- sisestame 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. Impordi jÀrelprotsessimine

Samas KLADris tuleb kĂ”ik muudetud kirjed tĂ€iendavalt lĂ€bi viia jĂ€relprotsessimine — normaliseerida, eraldada mĂ€rksĂ”nad, viia sobivatesse struktuuridesse. Kuid kuidas teada — mida tĂ€pselt muudeti, keerukust sĂŒvenemata sĂŒnkroniseerimise koodi, ideaaljuhul, mitte puudutades seda ĂŒldse?

Kui kirjutamisĂ”igus sĂŒnkroniseerimise hetkel on ainult teie protsessil, siis vĂ”ib kasutada kĂ€ivitusklahvi, mis kogub kĂ”ik meie muudatused:

-- sihtlaud tabelid
CREATE TABLE kladr(...);
CREATE TABLE kladr_house(...);

-- muutuste ajalooga tabelid
CREATE TABLE kladr$log(
  ro kladr, -- siin on vanade/uute kirgede tÀpsed koopiad
  rn kladr
);

CREATE TABLE kladr_house$log(
  ro kladr_house,
  rn kladr_house
);

-- ĂŒldine funktsioon muudatuste logimiseks
CREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$
DECLARE
  dst varchar = TG_TABLE_NAME || '$log';
  stmt text = '';
BEGIN
  -- kontrollime vajadust logimise jÀrel 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 algust kĂ€ivitada (vĂ”i aktiveerida via 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();

SeejÀrel saame logitabelitest rahulikult vÀlja tÔmmata kÔik vajalikud muudatused ning saata need edasi tÀiendavatele töötlejatele.

3.3. Seotud komplektide import

Ülal arutlesime juhtumeid, kus allika ja sihtkoha andmestruktuurid kattuvad. Kuid mis juhul, kui vĂ€listest sĂŒsteemidest saadud eksport on formaadilt erinev meie andmebaasi ladustamisstruktuurist?

VĂ”tame nĂ€iteks klientide ja nende arvete hoidmise, klassikalise â€žĂŒhed-palat“ 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Ă€line allika eksport tuleb meile „kĂ”ik ĂŒhes“ kujul:

CREATE TEMPORARY TABLE invoice_import(
  client_inn
    varchar
, client_name
    varchar
, invoice_number
    varchar
, invoice_dt
    date
, invoice_sum
    numeric(32,2)
);

Ilmselt vĂ”ivad klientide andmed sellises variandis dubleeruda, kuid peamine kirje on „arve“:

0123456789;Vasily;A-01;2020-03-16;1000.00
9876543210;Peter;A-02;2020-03-16;666.00
0123456789;Vasily;B-03;2020-03-16;9999.00

Mudeli jaoks sisestame lihtsalt oma testandmed, kuid peame meeles pidama — COPY efektiivsemalt!

INSERT INTO invoice_import
VALUES
  ('0123456789', 'Vasily', 'A-01', '2020-03-16', 1000.00)
, ('9876543210', 'Peter', 'A-02', '2020-03-16', 666.00)
, ('0123456789', 'Vasily', 'B-03', '2020-03-16', 9999.00);

Alustame nende „vĂ”tmete“ esitlemisest, millele meie „faktid“ viitavad. Meie puhul viitavad arved klientidele:

LOO TEMPORARY TABLE client_import AS
SELECT DISTINCT ON(client_inn)
-- vÔite lihtsalt kasutada SELECT DISTINCT, kui andmed on kindlasti vastuolulised
  client_inn inn
, client_name "name"
FROM
  invoice_import;

Kuna soovime Ôigesti siduda arvete ID-d klientidega, peame esmalt 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 ĂŒlaltoodud viisi tabelite sĂŒnkroniseerimiseks, kuid vĂ€ikese muudatusega — me ei uuenda ega kustuta siht-tabletis, kuna klientide import on meil "append-only":

-- mÀÀrame imporditabelis olemasolevate kirjete ID-d
UPDATE
  client_import T
SET
  client_id = D.client_id
FROM
  client D
WHERE
  T.inn = D.inn; -- unikaalne vÔti

-- sisestame 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 ei ole 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 kirjetes
UPDATE
  invoice_import T
SET
  client_id = D.client_id
FROM
  client_import D
WHERE
  T.client_inn = D.inn; -- rakenduslik vÔti

Sisuliselt on kĂ”ik — invoice_import nĂŒĂŒd on meil tĂ€idetud seose vĂ€li client_id, millega me ka arve sisestame.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster