terça-feira, 20 de agosto de 2013

PostgreSQL - Job de REINDEX concorrente


  Após criar a rotina de VACUUM controlando o tempo de execução, post anterior, pensei em como poderia aplicar este método na manutenção dos índices de uma determinada base. Então as perguntas iniciais foram:

1 - O que eu queria com isso?

   Precisava de uma rotina que realizasse a manutenção dos índices mais inchados da base, sendo que devido ao curto espaço de tempo disponível para a execução desta rotina, a mesma precisaria atingir as tabelas mais críticas da base.

2 - O que não poderia acontecer com isso?

   - A rotina afetar a produção ou carga durante a produção;
   - A rotina remover algum índice ativo;
   - Prejudicar o plano de execução da base;
   - Comprometer outra rotina de manutenção;

  Com o risco calculado e um bom ambiente de homologação, foi criada uma rotina de reindexação que avalia o inchaço dos índices não PK das 15 tabelas com mais atualizações e exclusões de registros. Em resumo a rotina obtêm a DDL destes índices e um a um cria um índice temporário, após deleta o índice inchado e ao término atualiza as estatísticas destas tabelas.

  Como o processo é transacional, como temos um controle de tempo(exemplo timeout é 6 horas da manhã) caso este controle de tempo mate a criação de índice o índice ainda inchado não é afetado.

     Requisitos:

