Happy Party o alcune righe di ricordi sulla suddivisione in PostgreSQL 10

Prefazione o come è nata l'idea della partizionamento

L'inizio della storia qui: Ricordi come tutto è iniziato. Era tutto nuovo e inedito. Dopo aver esaurito quasi tutte le risorse per ottimizzare la query, a quel punto si è posto il problema: e adesso cosa facciamo? Così è nata l'idea del partizionamento.

Happy Party o alcune righe di ricordi sulla suddivisione in PostgreSQL 10

Una digressione lirica:
Proprio 'a quel punto', perché come si è scoperto, c'erano riserve di ottimizzazione non sfruttate. Grazie asmm e a Habr!

Quindi, come possiamo rendere il cliente in qualche modo felice e al contempo migliorare le proprie competenze?

Se semplifichiamo al massimo, ci sono fondamentalmente solo due modi per migliorare radicalmente le prestazioni del database:
1) Approccio estensivo - aumentiamo le risorse, cambiamo la configurazione;
2) Approccio intensivo - ottimizzazione delle query

Poiché, ripeto, a quel punto non era più chiaro cosa modificare nella query per accelerare, si è scelto di cambiare il design delle tabelle.

Dunque, sorge la domanda principale: cosa e come cambieremo?

Condizioni iniziali

Innanzitutto, abbiamo il seguente ERD (mostrato in modo schematico):
Happy Party o alcune righe di ricordi sulla suddivisione in PostgreSQL 10
Caratteristiche principali:

  1. relazioni 'molti a molti'
  2. la tabella ha già una potenziale chiave di partizionamento

Query iniziale:

SELECT
            p."PARAMETER_ID" as  parameter_id,
            pc."PC_NAME" AS pc_name,
            pc."CUSTOMER_PARTNUMBER" AS customer_partnumber,
            w."LASERMARK" AS lasermark,
            w."LOTID" AS lotid,
            w."REPORTED_VALUE" AS reported_value,
            w."LOWER_SPEC_LIMIT" AS lower_spec_limit,
            w."UPPER_SPEC_LIMIT" AS upper_spec_limit,
            p."TYPE_CALCUL" AS type_calcul,
            s."SHIPMENT_NAME" AS shipment_name,
            s."SHIPMENT_DATE" AS shipment_date,
            extract(year from s."SHIPMENT_DATE") AS year,
            extract(month from s."SHIPMENT_DATE") as month,
            s."REPORT_NAME" AS report_name,
            p."SPARAM_NAME" AS SPARAM_name,
            p."CUSTOMERPARAM_NAME" AS customerparam_name
        FROM data w INNER JOIN shipment s ON s."SHIPMENT_ID" = w."SHIPMENT_ID"
             INNER JOIN parameters p ON p."PARAMETER_ID" = w."PARAMETER_ID"
             INNER JOIN shipment_pc sp ON s."SHIPMENT_ID" = sp."SHIPMENT_ID"
             INNER JOIN pc pc ON pc."PC_ID" = sp."PC_ID"
             INNER JOIN ( SELECT w2."LASERMARK" , MAX(s2."SHIPMENT_DATE") AS "SHIPMENT_DATE"
                          FROM shipment s2 INNER JOIN data w2 ON s2."SHIPMENT_ID" = w2."SHIPMENT_ID" 
                          GROUP BY w2."LASERMARK"
                         ) md ON md."SHIPMENT_DATE" = s."SHIPMENT_DATE" AND md."LASERMARK" = w."LASERMARK"
        WHERE 
             s."SHIPMENT_DATE" >= '2018-07-01' AND s."SHIPMENT_DATE" <= '2018-09-30' ;

Risultati dell'esecuzione sul database di test:
Costo : 502 997.55
Tempo di esecuzione: 505 secondi.

Cosa vediamo? È una query normale, su un intervallo temporale.
Facciamo un semplice presupposto logico: se c'è un campione di intervallo temporale, ci aiuterà? Esatto - il partizionamento.

Cosa partizionare?

A prima vista, la scelta è ovvia - partizionamento dichiarativo della tabella 'shipment' per chiave 'SHIPMENT_DATE' (anticipando un po' - alla fine in produzione è andata un po' diversamente).

Come partizionare?

Questo quesito non è neanche troppo complesso. Fortunatamente, in PostgreSQL 10, ora c'è un meccanismo di partizionamento accessibile.
Quindi:

  1. Salviamo il dump della tabella originale - pg_dump source_table
  2. Eliminiamo la tabella originale - drop table source_table
  3. Creiamo la tabella principale con partizionamento per intervallo - create table source_table
  4. Creiamo le sezioni - create table source_table, create index
  5. Importiamo il dump creato al passo 1 - pg_restore

