Dadansoddeg weithredol mewn pensaernïaeth microwasanaeth: helpu ac annog Postgres FDW

Mae gan bensaernïaeth microwasanaeth, fel popeth yn y byd hwn, ei fanteision a'i anfanteision. Mae rhai prosesau yn dod yn haws ag ef, eraill yn fwy anodd. Ac er mwyn cyflymder newid a gwell scalability, mae angen i chi aberthu. Un ohonynt yw cymhlethdod cynyddol dadansoddeg. Os gellir lleihau'r holl ddadansoddeg weithredol mewn monolith i ymholiadau SQL i atgynhyrchiad dadansoddol, yna mewn pensaernïaeth aml-wasanaeth mae gan bob gwasanaeth ei gronfa ddata ei hun ac mae'n ymddangos na ellir gwneud un ymholiad (neu efallai y gellir?). I'r rhai sydd â diddordeb mewn sut y gwnaethom ddatrys problem dadansoddeg weithredol yn ein cwmni a sut y dysgon ni fyw gyda'r ateb hwn - croeso.

Dadansoddeg weithredol mewn pensaernïaeth microwasanaeth: helpu ac annog Postgres FDW
Fy enw i yw Pavel Sivash, yn DomClick rwy'n gweithio mewn tîm sy'n gyfrifol am gynnal y warws data dadansoddol. Yn gonfensiynol, gellir dosbarthu ein gweithgareddau fel peirianneg data, ond, mewn gwirionedd, mae ystod y tasgau yn llawer ehangach. Mae safon ETL/ELT ar gyfer peirianneg data, cefnogi ac addasu offer ar gyfer dadansoddi data a datblygu eich offer eich hun. Yn benodol, ar gyfer adrodd gweithredol, fe benderfynon ni “esgus” bod gennym ni fonolith a rhoi un gronfa ddata i ddadansoddwyr a fydd yn cynnwys yr holl ddata sydd ei angen arnynt.

Yn gyffredinol, fe wnaethom ystyried gwahanol opsiynau. Roedd yn bosibl adeiladu ystorfa lawn - fe wnaethon ni hyd yn oed geisio, ond, a bod yn onest, nid oeddem yn gallu cyfuno newidiadau gweddol aml mewn rhesymeg â'r broses eithaf araf o adeiladu ystorfa a gwneud newidiadau iddi (pe bai rhywun yn llwyddo , ysgrifennwch yn y sylwadau sut). Roedd yn bosibl dweud wrth y dadansoddwyr: “Bois, dysgwch python a mynd at replicas dadansoddol,” ond mae hwn yn ofyniad ychwanegol ar gyfer recriwtio, ac roedd yn ymddangos y dylid osgoi hyn os yn bosibl. Fe benderfynon ni geisio defnyddio technoleg FDW (Foreign Data Wrapper): yn y bôn, dblink safonol yw hwn, sydd yn y safon SQL, ond gyda'i ryngwyneb llawer mwy cyfleus ei hun. Yn seiliedig arno, gwnaethom ddatrysiad, a ddaliodd sylw yn y pen draw, ac fe wnaethom setlo arno. Mae ei fanylion yn destun erthygl ar wahân, ac efallai mwy nag un, gan fy mod eisiau siarad am lawer: o gydamseru sgemâu cronfa ddata i gael mynediad at reolaeth a dadbersonoli data personol. Mae hefyd angen gwneud amheuaeth nad yw'r datrysiad hwn yn disodli cronfeydd data dadansoddol a storfeydd go iawn; dim ond problem benodol y mae'n ei datrys.

Ar y lefel uchaf mae'n edrych fel hyn:

Dadansoddeg weithredol mewn pensaernïaeth microwasanaeth: helpu ac annog Postgres FDW
Mae cronfa ddata PostgreSQL lle gall defnyddwyr storio eu data gwaith, ac yn bwysicaf oll, mae copïau dadansoddol o'r holl wasanaethau wedi'u cysylltu â'r gronfa ddata hon trwy FDW. Mae hyn yn ei gwneud hi'n bosibl ysgrifennu ymholiad i sawl cronfa ddata, ac nid oes ots beth ydyw: PostgreSQL, MySQL, MongoDB neu rywbeth arall (ffeil, API, os yn sydyn nad oes deunydd lapio addas, gallwch chi ysgrifennu eich un chi). Wel, mae popeth yn ymddangos yn wych! Ydyn ni'n torri i fyny?