1 - A base alvo deve possuir a VIEW abaixo:
CREATE OR REPLACE VIEW bloat_objects AS 
 SELECT current_database() AS current_database, sml.schemaname, sml.tablename, sml.reltuples::bigint AS reltuples, sml.relpages::bigint AS relpages, sml.otta, round(
        CASE
            WHEN sml.otta = 0::double precision THEN 0.0
            ELSE sml.relpages::numeric / sml.otta::numeric
        END, 1) AS tbloat, 
        CASE
            WHEN sml.relpages::double precision < sml.otta THEN 0::double precision
            ELSE sml.relpages::bigint::double precision - sml.otta
        END AS wastedpages, 
        CASE
            WHEN sml.relpages::double precision < sml.otta THEN 0::numeric
            ELSE sml.bs * (sml.relpages::double precision - sml.otta)::bigint::numeric
        END AS wastedbytes, 
        CASE
            WHEN sml.relpages::double precision < sml.otta THEN '0 bytes'::text
            ELSE (sml.bs::double precision * (sml.relpages::double precision - sml.otta))::bigint || ' bytes'::text
        END AS wastedsize, sml.iname, sml.ituples::bigint AS ituples, sml.ipages::bigint AS ipages, sml.iotta, round(
        CASE
            WHEN sml.iotta = 0::double precision OR sml.ipages = 0 THEN 0.0
            ELSE sml.ipages::numeric / sml.iotta::numeric
        END, 1) AS ibloat, round(
        CASE
            WHEN sml.iotta = 0::double precision OR sml.ipages = 0 THEN 0.0
            ELSE sml.ipages::numeric / sml.iotta::numeric
        END, 1) * 100::numeric AS perc_ibloat, 
        CASE
            WHEN sml.ipages::double precision < sml.iotta THEN 0::double precision
            ELSE sml.ipages::bigint::double precision - sml.iotta
        END AS wastedipages, 
        CASE
            WHEN sml.ipages::double precision < sml.iotta THEN 0::double precision
            ELSE sml.bs::double precision * (sml.ipages::double precision - sml.iotta)
        END AS wastedibytes, 
        CASE
            WHEN sml.ipages::double precision < sml.iotta THEN '0 bytes'::text
            ELSE (sml.bs::double precision * (sml.ipages::double precision - sml.iotta))::bigint || ' bytes'::text
        END AS wastedisize
   FROM ( SELECT rs.schemaname, rs.tablename, cc.reltuples, cc.relpages, rs.bs, ceil(cc.reltuples * ((rs.datahdr + rs.ma::numeric - 
                CASE
                    WHEN (rs.datahdr % rs.ma::numeric) = 0::numeric THEN rs.ma::numeric
                    ELSE rs.datahdr % rs.ma::numeric
                END)::double precision + rs.nullhdr2 + 4::double precision) / (rs.bs::double precision - 20::double precision)) AS otta, COALESCE(c2.relname, '?'::name) AS iname, COALESCE(c2.reltuples, 0::real) AS ituples, COALESCE(c2.relpages, 0) AS ipages, COALESCE(ceil(c2.reltuples * (rs.datahdr - 12::numeric)::double precision / (rs.bs::double precision - 20::double precision)), 0::double precision) AS iotta
           FROM ( SELECT foo.ma, foo.bs, foo.schemaname, foo.tablename, (foo.datawidth + (foo.hdr + foo.ma - 
                        CASE
                            WHEN (foo.hdr % foo.ma) = 0 THEN foo.ma
                            ELSE foo.hdr % foo.ma
                        END)::double precision)::numeric AS datahdr, foo.maxfracsum * (foo.nullhdr + foo.ma - 
                        CASE
                            WHEN (foo.nullhdr % foo.ma::bigint) = 0 THEN foo.ma::bigint
                            ELSE foo.nullhdr % foo.ma::bigint
                        END)::double precision AS nullhdr2
                   FROM ( SELECT s.schemaname, s.tablename, constants.hdr, constants.ma, constants.bs, sum((1::double precision - s.null_frac) * s.avg_width::double precision) AS datawidth, max(s.null_frac) AS maxfracsum, constants.hdr + (( SELECT 1 + count(*) / 8
                                   FROM pg_stats s2
                                  WHERE s2.null_frac <> 0::double precision AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename)) AS nullhdr
                           FROM pg_stats s, ( SELECT ( SELECT current_setting('block_size'::text)::numeric AS current_setting) AS bs, 
                                        CASE
                                            WHEN "substring"(foo.v, 12, 3) = ANY (ARRAY['8.0'::text, '8.1'::text, '8.2'::text]) THEN 27
                                            ELSE 23
                                        END AS hdr, 
                                        CASE
                                            WHEN foo.v ~ 'mingw32'::text THEN 8
                                            ELSE 4
                                        END AS ma
                                   FROM ( SELECT version() AS v) foo) constants
                          GROUP BY s.schemaname, s.tablename, constants.hdr, constants.ma, constants.bs) foo) rs
      JOIN pg_class cc ON cc.relname = rs.tablename
   JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = rs.schemaname AND nn.nspname <> 'information_schema'::name
   LEFT JOIN pg_index i ON i.indrelid = cc.oid
   LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid) sml
  ORDER BY 
        CASE
            WHEN sml.relpages::double precision < sml.otta THEN 0::numeric
            ELSE sml.bs * (sml.relpages::double precision - sml.otta)::bigint::numeric
        END DESC;

ALTER TABLE bloat_objects
  OWNER TO postgres;

     Estrutura da rotina:

1 - O path deve ser ajustado dentro da rotina para o path que utilizará;
2 - Deves criar dentro deste path as pastas: bin / log / var / lib / tmp.

     Abaixo a rotina de reindexação(crie dentro do 'bin' com o nome 'reindex-maintenance-pgsql.pl'):

#!/usr/bin/perl
#reindex-maintenance-pgsql
#
#Description: Rotina de manutenção de REINDEX para o PostgreSQL
#
#Author:
#        Gabriel Prestes (helkmut@gmail.com)
#
#07-31-2013 : Created

use strict;
use Getopt::Long;
use POSIX;
use File::Basename;
use warnings;

#--------------------------------------------------
# Setting environment
#--------------------------------------------------
$ENV{"USER"}="root";
$ENV{"HOME"}="/root";

#--------------------------------------------------
# Global variables
#--------------------------------------------------
our $name = basename($0);
our $version = "1.2";
our $opt_path = "/opt/resources/reindex-maintenance-pgsql";
our $log_date = `/bin/date -I`;
chomp($log_date);
our $temp_log = "$opt_path/log/reindex-maintenance-pgsql-$log_date.log";
our $opt_props = "$opt_path/lib/maindb.props";
our ($opt_help, $opt_verbose, $opt_version);

#--------------------------------------------------
# Program variables
#--------------------------------------------------

#--------------------------------------------------
# Prop variables
#--------------------------------------------------
our $opt_pguser;
our $opt_pgport;
our $opt_pgdb;
our $opt_owner;
our $opt_fim;

#--------------------------------------------------------------------------------------

sub main {

        # --- Get Options --- #
        getoption();

        # --- Init function vars ---#
        my $counter = 0;
        my $flagcontrol=0;
        my $rows=0;
        my $sqlquery;
        my $cmd;
        my @prop_split=();
        my @props_array=();
        my @tables=();
        my @sqlcmd = ();

        # --- Verbose ---#
        logger("|PROGRAM OUT: Init agent|");

        # --- Get props program --- #
        open (PROPS, "$opt_props") or error();
        @props_array = ;
        close(PROPS);

        foreach(@props_array){

                chomp($_);
                @prop_split = split(/=/,$_);
                if($counter == 0){$opt_pguser = $prop_split[1];}
                if($counter == 1){$opt_pgport = $prop_split[1];}
                if($counter == 2){$opt_pgdb = $prop_split[1];}
                if($counter == 3){$opt_owner = $prop_split[1];}
                if($counter == 4){$opt_fim = $prop_split[1];}
                $counter++;

        }

        $counter=0;

        # --- Check and write pid --- #
        if(check_pid() == 0){

                logger("|PROGRAM OUT: Another job in execution($opt_path/var/reindex-maintenance-pgsql.pid)|");
                exit(1);

        } else {

                write_pid();

        }

        # --- Rotate logs more than 15 days --- #
        logger("|PROGRAM OUT: LOGs - Search for more than 15 days old|");
        $cmd=`\$\(which find\) $opt_path/log/* -name "*" -mtime +15 -exec \$\(which rm\) -rf {} \\; > /dev/null 2>&1`;


        # --- Get relations to reindex --- #
        logger("|PROGRAM OUT: Obtendo relacao de tabelas mais utilizadas com indices inchados|");
        $sqlquery = "SELECT DISTINCT ON (tablename) tablename FROM bloat_objects WHERE (tablename IN ( SELECT relname FROM pg_stat_user_tables WHERE (n_tup_upd+n_tup_del) > 0 ORDER BY (n_tup_upd+n_tup_del) DESC LIMIT 30 )) AND perc_ibloat > 50::numeric ORDER BY tablename, perc_ibloat DESC;";

        @sqlcmd = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"$sqlquery\"`;
        $flagcontrol+=$?;

        if($flagcontrol>0){

                logger("|PROGRAM OUT: Nao pode obter lista de tabelas : falha de comunicacao com a instancia|");
                exit_program();

        }

        # --- Mount tables in array --- #
        foreach(@sqlcmd){

                chomp($_);
                if($_ =~ m/^ (.+)$/g){

                        push(@tables,$1);

                }

                if($_ =~ m/^\((.+) row.+$/){

                        $rows=$1;

                }

        }

        my @sqlcmdaux = ();
        my $sqlindexquery;
        my $sqlin;

        my $sqlindexpart1;
        my $sqlindexpart2;
        my $sqlindexpart3;
        my $sqlindexpartnew = "idx_temp_maintenance";

        trim($rows);

        if(@tables){

                foreach(@tables){

                        chomp($_);
                        trim($_);

                        if($_ !~ "tablename"){

                                logger("|PROGRAM OUT: A tabela '$_' sera reindexada|");

                                foreach(@tables){

                                        chomp($_);
                                        $sqlin .= "\'$_\',";

                                }

                                chop($sqlin);

                        }


                }


        }

        logger("|PROGRAM OUT: $rows tabelas para reindexar|");

        if($rows==0){

                logger("|PROGRAM OUT: OK - Rotina de reindexacao realizada com sucesso!|");
                exit_program();

        }

        my $time=`/bin/date +%H`;
        my $indexcount=0;
        chomp($time);

        # --- Build REINDEX command --- #
        logger("|PROGRAM OUT: Montando vetor de tabelas...|");
        logger("|PROGRAM OUT: Obtendo relacao de indices...|");

        $sqlquery = "SELECT REPLACE(Pg_get_indexdef(i.oid), 'INDEX ', 'INDEX CONCURRENTLY ') || ';' FROM pg_index x JOIN pg_class c ON c.oid = x.indrelid JOIN pg_class i ON i.oid = x.indexrelid LEFT JOIN pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_tablespace t ON t.oid = i.reltablespace WHERE  c.relkind = 'r'::\"char\" AND i.relkind = 'i'::\"char\" AND indisprimary IS FALSE AND c.relname IN($sqlin);";

        @sqlcmd = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"$sqlquery\"`;

        logger("|PROGRAM OUT: Retorno de DDL criacao dos indices:|");

        foreach(@sqlcmd){

                chomp($_);
                logger("$_");

        }


        # --- Drop idx_temp_maintenance if exists --- #
        logger("|PROGRAM OUT: Drop $sqlindexpartnew if exists|");
        @sqlcmdaux = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"DROP INDEX IF EXISTS $sqlindexpartnew\"`;
        $flagcontrol += $?;

        # --- Run reindex --- #
        foreach(@sqlcmd){

                $sqlindexquery = "";
                $sqlindexquery = $_;

                if($sqlindexquery =~ m/^ (CREATE.+CONCURRENTLY) (.+) (ON.+)$/){

                        $sqlindexpart1=$1;
                        $sqlindexpart2=$2;
                        $sqlindexpart3=$3;

                        # --- Time check --- #
                        $time=`/bin/date +%H`;
                        chomp($time);
                        if($time>=$opt_fim){

                                logger("|PROGRAM OUT: Recriacao do indice abortado em funcao do horario: $time|");
                                next;

                        } else {

                                logger("|PROGRAM OUT: Horario verificado nao excede gatilho: $time|");
                                $indexcount++;

                        }

                        logger("|PROGRAM OUT: Recriando indice: $sqlindexpart2|");

                        logger("$sqlindexpart1 $sqlindexpartnew $sqlindexpart3");
                        @sqlcmdaux = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"$sqlindexpart1 $sqlindexpartnew $sqlindexpart3\"`;
                        $flagcontrol += $?;
                        if($flagcontrol>0){next;}
                        logger("DROP INDEX $sqlindexpart2;");
                        @sqlcmdaux = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"BEGIN;DROP INDEX $sqlindexpart2;ALTER INDEX $sqlindexpartnew RENAME TO $sqlindexpart2;ALTER INDEX $sqlindexpart2 OWNER TO $opt_owner;COMMIT;\"`;
                        $flagcontrol += $?;

                }

        }

        if($indexcount==0){

                logger("|PROGRAM OUT: ALERTA - Rotina abortada em funcao do horario: $time|");
                exit_program();

        }

        # --- ANALYZE tables --- #
        foreach(@tables){

                trim($_);

                if($_ !~ "tablename"){

                        chomp($_);
                        logger("|PROGRAM OUT: Atualizando as estatisticas da tabela '$_'|");
                        @sqlcmdaux = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"ANALYZE $_;\"`;
                        $flagcontrol += $?;

                }

        }

# ---> Avaliar necessidade de resetar estatísticas da base
#        logger("|PROGRAM OUT: Resetando as estatisticas da base|");
#        @sqlcmdaux = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"SELECT pg_stat_reset();\"`;
#        $flagcontrol += $?;

        # --- Threshoulds --- #
        if($flagcontrol>0){

                logger("|PROGRAM OUT: ERRO - Rotina de reindexacao finalizou com erros($flagcontrol) verifique o mais rapido possivel|");

        } else {

                logger("|PROGRAM OUT: OK - Rotina de reindexacao realizada com sucesso!|");

        }

        exit_program();

}

#--------------------------------------------------------------------------------------

sub getoption {
     Getopt::Long::Configure('bundling');
     GetOptions(
            'V|version'                 => \$opt_version,
            'h|help'                    => \$opt_help,
            'v|verbose=i'               => \$opt_verbose,
        );

     if($opt_help){

             printHelp();
             exit_program();

     }

     if($opt_version){

             print "$name - '$version'\n";
             exit_program();

     }

}

#--------------------------------------------------------------------------------------

sub logger {

        return(0) if (not defined $opt_verbose);

        my $msg = shift (@_);
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
        $wday++;
        $yday++;
        $mon++;
        $year+=1900;
        $isdst++;

        if ($opt_verbose == 0){

                         print "$msg\n";

        } else {

           open(LOG, ">>$temp_log") or error();
           printf LOG ("%02i/%02i/%i - %02i:%02i:%02i => %s\n",$mday,$mon,$year,$hour,$min,$sec,$msg);
           close(LOG);

        }

}

#--------------------------------------------------------------------------------------

sub printHelp {

                my $help = <<'HELP';


                Thanks for use Reindex Maintenance PGSQL.

                API required:

                                strict;
                                Getopt::Long;
                                POSIX;
                                File::Basename;
                                warnings;

                Agent binary           : bin/reindex-maintenance-pgsql.pl &
                Configuration Agent in : lib/maindb.props
                Support                : helkmut@gmail.com

                Salmo 91:

                "Direi do Senhor: Não temerás os terrores da noite, nem a seta que voe de dia, nem peste que anda na escuridão, nem mortandade que assole ao meio-dia."





HELP

                system("clear");
                print $help;

}

#--------------------------------------------------------------------------------------

sub error {

        print "|ERROR - Unexpected return - contact support|\n";
        exit_program();

}

#--------------------------------------------------------------------------------------

sub trim($) {

        my $string = shift;

        $string =~ s/^\s+//;
        $string =~ s/\s+$//;

        return $string;

}

#--------------------------------------------------------------------------------------

sub write_pid {

        my $cmd;

        $cmd=`\$\(which touch\) $opt_path/var/reindex-maintenance-pgsql.pid`;

        return 1;

}

#--------------------------------------------------------------------------------------

sub check_pid {

        if(-e "$opt_path/var/reindex-maintenance-pgsql.pid"){

                return 0;

        } else {

                return 1;

        }

}

#--------------------------------------------------------------------------------------

sub exit_program {

        my $cmd;

        $cmd=`\$\(which rm\) -rf $opt_path/var/reindex-maintenance-pgsql.pid`;

        exit;

}

#--------------------------------------------------------------------------------------

&main

##Ignore o '' se existir no final, é um erro de formatação do post. 


Arquivo de configuração(crie dentro do 'lib' com o nome 'maindb.props'):
opt_pguser=postgres
opt_pgport=5432
opt_pgdb=postgres
opt_owner=postgres
opt_fim=06

# --- Additional info --- #

opt_pguser - > Login role
opt_pgport -> Instance port
opt_pgdb -> Database connect
opt_owner -> Owner index
opt_fim -> Time abort

# --- Additional info --- #

Arquivo de controle de tempo(crie dentro do 'bin' com nome 'reindex-controltime-pgsql.pl'):
#!/usr/bin/perl
#reindex-controltime-pgsql
#
#Description: Rotina de controle de manutenção de REINDEX para o PostgreSQL
#
#Author:
#        Gabriel Prestes (helkmut@gmail.com)
#
#08-09-2013 : Created

use strict;
use Getopt::Long;
use POSIX;
use File::Basename;
use warnings;

#--------------------------------------------------
# Setting environment
#--------------------------------------------------
$ENV{"USER"}="root";
$ENV{"HOME"}="/root";

#--------------------------------------------------
# Global variables
#--------------------------------------------------
our $name = basename($0);
our $version = "0.1";
our $opt_path = "/opt/resources/reindex-maintenance-pgsql";
our $log_date = `/bin/date -I`;
chomp($log_date);
our $temp_log = "$opt_path/log/reindex-controltime-pgsql-$log_date.log";
our $opt_props = "$opt_path/lib/maindb.props";
our ($opt_help, $opt_verbose, $opt_version);

#--------------------------------------------------
# Program variables
#--------------------------------------------------

#--------------------------------------------------
# Prop variables
#--------------------------------------------------
our $opt_pguser;
our $opt_pgport;
our $opt_pgdb;
our $opt_fim;

#--------------------------------------------------------------------------------------

sub main {

        # --- Get Options --- #
        getoption();

        # --- Init function vars ---#
        my $counter = 0;
        my $flagcontrol=0;
        my $rows=0;
        my $sqlquery;
        my $cmd;
        my @prop_split=();
        my @props_array=();
        my @command=();
        my @sqlcmd = ();

        # --- Verbose ---#
        logger("|PROGRAM OUT: Init agent|");

        # --- Get props program --- #
        open (PROPS, "$opt_props") or error();
        @props_array = ;
        close(PROPS);

        foreach(@props_array){

                chomp($_);
                @prop_split = split(/=/,$_);
                if($counter == 0){$opt_pguser = $prop_split[1];}
                if($counter == 1){$opt_pgport = $prop_split[1];}
                if($counter == 2){$opt_pgdb = $prop_split[1];}
                if($counter == 4){$opt_fim = $prop_split[1];}
                $counter++;

        }

        $counter=0;

        # --- Check and write pid --- #
        if(check_pid() == 0){

                logger("|PROGRAM OUT: Another job in execution($opt_path/var/reindex-controltime-pgsql.pid)|");
                exit(1);

        } else {

                write_pid();

        }

        # --- Rotate logs more than 15 days --- #
        logger("|PROGRAM OUT: LOGs - Search for more than 15 days old|");
        $cmd=`\$\(which find\) $opt_path/log/* -name "*" -mtime +15 -exec \$\(which rm\) -rf {} \\; > /dev/null 2>&1`;


        # --- Get relations to reindex --- #
        logger("|PROGRAM OUT: Obtendo relacao de reindex em execucao|");
        $sqlquery = "SELECT pg_stat_activity.procpid as processo FROM pg_stat_activity WHERE pg_stat_activity.current_query LIKE 'CREATE INDEX CONCURRENTLY idx_temp_maintenance%';";

        @sqlcmd = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"$sqlquery\"`;
        $flagcontrol+=$?;

        if($flagcontrol>0){

                logger("|PROGRAM OUT: Nao pode obter lista de indices : falha de comunicacao com a instancia|");
                exit_program();

        }

        # --- Mount tables in array --- #
        foreach(@sqlcmd){

                chomp($_);
                if($_ =~ m/^ (.+)$/g){

                        push(@command,$1);

                }

                if($_ =~ m/^\((.+) row.+$/){

                        $rows=$1;

                }

        }

        my @sqlcmdaux = ();
        trim($rows);

        if(@command){

                foreach(@command){

                        chomp($_);

                        if($_ !~ m/^proces.+$/){

                                trim($_);
                                logger("|PROGRAM OUT: O processo$_ de recriacao do indice sera cancelado|");
                                @sqlcmd = `/usr/bin/psql -U $opt_pguser -p $opt_pgport $opt_pgdb -q -c \"SELECT pg_cancel_backend($_)\"`;

                        }


                }


        }

        logger("|PROGRAM OUT: $rows reindex cancelado(s)|");

        if($rows==0){

                logger("|PROGRAM OUT: OK - Rotina de controle finalizada sem cancelamento de backends!|");
                exit_program();

        }

        # --- Threshoulds --- #
        logger("|PROGRAM OUT: OK - Rotina de controle finalizada com sucesso!|");
        exit_program();

}

#--------------------------------------------------------------------------------------

sub getoption {
     Getopt::Long::Configure('bundling');
     GetOptions(
            'V|version'                 => \$opt_version,
            'h|help'                    => \$opt_help,
            'v|verbose=i'               => \$opt_verbose,
        );

     if($opt_help){

             printHelp();
             exit_program();

     }

     if($opt_version){

             print "$name - '$version'\n";
             exit_program();

     }

}

#--------------------------------------------------------------------------------------

sub logger {

        return(0) if (not defined $opt_verbose);

        my $msg = shift (@_);
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
        $wday++;
        $yday++;
        $mon++;
        $year+=1900;
        $isdst++;

        if ($opt_verbose == 0){

                         print "$msg\n";

        } else {

           open(LOG, ">>$temp_log") or error();
           printf LOG ("%02i/%02i/%i - %02i:%02i:%02i => %s\n",$mday,$mon,$year,$hour,$min,$sec,$msg);
           close(LOG);

        }

}

#--------------------------------------------------------------------------------------

sub printHelp {

                my $help = <<'HELP';


                Thanks for use Reindex Controltime PGSQL.

                API required:

                                strict;
                                Getopt::Long;
                                POSIX;
                                File::Basename;
                                warnings;

                Agent binary           : bin/reindex-controltime-pgsql.pl &
                Configuration Agent in : lib/maindb.props
                Support                : helkmut@gmail.com

                "Cordeiro de Deus, retirai os pecados do mundo, tende piedade de nós!"




HELP

                system("clear");
                print $help;

}

#--------------------------------------------------------------------------------------

sub error {

        print "|ERROR - Unexpected return - contact support|\n";
        exit_program();

}

#--------------------------------------------------------------------------------------

sub trim($) {

        my $string = shift;

        $string =~ s/^\s+//;
        $string =~ s/\s+$//;

        return $string;

}

#--------------------------------------------------------------------------------------

sub write_pid {

        my $cmd;

        $cmd=`\$\(which touch\) $opt_path/var/reindex-controltime-pgsql.pid`;

        return 1;

}

#--------------------------------------------------------------------------------------

sub check_pid {

        if(-e "$opt_path/var/reindex-controltime-pgsql.pid"){

                return 0;

        } else {

                return 1;

        }

}

#--------------------------------------------------------------------------------------

sub exit_program {

        my $cmd;

        $cmd=`\$\(which rm\) -rf $opt_path/var/reindex-controltime-pgsql.pid`;

        exit;

}

#--------------------------------------------------------------------------------------

&main

##Ignore o '' se existir no final, é um erro de formatação do post.



Feito isto, precisarás de dois jobs no crontab ou no pgAgent, o da rotina de reindexação que deve por exemplo iniciar meia-noite e outro job com o controle de execução que deve ser agendado para o horário em que quer abortar a rotina, exemplo 6 horas da manhã.

        Exemplo de execução no pgAgent:

1 - Crie um job do tipo manutenção que executará um 'BATCH' e não 'SQL';
2 - Agende para a data e horário desejado;
3 - Na definição do passo coloque '/opt/resources/reindex-maintenance-pgsql/bin/reindex-controltime-pgsql.pl -v 1';

       Faça a mesma ação para o outro job de reindexação, deixando a definição como '/opt/resources/reindex-maintenance-pgsql/bin/reindex-maintenance-pgsql.pl -v 1'.

Explicação sobre os parâmetros de configuração:

opt_pguser=postgres -> Usuário do PostgreSQL que executará a rotina
opt_pgport=5432 -> Porta da instância
opt_pgdb=postgres -> Database da instância onde a rotina será executada
opt_owner=postgres -> Dono dos índices recriados na rotina
opt_fim=06 -> Tempo estabelecido para finalizar a rotina

    Então você se pergunta:

    Porque um programa auxiliar para terminar a rotina de reindexação se a mesma já possui este controle internamente?

    A resposta será, caso a rotina esteja criando um índice a mesma continuará em execução até finalizar essa criação, com o programa auxiliar o backend onde a criação está ocorrendo é cancelado e o índice temporário que está sendo criado é invalidado.

Análise da rotina:

Um log exemplo de execução da rotina abaixo. Lembrando que os logs já são rotacionados, mantendo somente os últimos 15 dias.

Controle de execução:
20/08/2013 - 06:30:01 => |PROGRAM OUT: Init agent|
20/08/2013 - 06:30:01 => |PROGRAM OUT: LOGs - Search for more than 15 days old|
20/08/2013 - 06:30:01 => |PROGRAM OUT: Obtendo relacao de reindex em execucao|
20/08/2013 - 06:30:01 => |PROGRAM OUT: 0 reindex cancelado(s)|
20/08/2013 - 06:30:01 => |PROGRAM OUT: OK - Rotina de controle finalizada sem cancelamento de backends!|

Rotina de reindexação:
17/08/2013 - 00:01:03 => |PROGRAM OUT: Init agent|
17/08/2013 - 00:01:03 => |PROGRAM OUT: LOGs - Search for more than 15 days old|
17/08/2013 - 00:01:03 => |PROGRAM OUT: Obtendo relacao de tabelas mais utilizadas com indices inchados|
17/08/2013 - 00:01:03 => |PROGRAM OUT: A tabela 'teste' sera reindexada|
17/08/2013 - 00:01:03 => |PROGRAM OUT: 1 tabelas para reindexar|
17/08/2013 - 00:01:03 => |PROGRAM OUT: Montando vetor de tabelas...|
17/08/2013 - 00:01:03 => |PROGRAM OUT: Obtendo relacao de indices...|
17/08/2013 - 00:01:03 => |PROGRAM OUT: Retorno de DDL criacao dos indices:|
17/08/2013 - 00:01:03 =>                                                            ?column?
17/08/2013 - 00:01:03 => -------------------------------------------------------------------------------------------------------------------------------
17/08/2013 - 00:01:03 =>  CREATE INDEX CONCURRENTLY idx_teste_cli ON teste USING btree (cli);
17/08/2013 - 00:01:03 =>  CREATE INDEX CONCURRENTLY idx_teste_clo ON teste USING btree (clo);
17/08/2013 - 00:01:03 => (2 rows)
17/08/2013 - 00:01:03 =>
17/08/2013 - 00:01:03 => |PROGRAM OUT: Drop idx_temp_maintenance if exists|
17/08/2013 - 00:01:03 => |PROGRAM OUT: Horario verificado nao excede gatilho: 00|
17/08/2013 - 00:01:03 => |PROGRAM OUT: Recriando indice: idx_teste_cli |
17/08/2013 - 00:01:03 => CREATE INDEX CONCURRENTLY idx_temp_maintenance ON teste USING btree (cli);
17/08/2013 - 00:02:03 => DROP INDEX idx_teste_cli;
17/08/2013 - 00:02:04 => |PROGRAM OUT: Horario verificado nao excede gatilho: 00|
17/08/2013 - 00:02:04 => |PROGRAM OUT: Recriando indice: idx_teste_clo |
17/08/2013 - 00:02:04 => CREATE INDEX CONCURRENTLY idx_temp_maintenance ON teste USING btree (clo);
17/08/2013 - 00:02:06 => DROP INDEX idx_teste_clo ;
17/08/2013 - 00:02:24 => |PROGRAM OUT: Atualizando as estatisticas da tabela 'teste'|
17/08/2013 - 00:02:45 => |PROGRAM OUT: Resetando as estatisticas da base|
17/08/2013 - 00:02:45 => |PROGRAM OUT: OK - Rotina de reindexacao realizada com sucesso!|