terça-feira, 28 de dezembro de 2010

PostgreSQL migrando uma database de tablespace


    No PostgreSQL diferente do Oracle uma tablespace é um local de armazenamento de dados do banco, deste modo imagine o seguinte cenário: 

Dois HDs, um onde está a pg_default(tablespace padrão do PostgreSQL) com 99% de uso e o outro HD com uma boa capacidade e performance com 1% de uso. Então chegou a hora de migrar uma de suas databases para outra tablespace.

Primeiro criamos uma nova tablespace apontando para o path do novo HD: 

CREATE TABLESPACE "external_data" OWNER postgres
LOCATION '/data/Archives1/pg_external_data';

Onde '/data/Archives1/pg_external_data' é o caminho absoluto da nova tablespace. 

Verificação das tablespaces existentes:

SELECT spcname AS Tablespace, pg_size_pretty(pg_tablespace_size (spcname)) AS Tamanho, spclocation AS Caminho
FROM pg_tableSpace;

tablespace | tamanho | caminho
---------------+---------+----------------------------------
pg_default | 53 GB |
pg_global | 609 kB |
external_data | 4 bytes | /data/Archives1/pg_external_data
(3 rows)

A saída acima é um exemplo.


Agora o índio veio tem dois caminhos, um migra-se objeto por objeto(tabela, índice e la la la), ou faz um dump da database e se importa depois, vamos pelo caminho mais fácil, que segue:

Criação do dump:


$ nohup /opt/pgsql/bin/pg_dump -h 127.0.0.1 -p 5432 -U postgres -f /data/Archives1/mydump-12-08-2010.dmp my_db &


Criação da database auxiliar:


CREATE DATABASE test
WITH ENCODING='UTF8'
OWNER=postgres
CONNECTION LIMIT=-1
TABLESPACE=external_data;

Agora vamos importar as crianças:

$ nohup /opt/pgsql/bin/psql -U postgres -h 127.0.0.1 test < /data/Archives1/mydump-12-08-2010.dmp &

Virando a chave:

Ativando database auxiliar para produção:


ALTER DATABASE my_db RENAME TO my_db-old;
ALTER DATABASE test RENAME TO my_db;

Liberação de espaço ao dropar database em desuso:


DROP DATABASE my_db-old;

sexta-feira, 3 de dezembro de 2010

MySQL - Salvando o Innodb corrompido no MySQL

O que fazer quando seu MySQL 5.x retorna a seguinte mensagem no log:


101203 14:32:07  InnoDB: Page checksum 3473125734, prior-to-4.0.14-form checksum 1097980787
InnoDB: stored checksum 3473125734, prior-to-4.0.14-form stored checksum 143311008
InnoDB: Page lsn 0 3817445983, low 4 bytes of lsn at page end 3816433326
InnoDB: Page number (if stored to page already) 1115,
InnoDB: space id (if created with >= MySQL-4.1.1 and stored already) 138
InnoDB: Page may be an index page where index id is 0 373
InnoDB: (index PRIMARY of table prod/item)
InnoDB: Database page corruption on disk or a failed
InnoDB: file read of page 1115.
InnoDB: You may have to recover from a backup.
InnoDB: It is also possible that your operating
InnoDB: system has corrupted its own file cache
InnoDB: and rebooting your computer removes the
InnoDB: error.
InnoDB: If the corrupt page is an index page
InnoDB: you can also try to fix the corruption
InnoDB: by dumping, dropping, and reimporting
InnoDB: the corrupt table. You can use CHECK
InnoDB: TABLE to scan your table for corruption.
InnoDB: See also InnoDB: http://dev.mysql.com/doc/refman/5.0/en/forcing-recovery.html
InnoDB: about forcing recovery.
InnoDB: Ending processing because of a corrupt database page.