Script per il partizionamento

Per semplicità e comodità, i passaggi 2, 3, 4 sono stati uniti in un unico script.

Quindi:
Salviamo il dump della tabella originale

pg_dump postgres --file=/dump/shipment.dmp --format=c --table=shipment --verbose > /dump/shipment.log 2>&1

Eliminiamo la tabella originale + Creiamo la tabella principale con partizionamento per intervallo + Creiamo le sezioni

--create_partition_shipment.sql
do language plpgsql $$
declare 
rec_shipment_date RECORD ;
partition_name varchar;
index_name varchar;
current_year varchar ;
current_month varchar ;
begin_year varchar ;
begin_month varchar ;
next_year varchar ;
next_month varchar ;
first_flag boolean ;
i integer ;
begin
  RAISE NOTICE 'CREA TABELLA TEMPORANEA PER SHIPMENT_DATE';
  CREATE TEMP TABLE tmp_shipment_date as select distinct "SHIPMENT_DATE" from shipment order by "SHIPMENT_DATE" ;

  RAISE NOTICE 'DROP TABLE shipment';
  drop table shipment cascade ;
  
  CREATE TABLE public.shipment
  (
    "SHIPMENT_ID" integer NOT NULL DEFAULT nextval('shipment_shipment_id_seq'::regclass),
    "SHIPMENT_NAME" character varying(30) COLLATE pg_catalog."default",
    "SHIPMENT_DATE" timestamp without time zone,
    "REPORT_NAME" character varying(40) COLLATE pg_catalog."default"
  )
  PARTITION BY RANGE ("SHIPMENT_DATE")
  WITH (
      OIDS = FALSE
  )
  TABLESPACE pg_default;

  RAISE NOTICE 'CREA PARTIZIONI PER LA TABELLA shipment';

  current_year:='0';
  current_month:='0';

  begin_year := '0' ;
  begin_month := '0'  ;
  next_year := '0' ;
  next_month := '0'  ;

  FOR rec_shipment_date IN SELECT * FROM tmp_shipment_date LOOP
      
      RAISE NOTICE 'SHIPMENT_DATE=%',rec_shipment_date."SHIPMENT_DATE";
      
      current_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE");
      current_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE") ; 

      IF to_number(current_month,'99') = to_date( begin_year||'.'||begin_month, 'YYYY.MM') AND 
         to_date( current_year||'.'||current_month, 'YYYY.MM') < to_date( next_year||'.'||next_month, 'YYYY.MM') AND 
         NOT first_flag 
      THEN
         CONTINUE ; 
      ELSE
       --NUOVI confini solo per la seconda e le volte successive 
       begin_year := current_year ;
       begin_month := current_month ;   
   
        IF current_month = '12' THEN
          next_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 year') ;
        ELSE
          next_year := current_year ;
        END IF;
     
       next_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 month') ;

      END IF;      

      partition_name := 'shipment_shipment_date_'||begin_year||'-'||begin_month||'-01-'|| next_year||'-'||next_month||'-01'  ;
 
     EXECUTE format('CREATE TABLE ' || quote_ident(partition_name) || ' PARTITION OF shipment FOR VALUES FROM ( %L ) TO ( %L )  ' , current_year||'-'||current_month||'-01' , next_year||'-'||next_month||'-01'  ) ; 

      index_name := partition_name||'_shipment_id_idx';
      RAISE NOTICE 'NOME INDICE =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID") TABLESPACE pg_default ' ) ; 

      --Drop first time flag
      first_flag := false ;
   
  END LOOP;

end
$$;

Importiamo il dump

pg_restore -d postgres --data-only --format=c --table=shipment --verbose  shipment.dmp > /tmp/data_dump/shipment_restore.log 2>&1

Controlliamo i risultati della partizione

Cosa abbiamo come risultato? Il testo completo del piano di esecuzione è lungo e noioso, quindi è possibile limitarsi ai numeri finali.

C'era

Costo: 502 997.55
Tempo di esecuzione: 505 secondi.

Diventato

Costo: 77 872.36
Tempo di esecuzione: 79 secondi.

Un ottimo risultato. Abbiamo ridotto i costi e il tempo di esecuzione. In questo modo, l'utilizzo della partizione dà l'effetto atteso e, in generale, senza sorprese.

Soddisfare il cliente

I risultati del test sono stati presentati al cliente per la revisione. E dopo averli esaminati, è stata emessa una sentenza piuttosto inaspettata: «Ottimo, partizionate la tabella «data»».