Pe bai popeth yn dod i ben mor gyflym a syml, yna, mae'n debyg, ni fyddai erthygl.

Mae'n bwysig bod yn glir ynghylch sut mae Postgres yn prosesu ceisiadau i weinyddion o bell. Mae hyn yn ymddangos yn rhesymegol, ond yn aml nid yw pobl yn talu sylw iddo: mae Postgres yn rhannu'r cais yn rhannau sy'n cael eu gweithredu'n annibynnol ar weinyddion anghysbell, yn casglu'r data hwn, ac yn perfformio'r cyfrifiadau terfynol ei hun, felly bydd cyflymder cyflawni ymholiad yn dibynnu'n fawr ar sut mae'n cael ei ysgrifennu. Dylid nodi hefyd: pan fydd y data'n cyrraedd o weinydd pell, nid oes ganddo fynegeion mwyach, nid oes unrhyw beth a fydd yn helpu'r trefnydd, felly, dim ond ni ein hunain all ei helpu a'i gynghori. A dyma'n union yr wyf am siarad amdano yn fwy manwl.

Ymholiad syml a chynllun gydag ef

I ddangos sut mae Postgres yn holi tabl rhes 6 miliwn ar weinydd pell, gadewch i ni edrych ar gynllun syml.

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

Mae defnyddio'r datganiad VERBOSE yn ein galluogi i weld yr ymholiad a fydd yn cael ei anfon at y gweinydd pell a'r canlyniadau y byddwn yn eu derbyn i'w prosesu ymhellach (llinell RemoteSQL).

Gadewch i ni fynd ychydig ymhellach ac ychwanegu sawl hidlydd i'n cais: un ar gyfer boolean maes, un wrth ddigwyddiad stamp amser yn y cyfwng ac un gan 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

Dyma lle mae'r pwynt y mae angen i chi roi sylw iddo wrth ysgrifennu ymholiadau. Ni throsglwyddwyd yr hidlwyr i'r gweinydd pell, sy'n golygu bod Postgres yn tynnu pob un o'r 6 miliwn o resi allan i'w gweithredu er mwyn hidlo'n lleol (Hidlo rhes) a pherfformio agregu. Yr allwedd i lwyddiant yw ysgrifennu ymholiad fel bod yr hidlwyr yn cael eu trosglwyddo i'r peiriant anghysbell, ac rydym yn derbyn ac yn agregu dim ond y rhesi angenrheidiol.

Dyna beth booleanshit

Gyda chaeau boolean mae popeth yn syml. Yn y cais gwreiddiol, y gweithredwr oedd yn gyfrifol am y broblem is. Os byddwch yn ei ddisodli gyda =, yna cawn y canlyniad canlynol:

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

Fel y gallwch weld, hedfanodd yr hidlydd i weinydd pell, a gostyngwyd yr amser gweithredu o 27 i 19 eiliad.

Mae'n werth nodi bod y gweithredwr is wahanol i'r gweithredwr = oherwydd gall weithio gyda'r gwerth Null. Mae'n golygu hynny Nid yw'n Gwir yn gadael y gwerthoedd Gau a Null yn yr hidlydd, tra != Gwir yn gadael dim ond gwerthoedd Gau. Felly, wrth ddisodli'r gweithredwr Nid yw dylid trosglwyddo dau amod gyda'r gweithredwr OR i'r hidlydd, er enghraifft, BLE (col! = Gwir) NEU (col yn null).

Rydyn ni wedi delio â boolean, gadewch i ni symud ymlaen. Am y tro, gadewch i ni ddychwelyd yr hidlydd Boole i'w ffurf wreiddiol er mwyn ystyried yn annibynnol effaith newidiadau eraill.

stamp amser? hz

Yn gyffredinol, yn aml mae'n rhaid i chi arbrofi gyda sut i ysgrifennu cais sy'n ymwneud â gweinyddwyr o bell yn gywir, a dim ond wedyn edrych am esboniad o pam mae hyn yn digwydd. Ychydig iawn o wybodaeth am hyn sydd i'w chael ar y Rhyngrwyd. Felly, mewn arbrofion canfuom fod hidlydd dyddiad sefydlog yn hedfan i'r gweinydd pell gyda chlec, ond pan fyddwn am osod y dyddiad yn ddeinamig, er enghraifft, nawr() neu CURRENT_DATE, nid yw hyn yn digwydd. Yn ein hesiampl, fe wnaethom ychwanegu hidlydd fel bod y golofn created_at yn cynnwys data am union 1 mis yn y gorffennol ( RHWNG CURRENT_DATE - INTERVAL '7 month' AND CURRENT_DATE - INTERVAL '6 month'). Beth wnaethom ni yn yr achos hwn?

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

Fe wnaethom ddweud wrth y cynlluniwr i gyfrifo'r dyddiad yn yr subquery ymlaen llaw a throsglwyddo'r newidyn parod i'r hidlydd. A rhoddodd yr awgrym hwn ganlyniad rhagorol i ni, daeth y cais bron 6 gwaith yn gyflymach!

Unwaith eto, mae'n bwysig bod yn ofalus yma: rhaid i'r math o ddata yn y subquery fod yr un fath â'r maes yr ydym yn hidlo arno, fel arall bydd y cynlluniwr yn penderfynu, gan fod y mathau'n wahanol, bod angen cael y cyfan yn gyntaf. y data a'i hidlo'n lleol.

Gadewch i ni ddychwelyd yr hidlydd dyddiad i'w werth gwreiddiol.

Freddy vs. Jsonb

Yn gyffredinol, mae meysydd a dyddiadau Boole eisoes wedi cyflymu ein hymholiad yn ddigonol, ond roedd un math arall o ddata ar ôl. Nid yw'r frwydr gyda hidlo ganddo, a dweud y gwir, ar ben eto, er bod llwyddiant yma hefyd. Felly, dyma sut y gwnaethom lwyddo i basio'r hidlydd heibio jsonb maes i'r gweinydd pell.

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

Yn lle hidlo gweithredwyr, rhaid i chi ddefnyddio presenoldeb un gweithredwr jsonb mewn gwahanol. 7 eiliad yn lle'r 29 gwreiddiol. Hyd yn hyn dyma'r unig opsiwn llwyddiannus ar gyfer trawsyrru ffilterau trwy jsonb i weinydd pell, ond yma mae'n bwysig ystyried un cyfyngiad: rydym yn defnyddio fersiwn 9.6 o'r gronfa ddata, ond erbyn diwedd mis Ebrill rydym yn bwriadu cwblhau'r profion olaf a symud i fersiwn 12. Ar ôl i ni ddiweddaru, byddwn yn ysgrifennu am sut yr effeithiodd, oherwydd mae yna lawer iawn o newidiadau y mae llawer o obaith amdanynt: json_path, ymddygiad CTE newydd, gwthio i lawr (sy'n bodoli ers fersiwn 10). Dwi wir eisiau rhoi cynnig arni yn fuan.

Gorffen ef

Fe wnaethon ni brofi sut roedd pob newid yn effeithio ar gyflymder ceisiadau yn unigol. Gadewch i ni nawr weld beth sy'n digwydd pan fydd pob un o'r tair hidlydd wedi'u hysgrifennu'n gywir.

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

Ydy, mae'r cais yn edrych yn fwy cymhleth, mae hwn yn ffi orfodol, ond mae'r cyflymder gweithredu yn 2 eiliad, sy'n fwy na 10 gwaith yn gyflymach! Ac rydym yn sôn am ymholiad syml yn erbyn set ddata gymharol fach. Ar geisiadau gwirioneddol, cawsom gynnydd o hyd at gannoedd o weithiau.

I grynhoi: os ydych yn defnyddio PostgreSQL gyda FDW, gwiriwch bob amser bod yr holl hidlyddion yn cael eu hanfon i'r gweinydd pell, a byddwch yn hapus... O leiaf nes i chi gyrraedd uniadau rhwng byrddau o weinyddion gwahanol. Ond stori ar gyfer erthygl arall yw honno.

Diolch am eich sylw! Byddwn wrth fy modd yn clywed cwestiynau, sylwadau, a straeon am eich profiadau yn y sylwadau.

Ffynhonnell: hab.com

Ychwanegu sylw