Happy Party o una coppia di righe di ricordi sulla conoscenza del partizionamento in PostgreSQL10

Introduzione o come è nata l'idea della suddivisione

L'inizio della storia è qui: Ricordi come è iniziato tutto? Era tutto la prima volta e di nuovo. Dopo che quasi tutte le risorse per l'ottimizzazione della query erano state esaurite, si è posto il problema: e adesso cosa? È così che è nata l'idea della suddivisione.

Happy Party o una coppia di righe di ricordi sulla conoscenza del partizionamento in PostgreSQL10

Una digressione lirica:
Proprio 'a quel tempo', perché come si è scoperto, c'erano riserve inutilizzate per l'ottimizzazione. Grazie asmm e Habr!

Quindi, come si può rendere il cliente un po' felice e allo stesso tempo migliorare le proprie competenze?

Se dobbiamo semplificare al massimo, ci sono solo due modi significativi per migliorare le prestazioni del database:
1) Percorso estensivo — aumentiamo le risorse, cambiamo la configurazione;
2) Percorso intensivo — ottimizzazione delle query

Poiché, ripeto, a quel tempo non era chiaro cosa potessimo ancora cambiare nella query per velocizzarla, è stata scelta la via — modifiche al design delle tabelle.

Quindi — sorge la domanda principale: cosa e come cambieremo?

Condizioni iniziali

Prima di tutto, c'è un ERD come questo (mostrato in modo semplificato):
Happy Party o una coppia di righe di ricordi sulla conoscenza del partizionamento in PostgreSQL10
Caratteristiche principali:

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

Query di origine:

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 su un database di test:
Costo : 502 997.55
Tempo di esecuzione: 505 secondi.

Cosa vediamo? Una query normale, per intervallo di tempo.
Facciamo una semplice ipotesi logica: se c'è un campione di intervallo temporale, ci aiuterà? Giusto — la partizionamento.

Cosa partizionare?

A prima vista, la scelta è ovvia — la partizione dichiarativa della tabella «shipment» in base alla chiave «SHIPMENT_DATE» (anticipando molto — alla fine in produzione è andata un po' diversamente).

Come partizionare?

Anche questa domanda non è troppo difficile. Fortunatamente, in PostgreSQL 10, ora c'è un meccanismo di partizionamento umano.
Ecco:

  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 partizioni — create table source_table, create index
  5. Importiamo il dump creato nel passo 1 — pg_restore

Script per il partizionamento

Per semplicità e comodità, i passi 2, 3, 4 sono stati uniti in uno script.

Ecco:
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 partizioni

--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 UNA TABELLA TEMPORANEA PER SHIPMENT_DATE';
  CREATE TEMP TABLE tmp_shipment_date as select distinct "SHIPMENT_DATE" from shipment order by "SHIPMENT_DATE" ;

  RAISE NOTICE 'ELIMINA LA TABELLA 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 volta e oltre 
       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 ' ) ; 

      --Elimina il flag prima volta
      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 partizionatura

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

Era

Costo: 502 997.55
Tempo di esecuzione: 505 secondi.

Diventato

Costo: 77 872.36
Tempo di esecuzione: 79 secondi.

Un risultato piuttosto buono. Abbiamo ridotto i costi e il tempo di esecuzione. Pertanto, l'uso della partizionamento dà l'effetto atteso e, in generale, nessuna sorpresa.

Compiacere il cliente

I risultati dei test sono stati presentati al cliente per la revisione. E dopo la consultazione gli è stato dato un verdetto piuttosto inaspettato: «Ottimo, partiziona la tabella 'data'».

Sì, ma abbiamo esaminato una tabella completamente diversa, 'shipment', la tabella 'data' non ha il campo 'SHIPMENT_DATE'.

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

Partizioniamo la tabella principale 'data'

In realtà non ci sono state particolari difficoltà. Anche se, ovviamente, l'algoritmo di partizionamento è cambiato un po'.

Aggiungiamo la colonna 'SHIPMENT_DATA' 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_DATA' nella tabella 'data' con i valori della colonna omonima della tabella 'shipment'

-----------------------------
--update_data.sql
--aggiornamento per la tabella "data" alterata con i valori di "shipment_data" dalla tabella "shipment"
--versione 1.0
do language plpgsql $$
dichiarare 
rec_shipment_data RECORD ;
shipment_date timestamp without time zone ; 
row_count integer ;
total_rows integer ;
inizio

  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;

fine
$$;

Salviamo il dump della tabella 'data'

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

Ricreiamo la tabella partizionata 'data'

--create_partition_data.sql
--crea partizioni per la tabella "wafer data" per intervallo della 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 'DROP TABLE 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 i confini';
       first_flag := true ; --flag prima volta
       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 la prima volta

      RAISE NOTICE 'Dati correnti = %',to_char( to_date( current_year||'.'||current_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Dati di inizio = %',to_char( to_date( begin_year||'.'||begin_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Dati successivi = %',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 la seconda volta e oltre 
       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('CREA TABELLA ' || quote_ident(partition_name) || ' PARTIZIONE di data PER VALORI DA ( %L ) A ( %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('CREA INDICE ' || 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('CREA INDICE ' || 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('CREA INDICE ' || 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('CREA INDICE ' || 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('CREA INDICE ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_DATE") TABLESPACE pg_default ' ) ; 

      --Rimuovi flag prima volta
      first_flag := false ;

  END LOOP;
end
$$;

Stiamo caricando il dump creato al passo 3.

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

Creiamo una sezione separata per i dati più vecchi

---------------------------------------------------
--create_partition_for_old_dates.sql
--creare partizioni per mantenere le date più 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 più 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:

Era
Costo: 502 997.55
Tempo di esecuzione: 505 secondi.

Diventato
Costo: 68 533.70
Tempo di esecuzione: 69 secondi

Meritevole, decisamente meritevole. E considerando che lungo il cammino si è riusciti a comprendere il meccanismo di partizionamento in PostgreSQL 10 — Ottimo risultato.

Divagazione lirica

E si può fare ancora meglio — SÌ, SI PUÒ!Per questo è necessario utilizzare una MATERIALIZED VIEW.
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 utilizzando la materialized view

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:
Era
Costo: 502 997.55
Tempo di esecuzione: 505 secondi

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

Sebbene, naturalmente, un risultato così promettente sia ingannevole, bisogna aggiornare le presentazioni. Quindi il tempo totale per l'ottenimento dei dati non sarà di grande aiuto. Ma come esperimento è piuttosto interessante.

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

Postfazione

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

Nuovo compito: Cosa si può pensare per approfondire ed espandere?

E qui mi viene in mente — ragazzi, ma non abbiamo il monitoraggio dei nostri database PostgreSQL.

A dire il vero, c'è una certa forma di monitoraggio tramite Cloud Watch su AWS. Ma quale utilità ha questo monitoraggio per un DBA? In effetti, praticamente nessuna.

Se c'è l'opportunità di fare qualcosa di utile e interessante anche per se stessi, non si può perdere tale occasione …
PERCHÉ

Happy Party o una coppia di righe di ricordi sulla conoscenza del partizionamento in PostgreSQL10

Così siamo arrivati al punto più interessante:

3 Dicembre 2018.
Decisione di iniziare i lavori per l'analisi delle possibilità di monitoraggio delle prestazioni delle query PostgreSQL.

Ma questa è già un'altra storia.

Continua, a seguire …

Fonte: habr.com

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