Uchanganuzi wa kiutendaji katika usanifu wa huduma ndogo: msaada na uharakishe Postgres FDW

Usanifu wa Microservice, kama kila kitu katika ulimwengu huu, una faida na hasara zake. Michakato mingine inakuwa rahisi nayo, mingine ngumu zaidi. Na kwa ajili ya kasi ya mabadiliko na scalability bora, unahitaji kutoa dhabihu. Mmoja wao ni kuongezeka kwa utata wa uchanganuzi. Ikiwa katika monolith uchambuzi wote wa uendeshaji unaweza kupunguzwa kwa maswali ya SQL kwa replica ya uchambuzi, basi katika usanifu wa multiservice kila huduma ina database yake na inaonekana kwamba swala moja haiwezi kufanywa (au labda inaweza?). Kwa wale ambao wana nia ya jinsi tulivyotatua tatizo la uchambuzi wa uendeshaji katika kampuni yetu na jinsi tulivyojifunza kuishi na suluhisho hili - karibu.

Uchanganuzi wa kiutendaji katika usanifu wa huduma ndogo: msaada na uharakishe Postgres FDW
Jina langu ni Pavel Sivash, huko DomClick ninafanya kazi katika timu ambayo ina jukumu la kudumisha ghala la data ya uchambuzi. Kwa kawaida, shughuli zetu zinaweza kuainishwa kama uhandisi wa data, lakini, kwa kweli, anuwai ya kazi ni pana zaidi. Kuna kiwango cha ETL/ELT cha uhandisi wa data, usaidizi na urekebishaji wa zana za uchanganuzi wa data na uundaji wa zana zako mwenyewe. Hasa, kwa taarifa ya uendeshaji, tuliamua "kujifanya" kuwa tuna monolith na kuwapa wachambuzi database moja ambayo itakuwa na data zote wanazohitaji.

Kwa ujumla, tulizingatia chaguzi tofauti. Iliwezekana kujenga hazina iliyojaa - tulijaribu hata, lakini, kuwa waaminifu, hatukuweza kuchanganya mabadiliko ya mara kwa mara katika mantiki na mchakato polepole wa kujenga hazina na kuifanyia mabadiliko (ikiwa mtu alifanikiwa. , andika kwenye maoni jinsi gani). Iliwezekana kuwaambia wachambuzi: "Guys, jifunze python na uende kwenye replicas za uchambuzi," lakini hii ni mahitaji ya ziada ya kuajiri, na ilionekana kuwa hii inapaswa kuepukwa ikiwa inawezekana. Tuliamua kujaribu kutumia teknolojia ya FDW (Foreign Data Wrapper): kimsingi, hii ni dblink ya kawaida, ambayo iko katika kiwango cha SQL, lakini na interface yake rahisi zaidi. Kulingana na hilo, tulifanya suluhisho, ambalo hatimaye lilishika, na tukakaa juu yake. Maelezo yake ni mada ya nakala tofauti, na labda zaidi ya moja, kwani nataka kuzungumza juu ya mengi: kutoka kwa kusawazisha schema za hifadhidata hadi kufikia udhibiti na ubinafsishaji wa data ya kibinafsi. Inahitajika pia kuweka uhifadhi kwamba suluhisho hili sio badala ya hifadhidata na hazina za uchambuzi halisi; husuluhisha shida maalum tu.

Katika hali ya juu, inaonekana kama hii:

Uchanganuzi wa kiutendaji katika usanifu wa huduma ndogo: msaada na uharakishe Postgres FDW
Kuna hifadhidata ya PostgreSQL ambapo watumiaji wanaweza kuhifadhi data zao za kazi, na muhimu zaidi, nakala za uchanganuzi za huduma zote zimeunganishwa kwenye hifadhidata hii kupitia FDW. Hii inafanya uwezekano wa kuandika swala kwa hifadhidata kadhaa, na haijalishi ni nini: PostgreSQL, MySQL, MongoDB au kitu kingine (faili, API, ikiwa ghafla hakuna wrapper inayofaa, unaweza kuandika yako mwenyewe). Naam, kila kitu kinaonekana kizuri! Je, tunaachana?

Ikiwa kila kitu kilimalizika haraka na kwa urahisi, basi, labda, hakutakuwa na nakala.

Ni muhimu kuwa wazi kuhusu jinsi Postgres inavyochakata maombi kwa seva za mbali. Hili linaonekana kuwa la kimantiki, lakini mara nyingi watu hawalitilii maanani: Postgres hugawanya ombi hilo katika sehemu ambazo hutekelezwa kwa kujitegemea kwenye seva za mbali, hukusanya data hii, na kufanya mahesabu ya mwisho yenyewe, kwa hivyo kasi ya utekelezaji wa hoja itategemea sana. jinsi imeandikwa. Inapaswa pia kuzingatiwa: wakati data inakuja kutoka kwa seva ya mbali, haina tena indexes, hakuna kitu ambacho kitasaidia mpangaji, kwa hiyo, sisi wenyewe tu tunaweza kumsaidia na kumshauri. Na hii ndiyo hasa ninataka kuzungumza juu kwa undani zaidi.

Swali rahisi na mpango nayo

Ili kuonyesha jinsi Postgres anauliza jedwali la safu mlalo milioni 6 kwenye seva ya mbali, hebu tuangalie mpango rahisi.

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

Kwa kutumia kauli ya VERBOSE huturuhusu kuona swali litakalotumwa kwa seva ya mbali na matokeo yake tutapokea kwa usindikaji zaidi (RemoteSQL line).

Hebu tuende mbele kidogo na kuongeza filters kadhaa kwa ombi letu: moja kwa boolean shamba, moja kwa tukio mhuri wa muda kwa muda na moja baada ya nyingine 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

Hapa ndipo hatua ambayo unahitaji kuzingatia wakati wa kuandika maswali iko. Vichujio havikuhamishwa hadi kwa seva ya mbali, ambayo ina maana kwamba ili kuitekeleza, Postgres huchota safu mlalo zote milioni 6 ili kisha kuchuja ndani (Safu mlalo ya Kichujio) na kufanya ujumlisho. Ufunguo wa mafanikio ni kuandika swala ili vichujio vihamishwe kwenye mashine ya mbali, na tunapokea na kukusanya safu muhimu tu.

Huo ni upuuzi fulani

Na mashamba ya boolean kila kitu ni rahisi. Katika ombi la awali, tatizo lilitokana na operator is. Ikiwa utaibadilisha na =, basi tunapata matokeo yafuatayo:

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

Kama unavyoona, kichungi kiliruka hadi kwa seva ya mbali, na wakati wa utekelezaji ulipunguzwa kutoka sekunde 27 hadi 19.

Ni muhimu kuzingatia kwamba operator is tofauti na operator = kwa sababu inaweza kufanya kazi na Null thamani. Ina maana kwamba si Kweli itaacha maadili kuwa ya Uongo na Batili kwenye kichungi, wakati != Kweli itaacha tu maadili ya Uongo. Kwa hiyo, wakati wa kuchukua nafasi ya operator si hali mbili zilizo na Opereta AU zinapaswa kupitishwa kwa kichungi, kwa mfano, WAPI (col != True) AU (col is null).

Tumeshughulika na boolean, wacha tuendelee. Kwa sasa, hebu turudishe kichujio cha Boolean kwa umbo lake asili ili tuzingatie kwa uhuru athari za mabadiliko mengine.

timestamptz? hz

Kwa ujumla, mara nyingi unapaswa kujaribu jinsi ya kuandika kwa usahihi ombi linalohusisha seva za mbali, na kisha utafute maelezo ya kwa nini hii inatokea. Habari ndogo sana kuhusu hili inaweza kupatikana kwenye mtandao. Kwa hivyo, katika majaribio tuligundua kuwa kichujio cha tarehe maalum huruka kwa seva ya mbali kwa mshindo, lakini tunapotaka kuweka tarehe kwa nguvu, kwa mfano, sasa () au CURRENT_DATE, hii haifanyiki. Katika mfano wetu, tuliongeza kichujio ili safu wima ya created_at iwe na data ya mwezi mmoja uliopita (KATI YA CURRENT_DATE - INTERVAL 'miezi 1' NA CURRENT_DATE - INTERVAL 'miezi 7'). Tulifanya nini katika kesi hii?

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

Tulimwambia mpangaji kuhesabu tarehe katika subquery mapema na kupitisha kutofautisha tayari kwa kichungi. Na wazo hili lilitupa matokeo bora, ombi likawa karibu mara 6 haraka!

Tena, ni muhimu kuwa makini hapa: aina ya data katika subquery lazima iwe sawa na ile ya shamba ambalo tunachuja, vinginevyo mpangaji ataamua kwamba kwa kuwa aina ni tofauti, ni muhimu kwanza kupata yote. data na kuichuja ndani ya nchi.

Hebu turudishe kichujio cha tarehe kwa thamani yake ya asili.

Freddy dhidi ya Jsonb

Kwa ujumla, sehemu na tarehe za Boolean tayari zimeharakisha swali letu vya kutosha, lakini kulikuwa na aina moja zaidi ya data iliyosalia. Vita ya kuchuja nayo, kusema ukweli, bado haijaisha, ingawa kuna mafanikio hapa pia. Kwa hivyo, hivi ndivyo tulivyoweza kupitisha kichungi jsonb shamba kwa seva ya mbali.

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

Badala ya kuchuja waendeshaji, lazima utumie uwepo wa operator mmoja jsonb katika tofauti. Sekunde 7 badala ya 29 ya awali. Kufikia sasa hili ndilo chaguo pekee lililofanikiwa kwa kusambaza vichujio kupitia jsonb kwa seva ya mbali, lakini hapa ni muhimu kuzingatia kizuizi kimoja: tunatumia toleo la 9.6 la hifadhidata, lakini mwishoni mwa Aprili tunapanga kukamilisha majaribio ya mwisho na kuhamia toleo la 12. Mara tu tunaposasisha, tutaandika kuhusu jinsi ilivyoathiri, kwa sababu kuna mabadiliko mengi sana ambayo kuna matumaini mengi kwayo: json_path, tabia mpya ya CTE, sukuma chini (iliyopo tangu toleo la 10). Kwa kweli nataka kuijaribu hivi karibuni.

Maliza yeye

Tulijaribu jinsi kila badiliko lilivyoathiri kasi ya ombi kibinafsi. Hebu sasa tuone kinachotokea wakati vichujio vyote vitatu vimeandikwa kwa usahihi.

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

Ndiyo, ombi inaonekana kuwa ngumu zaidi, hii ni ada ya kulazimishwa, lakini kasi ya utekelezaji ni sekunde 2, ambayo ni zaidi ya mara 10 kwa kasi! Na tunazungumza juu ya swali rahisi dhidi ya seti ndogo ya data. Kwa maombi halisi, tulipokea ongezeko la hadi mara mia kadhaa.

Kwa muhtasari: ikiwa unatumia PostgreSQL na FDW, hakikisha kuwa vichujio vyote vimetumwa kwa seva ya mbali, na utafurahi... Angalau hadi upate viungo kati ya jedwali kutoka kwa seva tofauti. Lakini hiyo ni hadithi kwa makala nyingine.

Asante kwa umakini wako! Ningependa kusikia maswali, maoni, na hadithi kuhusu uzoefu wako katika maoni.

Chanzo: mapenzi.com

Kuongeza maoni