terça-feira, 17 de dezembro de 2013

PostgreSQL - pg_upgrade sem mistérios


   Dias atrás precisei migrar um PGSQL 9.2 para um 9.3. Mais detalhes da evolução da versão podem ser visualizados em http://www.postgresql.org/about/news/1481/

   Para meu desespero o servidor era um Ubuntu, coisa medonha, Deus meu! Mas em três comandos realizei o procedimento. Primeiro tu precisas dos binários do 9.3 instalados, não vou mostrar aqui como fazer isto, mas use o repositório do PGDG que é muito bom. 

   Binários instalados e contrib também, pois lá é que está o binário do pg_upgrade, basta fazer o seguinte: 

1 - Parar ambas as instâncias, 9.2 e 9.3; 
2 - Executar o comando: sudo -H -u postgres /usr/lib/postgresql/9.3/bin/pg_upgrade -b /usr/lib/postgresql/9.2/bin -B /usr/lib/postgresql/9.3/bin -d /var/lib/postgresql/9.2/main -D /var/lib/postgresql/9.3/main -o ' -c config_file=/etc/postgresql/9.2/main/postgresql.conf' -O ' -c config_file=/etc/postgresql/9.3/main/postgresql.conf' 

Detalhes podem ser observados no help do comando: 

/usr/lib/postgresql/9.3/bin/pg_upgrade -h
pg_upgrade upgrades a PostgreSQL cluster to a different major version.

Usage:
  pg_upgrade [OPTION]...

Options:
  -b, --old-bindir=OLDBINDIR    old cluster executable directory
  -B, --new-bindir=NEWBINDIR    new cluster executable directory
  -c, --check                   check clusters only, don't change any data
  -d, --old-datadir=OLDDATADIR  old cluster data directory
  -D, --new-datadir=NEWDATADIR  new cluster data directory
  -j, --jobs                    number of simultaneous processes or threads to use
  -k, --link                    link instead of copying files to new cluster
  -o, --old-options=OPTIONS     old cluster options to pass to the server
  -O, --new-options=OPTIONS     new cluster options to pass to the server
  -p, --old-port=OLDPORT        old cluster port number (default 50432)
  -P, --new-port=NEWPORT        new cluster port number (default 50432)
  -r, --retain                  retain SQL and log files after success
  -u, --user=NAME               cluster superuser (default "root")
  -v, --verbose                 enable verbose internal logging
  -V, --version                 display version information, then exit
  -?, -h, --help                show this help, then exit

Before running pg_upgrade you must:
  create a new database cluster (using the new version of initdb)
  shutdown the postmaster servicing the old cluster
  shutdown the postmaster servicing the new cluster

When you run pg_upgrade, you must provide the following information:
  the data directory for the old cluster  (-d OLDDATADIR)
  the data directory for the new cluster  (-D NEWDATADIR)
  the "bin" directory for the old version (-b OLDBINDIR)
  the "bin" directory for the new version (-B NEWBINDIR)

For example:
  pg_upgrade -d oldCluster/data -D newCluster/data -b oldCluster/bin -B newCluster/bin
or
  $ export PGDATAOLD=oldCluster/data
  $ export PGDATANEW=newCluster/data
  $ export PGBINOLD=oldCluster/bin
  $ export PGBINNEW=newCluster/bin
  $ pg_upgrade

Report bugs to .

Log da execução: 

Performing Consistency Checks
-----------------------------
Checking cluster versions                                   ok
Checking database user is a superuser                       ok
Checking for prepared transactions                          ok
Checking for reg* system OID user data types                ok
Checking for contrib/isn with bigint-passing mismatch       ok
Creating dump of global objects                             ok
Creating dump of database schemas
                                                            ok
Checking for presence of required libraries                 ok
Checking database user is a superuser                       ok
Checking for prepared transactions                          ok

If pg_upgrade fails after this point, you must re-initdb the
new cluster before continuing.

Performing Upgrade
------------------
Analyzing all rows in the new cluster                       ok
Freezing all rows on the new cluster                        ok
Deleting files from new pg_clog                             ok
Copying old pg_clog to new server                           ok
Setting next transaction ID for new cluster                 ok
Setting oldest multixact ID on new cluster                  ok
Resetting WAL archives                                      ok
Setting frozenxid counters in new cluster                   ok
Restoring global objects in the new cluster                 ok
Adding support functions to new cluster                     ok
Restoring database schemas in the new cluster
                                                            ok
Removing support functions from new cluster                 ok
Copying user relation files
                                                            ok
Setting next OID for new cluster                            ok
Sync data directory to disk                                 ok
Creating script to analyze new cluster                      ok
Creating script to delete old cluster                       ok

Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade so,
once you start the new server, consider running:
    analyze_new_cluster.sh

Running this script will delete the old cluster's data files:
    delete_old_cluster.sh

3 - Depois disto no diretório corrente onde tu estás serão criados os scripts que rodaremos na sequência: 

analyze_new_cluster.sh : Atualiza estatísticas de todas as bases
delete_old_cluster.sh : Cuidado, esse cara remove o pg_data do 9.2. 

---

This script will generate minimal optimizer statistics rapidly
so your system is usable, and then gather statistics twice more
with increasing accuracy.  When it is done, your system will
have the default level of optimizer statistics.

If you have used ALTER TABLE to modify the statistics target for
any tables, you might want to remove them and restore them after
running this script because they will delay fast statistics generation.

If you would like default statistics as quickly as possible, cancel
this script and run:
    vacuumdb --all --analyze-only

Generating minimal optimizer statistics (1 target)
--------------------------------------------------
vacuumdb: limpando banco de dados "postgres"
vacuumdb: limpando banco de dados "template1"

The server is now available with minimal optimizer statistics.
Query performance will be optimal once this script completes.

Generating medium optimizer statistics (10 targets)
---------------------------------------------------
vacuumdb: limpando banco de dados "postgres"
vacuumdb: limpando banco de dados "template1"

Generating default (full) optimizer statistics (100 targets?)
-------------------------------------------------------------
vacuumdb: limpando banco de dados "postgres"
vacuumdb: limpando banco de dados "template1"

Done

./delete_old_cluster.sh

4 - Valide teu 'pg_hba' e 'postgresql.conf' da nova instância e era isso. 

segunda-feira, 4 de novembro de 2013

PostgreSQL - pg_repack



    Olá amigo sofredor que mantêm um PostgreSQL 8.x, sim, falemos besteiras, pois afinal não são muitos os que as leem mesmo. 

    Por que tu sofres? Pelo VACUUM FULL inviável? Por índices maltratados e ou inchados? Aqui não falaremos dos inexistentes, pois como cantavam(tempo passado, pois hoje a formação da banda não é mais a mesma) Os Mirins(http://pt.wikipedia.org/wiki/Os_Mirins): "Dos covardes não se fala". 

    Bem, este é o mesmo sofrimento que eu passo. Então há alguns dias o Fabrízio Mello(http://fabriziomello.blogspot.com/) me apresentou o pg_repack(https://github.com/reorg/pg_repack).

    Primeiro, por que ele é importante? 

    Porque o VACUUM ou AUTOVACUUM somente marcam para reuso as túpulas mortas. VACUUM FULL é impraticável no 8.x, é mais rápido exportar e importar um dump do que rodá-lo. CLUSTER gera exclusive lock na tabela, o que é impossível também em uma base 24x7. 

    REINDEX em tabelas grandes não é viável também, ou se você vai para uma recriação de índice concorrente te falta o maldito índice da PK.  

    O que, sua base não é 24x7? Pare de ler este post, isto não serve pra ti. Continua com CLUSTER sem problemas. 

    Como funciona o pg_repack: Na prática ele recria a tabela e seus índices com o mínimo de lock possível. Durante o processo ele cria uma tabela temporária e armazena os dados da tabela atual nela(sim, você precisa de pelo menos 120% do espaço da tabela livre em espaço em disco), recria seus índices e durante este processo armazena os logs das alterações(via trigger) sofridas em uma outra tabela para aplicar posteriormente ao finalizar o processo. Feito isto remove a tabela antiga e renomeia a nova.

    Qual o comando para isso:

/usr/pgsql-8.4/bin/pg_repack --order-by=identificador_unico --table=public.teste‏ --echo -U postgres -d producao 

    O que significam os parâmetros? 

--order-by=identificador_unico : Para recriar a tabela ordenando-a pela PK
--table=public.teste‏ : A tabela em si
--echo : Para que você veja a coisa acontecer 
-U postgres : Sério mesmo que tu não sabe? 
-d producao : database alvo

   Quais cuidados devo ter?  

    O único ponto onde não testei o rollback é quando o mesmo está enviando as transações durante o processo para um local temporário via trigger, então tenha bastante cuidado ao automatizar uma rotina desta, pois a falha da rotina PODE(talvez, pois não foi testado) continuar enviando as DMLs para o limbo e não para a tabela em si. 

quinta-feira, 17 de outubro de 2013

VirtualBox - Iniciando VMs automaticamente no CentOS/RHEL sem precisar de X rodando

 
   Como estava sem muitas ideias e precisava fazer isso funcionar, segue um 'service' do CentOS/RHEL que faz VMs iniciarem e pararem(salvar status) sem tu precisares do X Window aberto.


  IMPORTANTE: O array 'vms' é responsável pelas VMs que queres gerenciar, se tiver mais de 1, adicione-as no array e pronto.


#!/bin/sh
#
# chkconfig: - 99 99
# description: Starts and stops VMs
#       #
# pidfile: /var/run/vbox-vms.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ ${NETWORKING} = "no" ] && exit 1

vms=( "CC-Linux-testvm" )

start() {
        kind="VMs"
        numvms=${#vms[*]}
        count=0

echo -n $"Starting $kind services:"

       while [ $count -lt $numvms ]
        do
/usr/bin/VBoxHeadless -startvm ${vms[$i]} >> /var/log/vbox-jobs/${vms[$i]}.log 2>&1 &
                RETVAL=$(($RETVAL + $?))
                (( count=count+1 ))
        done

echo " "

        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/vbox-vms || RETVAL=1
        return $RETVAL

}

stop() {
        echo
kind="VMs"
numvms=${#vms[*]}
count=0

echo -n $"Shutting down $kind services: "

while [ $count -lt $numvms ]
do
/usr/bin/vboxmanage controlvm ${vms[$i]} savestate
                RETVAL=$(($RETVAL + $?))
(( count=count+1 ))
done

        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/vbox-vms
        echo ""
        return $RETVAL

}

restart() {
stop
start
}

case "$1" in
  start)
  start
;;
  stop)
  stop
;;
  restart)
  restart
;;
  *)
echo $"Usage: $0 {start|stop|restart}"
exit 2
esac

exit $?

terça-feira, 8 de outubro de 2013

AWS - Desligando/Ligando instâncias na Amazon

Segue um shell safado para desligar instâncias na Amazon, depois só tens que ajustar os IDs das instâncias que queres desligar ou ligar e colocar no crontab de um servidor que gerenciará essa operação.


 IMPORTANTE: 

 1 - Se configurar isso em uma instância da Amazon, garanta(seu animal), que uma das instâncias desligadas NÃO SEJA a que gerencia o processo;
2 - Se trabalhares com regiões distintas deverás fazer condições no loop;
3 - Precisará o pacote da AWS de gerenciamento de cloud;
4 - Esqueci.

Para ligar:

#!/bin/bash

# Description: Program to start AWS instances

# --- Props vars --- #

# System
EC2_HOME=/opt/aws/apitools/ec2
JAVA_HOME=/usr/lib/jvm/jre
export EC2_HOME JAVA_HOME

# Rest vars
x=0

# Instances
instances=( "i-f32131" "i-0fc2435" "i-33213312" "i-f535324649" "i-98d74324329" )

# Keys AWS
awsaccesskey="1234567890ABC"
awssecretkey="1234315154364213357389460345ABC"


# Program

echo "|INIT Agent|" ; /bin/date

while [ $x != ${#instances[@]} ]

        do

                echo "Start instance ${instances[$x]} ..."

                if [ ${instances[$x]} == "i-0fc2435" ] || [ ${instances[$x]} == "i-33213312" ] ; then

   # DC São Paulo
                        /opt/aws/bin/ec2-start-instances -O ${awsaccesskey} -W ${awssecretkey} --region=sa-east-1 ${instances[$x]}

                fi

  # DC Virginia
                /opt/aws/bin/ec2-start-instances -O ${awsaccesskey} -W ${awssecretkey} ${instances[$x]}

                let "x = x +1"

done

echo "|FINISH Agent|" ; /bin/date




Para desligar:

#!/bin/bash

# Description: Program to stop AWS instances

# --- Props vars --- #

# System
EC2_HOME=/opt/aws/apitools/ec2
JAVA_HOME=/usr/lib/jvm/jre
export EC2_HOME JAVA_HOME

# Rest vars
x=0

# Instances
instances=( "i-f32131" "i-0fc2435" "i-33213312" "i-f535324649" "i-98d74324329" )

# Keys AWS
awsaccesskey="1234567890ABC"
awssecretkey="1234315154364213357389460345ABC"


# Program

echo "|INIT Agent|" ; /bin/date

while [ $x != ${#instances[@]} ]

        do

                echo "Stop instance ${instances[$x]} ..."

                if [ ${instances[$x]} == "i-0fc2435" ] || [ ${instances[$x]} == "i-33213312" ] ; then

   # DC São Paulo
                        /opt/aws/bin/ec2-stop-instances -O ${awsaccesskey} -W ${awssecretkey} --region=sa-east-1 ${instances[$x]}

                fi

  # DC Virginia
                /opt/aws/bin/ec2-stop-instances -O ${awsaccesskey} -W ${awssecretkey} ${instances[$x]}

                let "x = x +1"

done

echo "|FINISH Agent|" ; /bin/date


quinta-feira, 12 de setembro de 2013

AWS - Dica para quem tem problema de boot em uma instância Linux


   Quando você faz alguma arte em uma instância da Amazon, como você não tem acesso que não por SSH e a instância não inicia após um reboot/stop/start, e ao olhar o Console info você sabe qual arte você fez. Para contornar segue uma dica nobre:

1 - Crie uma nova instância(ou utilize uma existente que não seja de produção);
2 - Desligue a instância problemática;
3 - Desvincule o volume root dela e vincule a instância citada no item 1 especificando um device que não esteja em uso(é claro); (não é necessário reboot ou algo assim, o disco é reconhecido sem qualquer ação)
* Não se preocupe, a Amazon não deixa você fazer essa arte e informa que o device já está em uso caso você tente especificar um já em uso.
4 - Monte o device em um diretório em desuso;(exemplo: /mnt)
5 - Arrume sua arte e desmonte o device;
6 - Desvincule da instância e vincule a instância parada no item 2.

 Pronto, problema corrigido.

quarta-feira, 4 de setembro de 2013

PostgreSQL - Correção de bug rotina de REINDEX concorrente

   Este post é para corrigir duas falhas na rotina do post anterior. As falhas acontecem quando a base alvo da rotina possui múltiplos schemas. São elas:

1 - Quando o schema é diferente do 'public' a rotina não funcionava;
2 - Se a rotina falha-se na execução anterior o índice 'idx_temp_maintenance' podia entrar na relação de índices inchados.

   Adicionada uma feature para suportar execução em múltiplas bases de uma instância.

   Então segue link para download do arquivo da nova versão:

https://www.dropbox.com/s/ymaqs4zsrq8nja9/maindb.props
https://www.dropbox.com/s/7yzs23yto5sktki/reindex-maintenance-pgsql.pl

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!|



quarta-feira, 31 de julho de 2013

PostgreSQL - Rotina de VACUUM optimizada



   Primeiramente devo agradecer ao Fabrízio Mello(http://fabriziomello.blogspot.com.br/) que trabalhou junto comigo na rotina abaixo.

CENÁRIO: Base 24x7 em uma base de quase 500GB.

PROBLEMA: Optou-se por tornar o auto-vacuum mais agressivo para não impactar no ambiente durante o horário comercial, mas o VACUUM na base inteira ainda que executado no domingo demorava muito, mesmo com alteração dos parâmetros de memória.  

SOLUÇÃO: Em conjunto com o Fabrízio Mello, o qual tem minha indicação incondicional, elaboramos a rotina abaixo através do PgAgent em um job de BATCH:

#!/bin/bash

PGUSER="postgres"
PGPORT="5432"
PGDATABASE="postgres" 
PGHOST="localhost"

export PGUSER PGPORT PGDATABASE PGHOST

START="00:00:00"
END="06:00:00"
LIMIT="20"

SQL="
SELECT 'VACUUM ANALYZE VERBOSE '||quote_ident(schemaname)||'.'||quote_ident(relname)||';'
  FROM pg_stat_user_tables
 WHERE (n_dead_tup+n_live_tup)>0
 ORDER BY (n_dead_tup * 100)/(n_dead_tup+n_live_tup) desc
 LIMIT ${LIMIT};"

echo "Iniciando rotina de vacuum:" > /tmp/vacuum-maintenance-$PGDATABASE-v2.log

psql -Atq -c "$SQL" | while read i
do
 now=$(date +%H%M%S)
 if [[ "$now" == "$START" || "$now" > "$START" &&
   "$now" < "$END" || "$now" == "$END" ]]
 then
  psql -c "$i" >> /tmp/vacuum-maintenance-$PGDATABASE-v2.log 2>&1
 
 fi
done




Em resumo o job realiza VACUUM ANALYZE nas 20(quantidade definida com o parâmetro LIMIT) tabelas que mais tem tupulas mortas. Entretanto a execução ocorre apenas entre o START e o END que for definido no job, caso finalize a tabela 19 e o horário for maior que o definido em END o job é finalizado sem realizar o VACUUM ANALYZE na tabela 20 da query. O resultado podes ver em /tmp/vacuum-maintenance-$PGDATABASE-v2.log onde $PGDATABASE é o nome da base alvo do BATCH.

quarta-feira, 10 de julho de 2013

JBoss - Erro 503 no Apache

   Aqui vai uma super dica para os amantes de JBoss combinado com Jk ou ModCluster.

   Durante a semana me deparei com o seguinte cenário, quando o Apache recebia mais de 500 acessos simultâneos o log do modjk retornava o seguinte erro:
___________________________________________
[Wed Jul 10 16:42:39 2013][22054:139731589330912] [error] ajp_connect_to_endpoint::jk_ajp_common.c (1035): (test-102) cping/cpong after connecting to the backend server failed (errno=24)
[Wed Jul 10 16:42:39 2013][22054:139731589330912] [error] ajp_send_request::jk_ajp_common.c (1630): (test-102) connecting to backend failed. Tomcat is probably not started or is listening on the wrong port (errno=24)
[Wed Jul 10 16:42:39 2013][22054:139731589330912] [error] ajp_service::jk_ajp_common.c (2626): (test-102) connecting to tomcat failed.
[Wed Jul 10 16:42:39 2013][22054:139731589330912] [error] service::jk_lb_worker.c (1485): All tomcat instances failed, no more workers left
________________________________________________

   O reflexo disto no Apache era o retorno de um erro 503 conforme abaixo:
__________________________________________
10.0.0.1 - - [10/Jul/2013:16:41:16 -0300] "POST /teste/salsa.json?blabla=1 HTTP/1.1" 503 437 "-" "Dalvik/1.6.0 (Linux; U; Android 4.0.3; GT-P3100 Build/IML74K)"
__________________________________________

   Quando o número de requisições reduzia os erros paravam e o ambiente era normalizado.

   Após ler em diversos fóruns mencionando que o erro era ocasionado por um Firewall bloqueando a conexão do AJP entre JBoss e Apache(o que não se aplicava no cenário corrente, pois estavam no mesmo servidor), realizei os seguintes ajustes no modjk e no JBossWeb:

Apache:

- Aumentei o connection_pool dos workers.

JBoss:

- Aumentei o MaxThreads das instâncias;
- Aumentei o ConnectionTimeout das instâncias.

   Com isso os erros 503 sumiram ao receber mais de 500 requisições simultâneas, e a comunicação AJP entre JBoss e Apache foi normalizada acabando com o erro 'cping/cpong' que na verdade quando é gerado manda o worker do URI para status ERR.



terça-feira, 4 de junho de 2013

PostgreSQL - Aumentando um LVM em RAID na AWS

   Hoje tive a experiência de aumentar um LVM com RAID0 na AWS, a situação foi que um PostgreSQL warm-standby parou de funcionar em função de não ter mais espaço no PGDATA para consumir WALs, então o pg_standby caiu. 

   Detalhes da situação:

1 - Instância apenas com uma tablespace;
2 - Sistema de arquivos em XFS; 
3 - 3 discos dedicados para o PGDATA em RAID0.

   Não vou dar grandes explicações, vamos ao procedimento:

1 - Adição do novo disco e vinculação a instância na Amazon;

2 - Adição do novo disco no array do RAID(mdadm --detail /dev/md0 ; mdadm --grow /dev/md0 --raid-devices=3 --add /dev/xvdi);

   Observe que no parâmetro '--raid-devices' você tem que preencher o número de discos/partições que seu RAID será composto. Em caso de dúvida rode um 'cat /proc/mdstat'.

IMPORTANTE: Considere neste passo o nível de RAID, pois alguns tipos de RAID não podem crescer sem que o novo disco fique em spare, então se este caso é o seu minha sugestão é criar um novo array de RAID e depois  vincular ao LVM, claro que para isso você precisará de mais discos. 

3 - Desativar o LVM(umount /var/lib/pgsql/data/ ; swapoff -a ; lvchange -an  /dev/vg_dados/lv_swap ; lvchange -an  /dev/vg_dados/lv_pgdata);

  Aqui desativei o LV do swap também, pois no mesmo LVM está a swap do sistema operacional, talvez isso não se aplica ao seu caso. 

4 - Aguardar até que o novo disco seja sincronizado com os demais(watch -n1 "mdadm --detail /dev/md0);

    Com este comando você verá o percentual de execução do sincronismo do novo disco do RAID, quando todos estiverem sincronizados vá para o passo 8. 

    Mentira, é o 5. 

5 - Após fazer um resize do LVM(pvresize /dev/md0 ; vgextend vg_dados /dev/md0 ; lvextend -L+120G /dev/vg_dados/lv_pgdata ; lvchange -ay /dev/vg_dados/lv_pgdata ; lvchange -ay /dev/vg_dados/lv_swap ; swapon -a);

   Aqui estou fazendo um resize no PV e LV, no parâmetro '-L' utilize o tamanho do seu novo HD do RAID. 

6 - Aumentar o XFS(xfs_growfs /dev/mapper/vg_dados-lv_pgdata);

   Como meu sistema de arquivos é XFS utilizei o comando acima. 

7 - Reparar o XFS(xfs_repair -v /dev/mapper/vg_dados-lv_pgdata ; mount -a);

   Quando tentei iniciar o PostgreSQL sem executar o passo atual, o pg_ctl retornou que não conseguia gravar o 'postmaster.pid' em função da estrutura não estar limpa, então procurando na Internet descobri que era coisa do sistema de arquivos, o comando corrente resolveu o problema. 

8 - Aguardar o PostgreSQL consumir os WALs(tail -f /var/log/pgsql.log); 

   Chegou a hora de subir o PGSQL, se tudo deu certo é só alegria. Que foi o meu caso, tomara que seja o seu leitor. 

quinta-feira, 9 de maio de 2013

JBoss - RHQ/JON deployment em Domain mode EAP6 ou JBoss7.x

IMPORTANTE: Domain mode não suporte Bundles!!!

Deployment de novas aplicações

PASSO 1

Acesse o link do JON/RHQ no seu navegador e preencha os campos com as credenciais.

PASSO 2


Acesse o 'Inventory', selecione o item 'Servers' do inventário e clique no recurso 'EAP Domain Controller'.


PASSO 3


Após clique com o botão direito no item do 'Domain Controller' e com o botão esquerdo em 'DomainDeployment'.

PASSO 4


Informe a versão da aplicação que está realizando o deploy, faça o upload da mesma:




PASSO 5


Avance a tela e clique em 'Finish'. Aguarde até que a aplicação que realizaste deploy esteja disponível no item 'DomainDeployment', isto pode levar alguns minutos.



PASSO 6

Agora você precisa designar ao grupo de servidores a aplicação, então clique com o botão direito do mouse sobre a aplicação que criaste, selecione 'Operations' e após clique em 'Assing to Server-group'.



Selecione o Server-grop do seu ambiente, marque o 'Yes' como Enabled, no Schedule marque 'Now' e após clique em 'Schedule'. 

* Imagens extraídas da documentação oficial da RedHat.