Operatīvā analītika mikropakalpojumu arhitektūrā: palīdzība un uzvedne Postgres FDW

Mikropakalpojumu arhitektÅ«rai, tāpat kā visam Å”ajā pasaulē, ir savi plusi un mÄ«nusi. Daži procesi ar to kļūst vieglāki, citi grÅ«tāki. Un, lai nodroÅ”inātu pārmaiņu ātrumu un labāku mērogojamÄ«bu, jums ir jānes upuri. Viens no tiem ir analÄ«tikas sarežģītÄ«ba. Ja monolÄ«tā visu operacionālo analÄ«zi var reducēt lÄ«dz SQL vaicājumiem lÄ«dz analÄ«tiskai replikai, tad daudzpakalpojumu arhitektÅ«rā katram pakalpojumam ir sava datu bāze un Ŕķiet, ka ar vienu vaicājumu nepietiek (vai varbÅ«t arÄ« pietiks?). Tiem, kas interesējas par to, kā mēs savā uzņēmumā atrisinājām operatÄ«vās analÄ«tikas problēmu un kā mēs iemācÄ«jāmies sadzÄ«vot ar Å”o risinājumu - laipni lÅ«gti.

Operatīvā analītika mikropakalpojumu arhitektūrā: palīdzība un uzvedne Postgres FDW
Mani sauc Pāvels SivaÅ”s. DomClick es strādāju komandā, kas ir atbildÄ«ga par analÄ«tisko datu noliktavas uzturÄ“Å”anu. Tradicionāli mÅ«su darbÄ«bas var attiecināt uz datu inženieriju, taču patiesÄ«bā uzdevumu loks ir daudz plaŔāks. Ir standarta datu inženierijas ETL / ELT, datu analÄ«zes rÄ«ku atbalsts un pielāgoÅ”ana un savu rÄ«ku izstrāde. Jo Ä«paÅ”i operatÄ«vo pārskatu sniegÅ”anai mēs nolēmām ā€œizliktiesā€, ka mums ir monolÄ«ts, un dot analÄ«tiÄ·iem vienu datu bāzi, kurā bÅ«s visi nepiecieÅ”amie dati.

Kopumā mēs izskatÄ«jām dažādas iespējas. PilnvērtÄ«gu repozitoriju bija iespējams uzbÅ«vēt - mēs pat mēģinājām, bet, godÄ«gi sakot, ar diezgan lēnu repozitorija veidoÅ”anas un izmaiņu veikÅ”anas procesu nevarējām sadraudzēties ar diezgan biežām loÄ£ikas izmaiņām ( ja kādam izdevās, rakstiet komentāros kā). JÅ«s varētu teikt analÄ«tiÄ·iem: "PuiÅ”i, mācieties pitonu un dodieties uz analÄ«tiskajām lÄ«nijām", taču tā ir papildu prasÄ«ba darbā pieņemÅ”anai, un Ŕķita, ka no tā, ja iespējams, vajadzētu izvairÄ«ties. Mēs nolēmām izmēģināt FDW (Foreign Data Wrapper) tehnoloÄ£iju: patiesÄ«bā Ŕī ir standarta dblink, kas ir SQL standartā, bet ar daudz ērtāku saskarni. Pamatojoties uz to, mēs pieņēmām lēmumu, kas galu galā iesakņojās, mēs pie tā pieņēmāmies. SÄ«kāka informācija par to ir atseviŔķa raksta tēma un varbÅ«t vairāk nekā viena, jo es vēlos runāt par daudz ko: no datu bāzes shēmas sinhronizācijas lÄ«dz piekļuves kontrolei un personas datu depersonalizācijai. Jāņem vērā arÄ« tas, ka Å”is risinājums neaizstāj reālas analÄ«tiskās datu bāzes un repozitorijus, tas tikai atrisina konkrētu problēmu.

AugŔējā lÄ«menÄ« tas izskatās Ŕādi:

Operatīvā analītika mikropakalpojumu arhitektūrā: palīdzība un uzvedne Postgres FDW
Ir PostgreSQL datu bāze, kurā lietotāji var glabāt savus darba datus, un pats galvenais, visu pakalpojumu analÄ«tiskās kopijas ir savienotas ar Å”o datu bāzi, izmantojot FDW. Tas ļauj rakstÄ«t vaicājumu vairākām datu bāzēm, un nav svarÄ«gi, kas tas ir: PostgreSQL, MySQL, MongoDB vai kaut kas cits (fails, API, ja pēkŔņi nav piemērota iesaiņojuma, varat rakstÄ«t savu). Nu, Ŕķiet, ka viss ir lieliski! IzŔķirties?

Ja viss beigtos tik ātri un vienkārÅ”i, tad, iespējams, raksts nepastāvētu.

Ir svarÄ«gi skaidri saprast, kā Postgres apstrādā pieprasÄ«jumus attāliem serveriem. Tas Ŕķiet loÄ£iski, taču bieži vien cilvēki tam nepievērÅ” uzmanÄ«bu: postgres sadala vaicājumu daļās, kas tiek izpildÄ«tas neatkarÄ«gi attālos serveros, apkopo Å”os datus un pats veic gala aprēķinus, tāpēc vaicājuma izpildes ātrums lielā mērā bÅ«s atkarÄ«gs no tā, kā tas ir rakstÄ«ts. Jāpiebilst arÄ«: kad dati nāk no attālā servera, tiem vairs nav indeksu, plānotājam nekas nepalÄ«dzēs, tāpēc palÄ«dzēt un ieteikt varam tikai mēs paÅ”i. Un par to es gribu runāt sÄ«kāk.

VienkārŔs pieprasījums un plāns ar to

Lai parādītu, kā Postgres vaicā 6 miljonu rindu tabulu attālā serverī, apskatīsim vienkārŔu plānu.

explain analyze verbose  
SELECT count(1)
FROM fdw_schema.table;

Aggregate  (cost=418383.23..418383.24 rows=1 width=8) (actual time=3857.198..3857.198 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.00..402376.14 rows=6402838 width=0) (actual time=4.874..3256.511 rows=6406868 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Remote SQL: SELECT NULL FROM fdw_schema.table
Planning time: 0.986 ms
Execution time: 3857.436 ms

VERBOSE priekÅ”raksta izmantoÅ”ana ļauj redzēt vaicājumu, kas tiks nosÅ«tÄ«ts uz attālo serveri un kura rezultātus saņemsim tālākai apstrādei (RemoteSQL virkne).

Dosimies nedaudz tālāk un pievienosim savam vaicājumam vairākus filtrus: pa vienam būla lauks, pa vienam pēc ieraksta laikspiedolu intervālā un pa vienam jsonb.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active is True
AND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' 
AND CURRENT_DATE - INTERVAL '6 month'
AND meta->>'source' = 'test';

Aggregate  (cost=577487.69..577487.70 rows=1 width=8) (actual time=27473.818..25473.819 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.00..577469.21 rows=7390 width=0) (actual time=31.369..25372.466 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: (("table".is_active IS TRUE) AND (("table".meta ->> 'source'::text) = 'test'::text) AND ("table".created_dt >= (('now'::cstring)::date - '7 mons'::interval)) AND ("table".created_dt <= ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)))
        Rows Removed by Filter: 5046843
        Remote SQL: SELECT created_dt, is_active, meta FROM fdw_schema.table
Planning time: 0.665 ms
Execution time: 27474.118 ms

Å eit ir Ä«stais brÄ«dis, kam jāpievērÅ” uzmanÄ«ba, rakstot vaicājumus. Filtri netika pārsÅ«tÄ«ti uz attālo serveri, kas nozÄ«mē, ka, lai to izpildÄ«tu, postgres izvelk visus 6 miljonus rindu, lai filtrētu lokāli (rinda Filtrs) un vēlāk veiktu apkopoÅ”anu. Panākumu atslēga ir uzrakstÄ«t vaicājumu, lai filtri tiktu pārsÅ«tÄ«ti uz attālo maŔīnu, un mēs saņemtu un apkopotu tikai nepiecieÅ”amās rindas.

Tas ir kaut kāds bÅ«liņŔ

Ar BÅ«la laukiem viss ir vienkārÅ”i. Sākotnējā vaicājumā problēmu radÄ«ja operators is. Ja mēs to aizstājam ar =, tad iegÅ«stam Ŕādu rezultātu:

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table
WHERE is_active = True
AND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' 
AND CURRENT_DATE - INTERVAL '6 month'
AND meta->>'source' = 'test';

Aggregate  (cost=508010.14..508010.15 rows=1 width=8) (actual time=19064.314..19064.314 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.00..507988.44 rows=8679 width=0) (actual time=33.035..18951.278 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: ((("table".meta ->> 'source'::text) = 'test'::text) AND ("table".created_dt >= (('now'::cstring)::date - '7 mons'::interval)) AND ("table".created_dt <= ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)))
        Rows Removed by Filter: 3567989
        Remote SQL: SELECT created_dt, meta FROM fdw_schema.table WHERE (is_active)
Planning time: 0.834 ms
Execution time: 19064.534 ms

Kā redzat, filtrs aizlidoja uz attālo serveri, un izpildes laiks tika samazināts no 27 līdz 19 sekundēm.

Jāpiebilst, ka operators is atŔķiras no operatora = tas, kas var darboties ar Null vērtÄ«bu. Tas nozÄ«mē, ka nav PatiesÄ«ba filtrā atstās vērtÄ«bas False un Null, kamēr != TaisnÄ«ba atstās tikai Viltus vērtÄ«bas. Tāpēc, nomainot operatoru ir ne jums ir jānodod filtram divi nosacÄ«jumi ar operatoru VAI, piemēram, WHERE (kolonna != patiesa) VAI (kola ir nulle).

Ar Būla izdomātu, dodamies tālāk. Tikmēr atgriezīsim filtru pēc Būla vērtības sākotnējā formā, lai neatkarīgi apsvērtu citu izmaiņu ietekmi.

laika zīmogs? hz

Parasti bieži vien ir jāeksperimentē, kā pareizi uzrakstÄ«t vaicājumu, kas ietver attālos serverus, un tikai tad jāmeklē skaidrojums, kāpēc tas notiek. Internetā par to var atrast ļoti maz informācijas. Tātad eksperimentos mēs noskaidrojām, ka fiksēta datuma filtrs ar triecienu lido uz attālo serveri, taču, ja mēs vēlamies iestatÄ«t datumu dinamiski, piemēram, now() vai CURRENT_DATE, tas nenotiek. MÅ«su piemērā esam pievienojuÅ”i filtru, lai kolonnā Created_at bÅ«tu dati par tieÅ”i 1 mēnesi pagātnē (STARP CURRENT_DATE ā€” INTERVAL '7 mēneÅ”i' UN CURRENT_DATE - INTERVAL '6 mēneÅ”i'). Ko mēs darÄ«jām Å”ajā gadÄ«jumā?

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active is True
AND created_dt >= (SELECT CURRENT_DATE::timestamptz - INTERVAL '7 month') 
AND created_dt <(SELECT CURRENT_DATE::timestamptz - INTERVAL '6 month')
AND meta->>'source' = 'test';

Aggregate  (cost=306875.17..306875.18 rows=1 width=8) (actual time=4789.114..4789.115 rows=1 loops=1)
  Output: count(1)
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.007..0.008 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '7 mons'::interval)
  InitPlan 2 (returns $1)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.02..306874.86 rows=105 width=0) (actual time=23.475..4681.419 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: (("table".is_active IS TRUE) AND (("table".meta ->> 'source'::text) = 'test'::text))
        Rows Removed by Filter: 76934
        Remote SQL: SELECT is_active, meta FROM fdw_schema.table WHERE ((created_dt >= $1::timestamp with time zone)) AND ((created_dt < $2::timestamp with time zone))
Planning time: 0.703 ms
Execution time: 4789.379 ms

Mēs mudinājām plānotāju apakÅ”vaicājumā iepriekÅ” aprēķināt datumu un nodot filtram jau sagatavoto mainÄ«go. Un Å”is mājiens mums deva lielisku rezultātu, vaicājums kļuva gandrÄ«z 6 reizes ātrāks!

Å eit atkal ir svarÄ«gi bÅ«t uzmanÄ«giem: datu tipam apakÅ”vaicājumā ir jābÅ«t tādam paÅ”am kā laukam, pēc kura mēs filtrējam, pretējā gadÄ«jumā plānotājs nolems, ka, jo veidi ir atŔķirÄ«gi un vispirms ir jāiegÅ«st visi datus un filtrējiet tos lokāli.

Atgriezīsim filtra pēc datuma sākotnējo vērtību.

Fredijs vs. jsonb

Kopumā BÅ«la lauki un datumi jau ir pietiekami paātrinājuÅ”i mÅ«su vaicājumu, taču bija vēl viens datu veids. Cīņa ar filtrÄ“Å”anu pēc tā, godÄ«gi sakot, joprojām nav beigusies, lai gan arÄ« Å”eit ir panākumi. Tātad, lÅ«k, kā mums izdevās tikt garām filtram jsonb uz attālo serveri.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active is True
AND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' 
AND CURRENT_DATE - INTERVAL '6 month'
AND meta @> '{"source":"test"}'::jsonb;

Aggregate  (cost=245463.60..245463.61 rows=1 width=8) (actual time=6727.589..6727.590 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=1100.00..245459.90 rows=1478 width=0) (actual time=16.213..6634.794 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: (("table".is_active IS TRUE) AND ("table".created_dt >= (('now'::cstring)::date - '7 mons'::interval)) AND ("table".created_dt <= ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)))
        Rows Removed by Filter: 619961
        Remote SQL: SELECT created_dt, is_active FROM fdw_schema.table WHERE ((meta @> '{"source": "test"}'::jsonb))
Planning time: 0.747 ms
Execution time: 6727.815 ms

Tā vietā, lai filtrētu operatorus, ir jāizmanto viena operatora klātbÅ«tne. jsonb citā. 7 sekundes oriÄ£inālo 29 vietā. LÄ«dz Å”im Ŕī ir vienÄ«gā veiksmÄ«gā iespēja filtru pārsÅ«tÄ«Å”anai jsonb uz attālo serveri, taču Å”eit svarÄ«gi ņemt vērā vienu ierobežojumu: izmantojam datu bāzes versiju 9.6, bet lÄ«dz aprīļa beigām plānojam pabeigt pēdējos testus un pāriet uz 12. versiju. TiklÄ«dz mēs atjaunināsim, mēs rakstÄ«sim, kā tas ietekmēja, jo ir daudz izmaiņu, uz kurām ir daudz cerÄ«bu: json_path, jauna CTE uzvedÄ«ba, push down (pastāv no versijas 10). Es ļoti vēlos to izmēģināt drÄ«z.

Piebeidz viņu

Mēs pārbaudÄ«jām, kā katras izmaiņas ietekmē vaicājuma ātrumu atseviŔķi. Tagad redzēsim, kas notiek, ja visi trÄ«s filtri ir uzrakstÄ«ti pareizi.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active = True
AND created_dt >= (SELECT CURRENT_DATE::timestamptz - INTERVAL '7 month') 
AND created_dt <(SELECT CURRENT_DATE::timestamptz - INTERVAL '6 month')
AND meta @> '{"source":"test"}'::jsonb;

Aggregate  (cost=322041.51..322041.52 rows=1 width=8) (actual time=2278.867..2278.867 rows=1 loops=1)
  Output: count(1)
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.010..0.010 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '7 mons'::interval)
  InitPlan 2 (returns $1)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.02..322041.41 rows=25 width=0) (actual time=8.597..2153.809 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Remote SQL: SELECT NULL FROM fdw_schema.table WHERE (is_active) AND ((created_dt >= $1::timestamp with time zone)) AND ((created_dt < $2::timestamp with time zone)) AND ((meta @> '{"source": "test"}'::jsonb))
Planning time: 0.820 ms
Execution time: 2279.087 ms

Jā, vaicājums izskatās sarežģītāks, tā ir piespiedu cena, bet izpildes ātrums ir 2 sekundes, kas ir vairāk nekā 10 reizes ātrāks! Un mēs runājam par vienkārÅ”u vaicājumu salÄ«dzinoÅ”i nelielai datu kopai. Pēc reāliem pieprasÄ«jumiem mēs saņēmām palielinājumu lÄ«dz pat vairākiem simtiem reižu.

Rezumējot: ja jūs izmantojat PostgreSQL ar FDW, vienmēr pārbaudiet, vai visi filtri ir nosūtīti uz attālo serveri, un jūs būsiet laimīgs... Vismaz līdz brīdim, kad nokļūsit pie savienojumu starp tabulām no dažādiem serveriem. Bet tas ir stāsts citam rakstam.

Paldies par jūsu uzmanību! Es labprāt komentāros dzirdētu jautājumus, komentārus un stāstus par jūsu pieredzi.

Avots: www.habr.com

Pievieno komentāru