Primeiro para subir o banco, altere o argumento dentro do arquivo my.cnf:
innodb_force_recovery = 4
Salve o arquivo e inicie o MySQL.
Após faça um dump completo de todas as databases:
# mysqldump -A -u root -v > /tmp/all.dmp
Com um dump em mãos podes DROPAR todas as databases. Mas antes faça uma lista delas:
# mysql --batch --skip-column-names -u root -e "SHOW DATABASES" > /tmp/mysql.db.list
# for db in `cat /tmp/mysql.db.list`; do mysql -u root -e "DROP DATABASE $db";   echo -e "Dropped $db"; done
Mova o diretório base do MySQL para exemplo 'data-old'. 
Recrie a pasta 'data' do MySQL e coloque como owner desta pasta o usuário de execução do 'mysqld'. 
Inicialize uma nova database MySQL e importe o dump criado anteriormente:
# mysql -u root < /tmp/all.dmp

Banco recriado e informações persistidas sem mais erros de 'Innodb'. 

sábado, 27 de novembro de 2010

PostgreSQL - Usando o pgbench

Como todos sabem, o 'pgbench' é uma ferramenta muito útil para testar tuning no PostgreSQL, lembrando que para comparar retornos do 'pgbench' deve-se ter o mesmo cenário antes e depois do tuning, não adianta executar o 'pgbench' em horário comercial e depois do tuning rodá-lo novamente na madrugada. O ideal é executá-lo quando o banco está dedicado a você.

Pacote que instala o 'pgbench' no RHEL: postgresql-contrib

Primeiro é necessário criar a database onde o 'pgbench' executará as transações, pode ser via 'psql' ou 'createdb'.

Para criar as estruturas na database que você criou:

# pgbench -i -s 10 database_criada -U postgres

-U neste caso é necessário porque estou executando o comando como 'root'.

Agora vamos ao teste:

# pgbench -c 90 -t 10 bmark -U postgres -n

-c 90 significa o número de clientes conectados, não deve superar o número de conexões disponíveis no seu postgresql.conf

-t 10 o número de transações por cliente

-n não executar autovacuum para iniciar as transações

Antes de executar um benchmark sugiro atualizar as estatísticas do banco, bem como os índices, para que melhore o plano de execução e tempo de consulta do seu banco de dados.

quarta-feira, 24 de novembro de 2010

Oracle - Solucionando erro ORA-00257

Problema: ORA-00257: archiver error. Connect internal only, until freed.

Sintoma: Aceita apenas conexões internas e rejeita externas, o banco está aberto, mas não aceita transações em razão de um redo log travado.

Passo 1 - Baixe a database.

SQL> SHUTDOWN IMMEDIATE;
ORACLE instance shut down.

Passo 2 - Inicie em modo de manutenção.

SQL> STARTUP MOUNT
ORACLE instance started.
Total System Global Area 1191182336 bytes
Fixed Size 2020320 bytes
Variable Size 318770208 bytes
Database Buffers 855638016 bytes
Redo Buffers 14753792 bytes
Database mounted.


Passo 3 (não execute) - Ocorre erro ao tentar "abrir" a database, mencionando que não
pode gravar o arquivo de redo.


SQL> ALTER DATABASE OPEN;
ALTER DATABASE OPEN
*
ERROR at line 1:
ORA-16038: log 1 sequence# 53 cannot be archived
ORA-19809: limit exceeded for recovery files
ORA-00312: online log 1 thread 1:
'/u01/app/oracle/product/10.2.0/oradata/orcl/redo01.log'


Passo 4 - Validando o destino e se está em modo archive, veja que o espaço da Flash
Recovery Area está lotado, causa do problema. 


SQL > SELECT * FROM V$RECOVERY_FILE_DEST;
NAME
--------------------------------------------------------------------------------
SPACE_LIMIT SPACE_USED SPACE_RECLAIMABLE NUMBER_OF_FILES
----------- ---------- ----------------- ---------------
/u01/app/oracle/product/10.2.0/flash_recovery_area/ 2147483648 2141670912 0 50

Passo 5 - Mova os archives antigo da Flash Recovery Area para outro local. E inicie o
RMAN.

$ rman target sys/senha@SID

Passo 6 - No RMAN valide os archives e apague os antigos.

RMAN > CROSSCHECK ARCHIVELOG ALL;
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=151 devtype=DISK
RMAN > DELETE EXPIRED ARCHIVELOG ALL;


Passo 7 - Saia do RMAN e conectado na instância localmente altere o tamanho da Flash
Recovery Area.


RMAN > exit;
SQL> ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE=4294967296 SCOPE=BOTH;

Passo 8 - Suba a database, alterando o estado para 'OPEN'.
SQL> ALTER DATABASE OPEN;
Database altered..

PostgreSQL - Remover arquivos WAL antigos que já foram "backupeados"

Um problema para quem utiliza o PostgreSQL em modo archive é que depois de fazer backup dos mesmos com o 'pg_rman' eles permanecem no disco. Com o tempo o disco ficará cheio e o que era para ser uma solução vira uma dor de cabeça.

Com isso criei um script em Perl que remove os archives mais antigos que já foram "backupeados". Os archives serão apagados se e somente se o último backup com o 'pg_rman' esteja validado com o status de 'OK'. A variável '$opt_date' deve ser utilizada fornecendo o número de archives criados antes do último backup que deseja manter em disco.

Código:


#!/usr/bin/perl

# Author: Gabriel Prestes
# Last modified: 11-24-2010
# Fuction: Recycle WAL backed in PostgreSQL

($opt_date) = @ARGV;

        # --- Doc --- #
        # WAL after backup example: 00000001000000000000004B.00000020.backup
        # --- Doc --- #

        # --- VARS --- #
        my @list=();
        my @list2=();
        my @stat=();
        my $wal_path="/data/archive_log";
        my $backup_catalog="/var/lib/pgsql/backup_catalog";
        my $lastbkp="";
        my $cmd="";
        my $counter=0;
        my $flag=0;
        my $flagok=0;
        my $pointrecycle="";

        if(!$opt_date){

                print "ERROR - Please insert number of WAL to keep after recycle\n";
                exit(1);

        }

        @list=`/bin/ls -t $wal_path`;
        @list2=`/usr/bin/pg_rman show -B $backup_catalog`;

        # -- CHECK first WAL backup --- #
        foreach(@list){

                chomp($_);
                if($_ =~ ".backup"){

                        @stat=`/usr/bin/stat $wal_path/$_`;
                        foreach(@stat){chomp($_);if($_ =~ m/^Modify: (.+) [0-9].+$/){$lastbkp=$1;last;}}
                        print "BACKUP LOCATED $wal_path/$_ - done in '$lastbkp'\n";
                        $pointrecycle=$_;
                        last;

                }

        }

        # --- CHECK last backup done with success --- #
        foreach(@list2){

                chomp($_);
                if($_ =~ $lastbkp and $_ =~ "OK"){

                        print "LAST VALIDATED BACKUP IS '$_'\n";
                        $flagok++;
                        last;

                }

        }

        # --- Thresholds --- #
        if($flagok>0){

                # --- Remove old WALs --- #
                foreach(@list){

                        chomp($_);
                        if($flag == 1){$counter++;}
                        if($_ =~ $pointrecycle){$flag++;}
                        if($flag == 1 and $counter > 0 and $counter <= $opt_date){print "OLD WAL '$_' PRESERVED\n";}
                        if($flag == 1 and $counter > 0 and $counter > $opt_date){print "OLD WAL '$_' REMOVED\n"; $cmd=`/bin/rm -f $wal_path/$_`; sleep(01);}

                }

        }

        else{

                print "LAST BACKUP RUN WITH ERRORS\n - WALS FILES NOT REMOVED\n";

        }



# EOF