Sì, ma stiamo analizzando un'altra tabella, «shipment», la tabella «data» non ha il campo «SHIPMENT_DATE».

Nessun problema, aggiungete, cambiate. L'importante è che il cliente sia soddisfatto del risultato, i dettagli dell'implementazione non sono particolarmente importanti.

Partizioniamo la tabella principale «data»

In effetti, non ci sono state particolari complessità. Tuttavia, l'algoritmo di partizione è cambiato un po'.

Aggiungiamo la colonna «SHIPMENT_DATE» nella tabella «data»

psql -h host -U database -d user
=> ALTER TABLE data ADD COLUMN "SHIPMENT_DATE" timestamp without time zone ;

Compiliamo i valori della colonna «SHIPMENT_DATE» nella tabella «data» con i valori della colonna omonima nella tabella «shipment»

-----------------------------
--update_data.sql
--aggiornamento per la tabella "data" modificata ai valori di "shipment_data" dalla tabella "shipment"
--version 1.0
do language plpgsql $$
declare 
rec_shipment_data RECORD ;
shipment_date timestamp without time zone ; 
row_count integer ;
total_rows integer ;
begin

  select count(*) into total_rows from shipment ; 
  RAISE NOTICE 'Totale %',total_rows;
  row_count:= 0 ;

  FOR rec_shipment_data IN SELECT * FROM shipment LOOP

   update data set "SHIPMENT_DATE" = rec_shipment_data."SHIPMENT_DATE" where "SHIPMENT_ID" = rec_shipment_data."SHIPMENT_ID";
   
   row_count:=  row_count +1 ;
   RAISE NOTICE 'conteggio righe = % , da %',row_count,total_rows;
  END LOOP;

end
$$;

Salviamo il dump della tabella «data»

pg_dump postgres --file=/dump/data.dmp --format=c --table=data --verbose > /dump/data.log 2>&1<source

Ricreiamo la tabella partizionata «data»

--create_partition_data.sql
--crea partizioni per la tabella "wafer data" per intervallo nella colonna "shipment_data" con una durata di un mese
--versione 1.0
do language plpgsql $$
declare 
rec_shipment_date RECORD ;
partition_name varchar;
index_name varchar;
current_year varchar ;
current_month varchar ;
begin_year varchar ;
begin_month varchar ;
next_year varchar ;
next_month varchar ;
first_flag boolean ;
i integer ;

begin

  RAISE NOTICE 'CREA TABELLA TEMPORANEA PER SHIPMENT_DATE';
  CREATE TEMP TABLE tmp_shipment_date as select distinct "SHIPMENT_DATE" from shipment order by "SHIPMENT_DATE" ;


  RAISE NOTICE 'CANCELLA TABELLA data';
  drop table data cascade ;


  RAISE NOTICE 'CREA TABELLA PARTIZIONATA data';
  
  CREATE TABLE public.data
  (
    "RUN_ID" integer,
    "LASERMARK" character varying(20) COLLATE pg_catalog."default" NOT NULL,
    "LOTID" character varying(80) COLLATE pg_catalog."default",
    "SHIPMENT_ID" integer NOT NULL,
    "PARAMETER_ID" integer NOT NULL,
    "INTERNAL_VALUE" character varying(75) COLLATE pg_catalog."default",
    "REPORTED_VALUE" character varying(75) COLLATE pg_catalog."default",
    "LOWER_SPEC_LIMIT" numeric,
    "UPPER_SPEC_LIMIT" numeric , 
    "SHIPMENT_DATE" timestamp without time zone
  )
  PARTITION BY RANGE ("SHIPMENT_DATE")
  WITH (
    OIDS = FALSE
  )
  TABLESPACE pg_default ;


  RAISE NOTICE 'CREA PARTIZIONI PER LA TABELLA data';

  current_year:='0';
  current_month:='0';

  begin_year := '0' ;
  begin_month := '0'  ;
  next_year := '0' ;
  next_month := '0'  ;
  i := 1;

  FOR rec_shipment_date IN SELECT * FROM tmp_shipment_date LOOP
      
      RAISE NOTICE 'SHIPMENT_DATE=%',rec_shipment_date."SHIPMENT_DATE";
      
      current_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE");
      current_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE") ; 

      --Inizializza i confini
      IF   begin_year = '0' THEN
       RAISE NOTICE '***Inizializza confini';
       first_flag := true ; --flag primo giro
       begin_year := current_year ;
       begin_month := current_month ;   
   
        IF current_month = '12' THEN
          next_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 year') ;
        ELSE
          next_year := current_year ;
        END IF;
     
       next_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 month') ;

      END IF;

--      RAISE NOTICE 'current_year=% , current_month=% ',current_year,current_month;
--      RAISE NOTICE 'begin_year=% , begin_month=% ',begin_year,begin_month;
--      RAISE NOTICE 'next_year=% , next_month=% ',next_year,next_month;

      -- Controlla la data corrente nei confini NON per il primo giro

      RAISE NOTICE 'Dati correnti = %',to_char( to_date( current_year||'.'||current_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Dati iniziali = %',to_char( to_date( begin_year||'.'||begin_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Prossimi dati = %',to_char( to_date( next_year||'.'||next_month, 'YYYY.MM'), 'YYYY.MM');

      IF to_date( current_year||'.'||current_month, 'YYYY.MM') >= to_date( begin_year||'.'||begin_month, 'YYYY.MM') AND 
         to_date( current_year||'.'||current_month, 'YYYY.MM') < to_date( next_year||'.'||next_month, 'YYYY.MM') AND 
         NOT first_flag 
      THEN
         RAISE NOTICE '***CONTINUA';
         CONTINUE ; 
      ELSE
       --NUOVI confini solo per secondi e successivi giri 
       RAISE NOTICE '***NUOVI CONFINI';
       begin_year := current_year ;
       begin_month := current_month ;   
   
        IF current_month = '12' THEN
          next_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 year') ;
        ELSE
          next_year := current_year ;
        END IF;
     
       next_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 month') ;


      END IF;      

      IF to_number(current_month,'99') < 10 THEN
        current_month := '0'||current_month ; 
      END IF ;

      IF to_number(begin_month,'99') < 10 THEN
        begin_month := '0'||begin_month ; 
      END IF ;

      IF to_number(next_month,'99') < 10 THEN
        next_month := '0'||next_month ; 
      END IF ;

      RAISE NOTICE 'current_year=% , current_month=% ',current_year,current_month;
      RAISE NOTICE 'begin_year=% , begin_month=% ',begin_year,begin_month;
      RAISE NOTICE 'next_year=% , next_month=% ',next_year,next_month;

      partition_name := 'data_'||begin_year||begin_month||'01_'||next_year||next_month||'01'  ;

      RAISE NOTICE 'NUMERO PARTIZIONE % , NOME TABELLA =%',i , partition_name;
      
      EXECUTE format('CREATE TABLE ' || quote_ident(partition_name) || ' PARTITION OF data FOR VALUES FROM ( %L ) TO ( %L )  ' , begin_year||'-'||begin_month||'-01' , next_year||'-'||next_month||'-01'  ) ; 

      index_name := partition_name||'_shipment_id_parameter_id_idx';
      RAISE NOTICE 'NOME INDICE =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID", "PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_lasermark_idx';
      RAISE NOTICE 'NOME INDICE =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("LASERMARK" COLLATE pg_catalog."default") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_id_idx';
      RAISE NOTICE 'NOME INDICE =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_parameter_id_idx';
      RAISE NOTICE 'NOME INDICE =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_date_idx';
      RAISE NOTICE 'NOME INDICE =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_DATE") TABLESPACE pg_default ' ) ; 

      --Cancella flag primo giro
      first_flag := false ;

  END LOOP;
end
$$;

Caricando il dump creato al passo 3.

pg_restore -h host -u user -d database --data-only --format=c --table=data --verbose data.dmp > data_restore.log 2>&1

Creiamo una sezione separata per i dati vecchi

---------------------------------------------------
--create_partition_for_old_dates.sql
--crea partizioni per mantenere date vecchie 
--versione 1.0
do language plpgsql $$
declare 
rec_shipment_date RECORD ;
partition_name varchar;
index_name varchar;

begin

      SELECT min("SHIPMENT_DATE") AS min_date INTO rec_shipment_date from data ;

      RAISE NOTICE 'La data vecchia è %',rec_shipment_date.min_date ;

      partition_name := 'data_old_dates'  ;

      RAISE NOTICE 'IL NOME DELLA PARTIZIONE È %',partition_name;

      EXECUTE format('CREATE TABLE ' || quote_ident(partition_name) || ' PARTITION OF data FOR VALUES FROM ( %L ) TO ( %L )  ' , '1900-01-01' , 
              to_char( rec_shipment_date.min_date,'YYYY')||'-'||to_char(rec_shipment_date.min_date,'MM')||'-01'  ) ; 

      index_name := partition_name||'_shipment_id_parameter_id_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID", "PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_lasermark_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("LASERMARK" COLLATE pg_catalog."default") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_id_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_parameter_id_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_date_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_DATE") TABLESPACE pg_default ' ) ; 

end
$$;

Risultati finali:

C'era
Costo: 502 997.55
Tempo di esecuzione: 505 secondi.

Diventato
Costo: 68 533.70
Tempo di esecuzione: 69 secondi

Notevole, davvero notevole. E considerando che lungo il percorso ho potuto più o meno apprendere il meccanismo di partizionamento in PostgreSQL 10 — Un ottimo risultato.

Divagazione lirica

E si può fare anche meglio — SÌ, SI PUÒ!Per fare ciò, è necessario utilizzare una VIEW MATERIALIZZATA.
CREATE MATERIALIZED VIEW LASERMARK_VIEW

CREATE MATERIALIZED VIEW LASERMARK_VIEW 
AS
SELECT w."LASERMARK" , MAX(s."SHIPMENT_DATE") AS "SHIPMENT_DATE"
FROM shipment s INNER JOIN data w ON s."SHIPMENT_ID" = w."SHIPMENT_ID" 
GROUP BY w."LASERMARK" ;

CREATE INDEX lasermark_vw_shipment_date_ind on lasermark_view USING btree ("SHIPMENT_DATE") TABLESPACE pg_default;
analyze lasermark_view ;

Ancora una volta riscriviamo la query:
Query con utilizzo della view materializzata

SELECT
            p."PARAMETER_ID" as  parameter_id,
            pc."PC_NAME" AS pc_name,
            pc."CUSTOMER_PARTNUMBER" AS customer_partnumber,
            w."LASERMARK" AS lasermark,
            w."LOTID" AS lotid,
            w."REPORTED_VALUE" AS reported_value,
            w."LOWER_SPEC_LIMIT" AS lower_spec_limit,
            w."UPPER_SPEC_LIMIT" AS upper_spec_limit,
            p."TYPE_CALCUL" AS type_calcul,
            s."SHIPMENT_NAME" AS shipment_name,
            s."SHIPMENT_DATE" AS shipment_date,
            extract(year from s."SHIPMENT_DATE") AS year,
            extract(month from s."SHIPMENT_DATE") as month,
            s."REPORT_NAME" AS report_name,
            p."STC_NAME" AS STC_name,
            p."CUSTOMERPARAM_NAME" AS customerparam_name
        FROM data w INNER JOIN shipment s ON s."SHIPMENT_ID" = w."SHIPMENT_ID"
             INNER JOIN parameters p ON p."PARAMETER_ID" = w."PARAMETER_ID"
             INNER JOIN shipment_pc sp ON s."SHIPMENT_ID" = sp."SHIPMENT_ID"
             INNER JOIN pc pc ON pc."PC_ID" = sp."PC_ID"
             INNER JOIN LASERMARK_VIEW md ON md."SHIPMENT_DATE" = s."SHIPMENT_DATE" AND md."LASERMARK" = w."LASERMARK"
        WHERE 
              s."SHIPMENT_DATE" >= '2018-07-01' AND s."SHIPMENT_DATE" <= '2018-09-30';

E otteniamo un altro risultato:
C'era
Costo: 502 997.55
Tempo di esecuzione: 505 secondi

Diventato
Costo: 42 481.16
Tempo di esecuzione: 43 secondi.

Anche se, naturalmente, un risultato così promettente è ingannevole, le view vanno rinfrescate. Quindi, il tempo finale per ottenere i dati non aiuterà molto. Ma come esperimento è piuttosto interessante.

In realtà, come si è scoperto, un altro grazie asmm e a Habr !- la query può essere ancora migliorata.

Epifania

Quindi, il cliente è soddisfatto. E bisogno sfruttare la situazione.

Nuova attività: Cosa si può inventare per approfondire e ampliare?

E qui mi ricordo — ragazzi, ma noi non abbiamo un monitoraggio dei nostri database PostgreSQL.

A dire il vero, c'è un certo tipo di monitoraggio in Cloud Watch su AWS. Ma quale utilità ha questo monitoraggio per il DBA? Fondamentalmente, nessuna.

Se c'è l'opportunità di fare qualcosa di utile e interessante anche per me, non posso lasciarmela sfuggire …
INFATTI

Happy Party o alcune righe di ricordi sulla suddivisione in PostgreSQL 10

Così siamo arrivati al punto più interessante:

3 Dicembre 2018.
Decisione di iniziare i lavori di ricerca per esplorare le possibilità di monitoraggio delle prestazioni delle query PostgreSQL.

Ma questa è una storia completamente diversa.

Continua…

Fonte: habr.com

Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster