Happy Party или няколко реда спомени за запознанството с секционирането в PostgreSQL10

Предисловие или как се роди идеята за секциониране

Началото на историята е тук: Помниш ли как всичко започна. Всичко беше за първи път и отново. След като почти всички ресурси за оптимизация на заявката, по онова време, бяха изчерпани, възникна въпросът — какво да правим нататък? Тук се роди идеята за секциониране.

Happy Party или няколко реда спомени за запознанството с секционирането в PostgreSQL10

Личен отклонение:
Наистина 'по онова време', защото както се оказа, имаше неизползвани резерви за оптимизация. Благодаря asmm и Хабру!

И така, как още можем да направим клиента да се чувства щастлив, а и да развием собствените си умения?

Ако всичко бъде максимално опростено, то има само два кардинални начина да подобрим работата на базата данни:
1) Екстензивен подход — увеличаваме ресурсите, променяме конфигурацията;
2) Интензивен подход — оптимизация на заявките

Тъй като, повтарям, по онова време вече не беше ясно какво още да променя в заявката за ускорение, беше избран вариантът — промяна на дизайна на таблиците.

И така — възниква основния въпрос — какво и как ще променим?

Начални условия

На първо място, имаме следната ERD (показано опростено):
Happy Party или няколко реда спомени за запознанството с секционирането в PostgreSQL10
Основни характеристики:

  1. отношения 'много към много'
  2. таблицата вече има потенциален ключ за секциониране

Исходна заявка:

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';

Резултати от изпълнението на тестовата база данни:
Разходи : 502 997.55
Време на изпълнение: 505 секунди.

Какво виждаме? Обичайна заявка, по времеви отрязък.
Правим просто логично предположение: ако имаме проба от времеви отрез, то ще ни помогне? Правилно — секциониране.

Какво да секционираме?

На пръв поглед изборът е очевиден — декларативно секциониране на таблицата «shipment» по ключа «SHIPMENT_DATE» (предвиждайки доста напред — на края в продукция не се получи точно така).

Как да секционираме?

Този въпрос също не е особено сложен. За щастие, в PostgreSQL 10, сега има разбираем механизъм за секциониране.
И така:

  1. Запазваме дапа на изходната таблица — pg_dump source_table
  2. Изтриваме изходната таблица — drop table source_table
  3. Създаваме родителска таблица с секциониране по диапазон — create table source_table
  4. Създаваме секции — create table source_table, create index
  5. Импортираме дапа, създаден на стъпка 1 — pg_restore

Скриптове за секциониране

За простота и удобство, стъпки 2, 3, 4 бяха обединени в един скрипт.

И така:
Запазваме дапа на изходната таблица

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

Изтриваме изходната таблица + Създаваме родителска таблица с секциониране по диапазон + Създаваме секции

--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 'CREATE TEMPORARY TABLE FOR 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 'CREATE PARTITIONS FOR TABLE 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
       --NEW borders only for second and after time 
       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 'INDEX NAME =%',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
$$;

Импорт на дамп

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

Проверка на резултатите от секционирането

Какво имаме в крайна сметка? Пълният текст на плана е дълъг и скучен, така че е напълно приемливо да се ограничим до крайния резултат.

Имаше

Цена: 502 997.55
Време за изпълнение: 505 секунди.

Стана

Цена: 77 872.36
Време за изпълнение: 79 секунди.

Отличен резултат. Намалихме разходите и времето за изпълнение. Така че използването на секциониране дава очаквания ефект, без изненади.

Да зарадваме клиента

Резултатите от тестовете бяха представени на клиента за преглед. След като се запозна с тях, той издаде доста неочаквана присъда: „Отлично, секционирайте таблицата „data“.”

Да, но ние проучвахме съвсем друга таблица „shipment“, таблицата „data“ няма поле „SHIPMENT_DATE“.

Няма проблем, добавяйте, променяйте. Основното е да е удобно на клиента с крайния резултат, подробностите по реализацията не са толкова важни.

Секционираме основната таблица „data“

В общи линии не възникнаха особени затруднения. Въпреки че, алгоритъмът за секциониране, разбира се, леко се промени.

Добавяме колоната „SHIPMENT_DATA“ в таблицата „data“

psql -h хост -U база -d юзер
=> ALTER TABLE data ADD COLUMN "SHIPMENT_DATE" timestamp without time zone ;

Запълваме стойностите в колоната „SHIPMENT_DATA“ в таблицата „data“ със стойности от същата колона в таблицата „shipment“

-----------------------------
--update_data.sql
--обновяване на изменената таблица "data" със стойности от "shipment_data" от таблицата "shipment"
--версия 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 'Общо %',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 'броене на редове = % , от %',row_count,total_rows;
  END LOOP;

end
$$;

Запазваме дампа на таблицата „data“

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

Пресъздаваме секционираната таблица „data“

--create_partition_data.sql
--създаване на партиции за таблицата "wafer data" по диапазона на колоната "shipment_data" с продължителност един месец
--версия 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 'СЪЗДАВАНЕ НА ВРЕМЕННА ТАБЛИЦА ЗА SHIPMENT_DATE';
  CREATE TEMP TABLE tmp_shipment_date as select distinct "SHIPMENT_DATE" from shipment order by "SHIPMENT_DATE" ;


  RAISE NOTICE 'ИЗТРИВАНЕ НА ТАБЛИЦА data';
  drop table data cascade ;


  RAISE NOTICE 'СЪЗДАВАНЕ НА ПАРТИЦИРОВАНА ТАБЛИЦА 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 'СЪЗДАВАНЕ НА ПАРТИЦИИ ЗА ТАБЛИЦА 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") ; 

      --Инициализация на границите
      IF   begin_year = '0' THEN
       RAISE NOTICE '***Инициализация на границите';
       first_flag := true ; --флаг за първи път
       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;

      -- Проверка на текущата дата в границите НЕ за първи път

      RAISE NOTICE 'Текущи данни = %',to_char( to_date( current_year||'.'||current_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Начални данни = %',to_char( to_date( begin_year||'.'||begin_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Следващи данни = %',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 '***ПРОДЪЛЖАВАНЕ';
         CONTINUE ; 
      ELSE
       --НОВИ граници само за втори и последващи пъти 
       RAISE NOTICE '***НОВИ ГРАНИЦИ';
       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 'НОМЕР НА ПАРТИЦИЯ % , ИМЕ НА ТАБЛИЦАТА =%',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 'ИМЕ НА ИНДЕКС =%',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 'ИМЕ НА ИНДЕКС =%',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 'ИМЕ НА ИНДЕКС =%',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 'ИМЕ НА ИНДЕКС =%',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 'ИМЕ НА ИНДЕКС =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_DATE") TABLESPACE pg_default ' ) ; 

      --Премахване на флага за първи път
      first_flag := false ;

  END LOOP;
end
$$;

Зареждане на дампа, създаден в стъпка 3.

pg_restore -h хост -юзер -d база --data-only --format=c --table=data --verbose data.dmp > data_restore.log 2>&1

Създаваме отделна секция за стари данни.

---------------------------------------------------
--create_partition_for_old_dates.sql
--създаване на партиции за съхранение на стари дати 
--версія 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 'Стара дата е %',rec_shipment_date.min_date ;

      partition_name := 'data_old_dates' ;

      RAISE NOTICE 'ИМЕ НА ПАРТИЦИЯТА Е %',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
$$;

Итогови резултати:

Имаше
Цена: 502 997.55
Време на изпълнение: 505 секунди.

Стана
Цена: 68 533.70
Време за изпълнение: 69 секунди

Достойно, напълно достойно. Имайки предвид, че по пътя успяхме да навлезем в механизма за секциониране в PostgreSQL 10 — Отличен резултат.

Личен бенефис

А можем ли да направим още по-добре — ДА, МОЖЕМ!За това е необходимо да се използва 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 ;

Отново пренаписваме запитването:
Запитване с използване на materialized view

ИЗБЕРИ
            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
        ОТ data w ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ shipment s ON s."SHIPMENT_ID" = w."SHIPMENT_ID"
             ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ parameters p ON p."PARAMETER_ID" = w."PARAMETER_ID"
             ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ shipment_pc sp ON s."SHIPMENT_ID" = sp."SHIPMENT_ID"
             ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ pc pc ON pc."PC_ID" = sp."PC_ID"
             ВНУТРЕННЕЕ ПРИСОЕДИНЕНИЕ LASERMARK_VIEW md ON md."SHIPMENT_DATE" = s."SHIPMENT_DATE" И md."LASERMARK" = w."LASERMARK"
        ГДЕ 
              s."SHIPMENT_DATE" >= '2018-07-01' И s."SHIPMENT_DATE" <= '2018-09-30';

И получаваме още един резултат:
Имаше
Цена: 502 997.55
Време на изпълнение: 505 секунди

Стана
Цена: 42 481.16
Време за изпълнение: 43 секунди.

Въпреки че, разбира се, толкова обещаващият резултат е измамлив, представленията трябва да се обновят. Така че крайното време за получаване на данни не е особено полезно. Но като експеримент, е доста интересно.

Всъщност, както се оказа, отново благодаря asmm и на Хабър !- запитването може да се подобри още.

Следсловие

И така, клиентът е доволен. И им е необходимо да се възползваме от ситуацията.

Нова задача: Какво можем да измислим, за да задълбочим и разширим?

И тук ми се припомня — момчета, а ние нямаме мониторинг на нашите PostgreSQL бази данни.

Честно казано, някакъв мониторинг в вид на Cloud Watch в AWS все пак съществува. Но каква полза от този мониторинг за DBA? Почти никаква.

Ако имате възможност да направите нещо полезно и интересно за себе си, не трябва да се пропуска такава възможност …
ПРОЧЕНО

Happy Party или няколко реда спомени за запознанството с секционирането в PostgreSQL10

Така стигнахме до най-интересното:

3 Декември 2018 година.
Взимане на решение за започване на работа по изследване на възможностите за мониторинг на производителността на заявките за PostgreSQL.

Но това вече е съвсем друга история.

Продължението следва…

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster