Sunday 18 September 2011

Você NÃO precisa de CPUAffinity, nBackup, shadow e multifile databases

Neste momento no site www.firebirdnews.org existe uma pesquisa sobre quais funcionalidades do Firebird você usa em ambientes de produção.
Algumas respostas são indícios de perigo, principalmente para os que rodam grandes bases de dados Firebird.



Bem, mas o que há de errado com estas respostas?
1. CPUAffinity. CPUAffinity pode ser usada para que o processo do Firebird SuperServer rode somente em CPU/núcleo (core) especificos. Hoje em dia temos múltiplos núcleos mesmo em estações de trabalho convencionais, então não há razão para usar a arquitetura SuperServer e limita-lo a uma única CPU - use as arquiteturas SuperClassic ou Classic e deixe o Firebird rodar com potência máxima. 


2.  NBackup. Nós da IBSurgeon não recomendamos que nossos clientes usem o NBackup sem um monitoramento externo e como a única forma de back-up. NBackup faz uma cópia física do banco de dados no nível das páginas de dados  - é rápido, mas, ao contrário do gbak, não há nenhuma verificação do conteúdo das páginas. Se você usa apenas o NBackup e não executa as rotinas de manutenção do banco de dados (pelo menos sweep's regulares combinados com monitoramento dos contadores de transação), e algum dia sua base de dados seja corrompida (devido a problemas de memória RAM ou um desligamento anormal por exemplo) , NBackup continuará a fazer e sobre-escrever "backups".  Um outro perigo são arquivos de delta "congelados", quando uma base de dados não é desbloqueada (unlocked) corretamente, e todas as mudanças são escritas para o arquivo de delta, e não possa ser feito o "merge" devido a um problema no arquivo de delta.  Pra usar os benefícios oferecidos pelo NBackup e evitar seus perigos, nós definimos esquemas especiais de back-up para nossos clientes.


3. Shadow é completamente inútil em ambientes de produção modernos. Ele oferece proteção para apenas um tipo de corrupção - uma falha ocasional crítica do HD (assumindo que o shadow está configurado corretamente em dois HD's separados).  ao invés do Shadow, use RAID5 (ou RAID10)  - será muito mais rápido e mais conveniente para manutenção.

4. Multifile database. Atualmente não há razão para ter bancos usando múltiplos arquivos. Usando-se múltiplos arquivos é impossível usar o NBackup, as bases de dados ficam presas aos locais físicos definidos, e não existe vantagem alguma. É comum que bases de dados com arquivos múltiplos sejam usadas para que se possa copiar os arquivos diretamente para DVD's, mas você precisa parar o serviço do Firebird durante esta operação.

Resumo
Claro que este post é uma provocação, e algumas vezes o administrador precisa usar CPUAffinity, NBackup e outras funcionalidades (menos shadow !), para que possa alcançar algum objetivo específico, mas isto precisa ser feito corretamente e com total conhecimento e entendimento dos passos que precisam ser dados. NBackup é a ferramenta mais útil, e geralmente subestimada, tanto em termos de recursos quanto possíveis problemas que pode ocasionar.

Maiores informações:
"Firebird's Big Database" presentation at slideshare:

Arquitetura Multi-Geracional (Multigenerational Architecture) resumida


Não existem documentos sobre como a MGA (Multigenerational Architecture) funciona. Neste documento tentaremos descreve-la da forma mais simples.
 
MGA em duas palavras
Multi-Geracional significa que podem existir muitas versões de cada registro na base de dados. As versões são criadas pelo engine da seguinte maneira:

Fig. 1
Imagine que exista uma tabela com vários registros, e que existe uma transação TR1 lendo um registro.

Fig. 2

Neste momento entra em jogo uma outra transação TR2. Ela pode (e vai) ler o mesmo registro. Mas, se a TR2 modificar este registro com uma sentença de UPDATE,
Fig. 3
o engine criará uma nova versão deste registro e o armazenará próximo ao registro original.

Fig. 4

Uma vez que o cabeçalho de todos os registros e de suas versões possuem informação sobre a transação que o criou, a visibilidade das versões são determinadas comparando os números das transações (a que lê com a que criou o registro).


Simples assim.

nota: novas versões de registros são criadas como “deltas”, i.e. a diferença entre o registro antigo e o novo. Este é o motivo pelo qual armazenar versões é muito eficiente e na maioria dos casos menos do que o dobro do tamanho do registro original.
 
Histórico da MGA
Claro que não é tão simples assim. Na verdade é um pouco mais complexo. Vamos nos aprofundar nos detalhes.

Estado das transações

Quando o servidor recebe um comando para iniciar uma transação ele faz o seguinte:
  • Gera um novo número para a transação. É um simples contador auto-incrementado; você pode ver o número do contador “next transaction” no relatório de estatísticas do banco de dados (use gstat –h ou IBStat para visualizar).
  • Aloca 2 bits na Transaction Inventory Page (TIP) – um tipo especial de página do banco de dados usado para armazenar os estados das transações.

Todos os registros ou versões de registros criados por esta transação será marcado por este número. Outras transações verificarão o estado na TIP quando lerem estas versões.

Existem quatro estados para uma transação (dois bits = quatro estados):

  1. Active (Ativa) – transações ainda em execução
  2. Committed – transação finalizada e “comitada”
  3. Rolled back – transação finalizada mas desfeita (rollback)
  4. In Limbo (No limbo) – Preparada, mas aguardando um Commit

O quarto estado só pode ocorrer durante um Commit em Duas Fases (Two Phase Commit) que ainda não foi finalizado. Neste artigo não falaremos sobre Commit em duas fases devido a ser uma funcionalidade raramente usada.

Visibilidade das versões
Como foi dito na primeira parte a visibilidade dos registros ou de suas versões são determinadas pela leitura do número da transação no cabeçalho do registro. Isto parece complicado, mas na verdade é algo bem simples. Por exemplo, se a transação está ativa ela pode visualizar, ao menos, todas as versões criadas por ela mesma (marcadas com o número 500) e todas as versões criadas por transações anteriores (com números menores que 500) que já foram comitadas (estado “committed”na TIP).
 
Read Committed e Snapshot
InterBase/Firebird possuem apenas dois tipos principais de transações (níveis de isolamento) - ReadCommitted e Snapshot. Veja a diferença:

  • Transações ReadCommitted olham no TIP global para verificar os estados das transações concorrentes.
  • Transações Snapshot fazem uma cópia local do TIP global quando iniciam, e usam apenas esta cópia local para verificar o estado das transações concorrentes

Portando transações ReadCommitted podem ver a mudança de estado de qualquer transação subsequente. Como no exemplo acima, se a versão do registro foi criada por uma transação com um número maior que 500 e está comitada então a transação 500 poderá visualizar esta versão do registro.

Transações snapshot não podem ver novas transações, ou mudança de estado das transações, uma vez que usa a cópia local do TIP que não é atualizada pelo engine. Este é o motivo pelo qual se uma transação ler a versão do registro criada pela transação 520 que não está na cópia local do TIP, portanto seu estado não pode ser determinado e a versão não poderá ser vista.

Nota: "versão pode ser vista pela transação" na verdade quer dizer que o engine pode enviar esta versão para o cliente como um registro real. Claro que cada transação independente do seu nível de isolamento pode ver todas as versões de todos os registros.

Agora podemos criar a fórmula de “visibilidade de versões”:

Uma versão do registro estará visivel se:
(tr_id = número da transação da versão do registro que está sendo lida)

  • tr_id = própria transação (i.e. a própria transação criou esta versão)
  • tudo onde tr_id < própria transação, que estejam comitadas (para qualquer tipo de transação)
  • tudo onde tr_id > própria transação, que estejam comitadas, se a transação for read_committed
 
Quantas versões ?
De quantas você gostaria ? Cada atualização de um registro gera uma nova versão de registro. Então podem existir muitas versões comitadas para um registro (nós já vimos por volta de 1.500.000 versões de um registro em uma base de dados). Mas só pode existir uma versão de registro não comitada.

Se duas transações tentarem atualizar o mesmo registro, a primeira terá sucesso, mas a segunda terá uma mensagem de erro de deadlock. Este é o único caso em que transações são conflitantes no InterBase/Firebird.

Commit, Rollback

Em alguns casos o servidor pode mudar o estado da transação por conta própria, mesmo que a transação foi finalizada corretamente ou se a conexão entre servidor e cliente for interrompida.

  1. Commit nunca é convertido para nada. Isto é fato.
  2. Rollback pode ser convertido para um Commit, se:
    1. Não houve nenhuma mudança de dados na transação
    2. Todas as mudanças na transação foram canceladas por uma exceção (erro ou chamada explicita de exceção)
    3. Não sejam muitas alterações durante a transação("Não sejam muitas" foi aumentado na versão 7.1 SP 1 do InterBase, e agora quer dizer 100 mil registros atualizados), no Firebird e versões antigas do Interbase é por volta de 60 mil.
  3. O estado Active da transação será convertido para Rollback (ou Commit, vejam item 2), se o servidor achar que a conexão da transação foi interrompida.

Vale mencionar o novo comportamento para transações "read only read committed" – a partir do InterBase 6.0 (em todas as versões do Firebird e Interbase posteriores) tais transações são iniciadas com o estado como “committed”, ao invés de “active”, e podem durar para sempre sem afetar a performance do servidor, processo de garbage collection, etc.
 
Arrumação da casa de forma cooperativa
Você pode estar pensando agora que se as versões são criadas de forma constante então o banco de dados irá crescer de forma constante até que você não poderá trabalhar com este banco de dados.

Claro que o servidor possui mecanismos para eliminar versões de registros desnecessárias. Este mecanismo é chamado de "cooperative garbage collection" (coleta de lixo cooperativa).

O mecanismo funciona como “se alguém quer comer, precisa lavar os pratos”.

Quando o servidor lê um registro, como dito anteriormente, ele lê um conjunto de registros e suas versões (uma vez que a base de dados é lida por páginas e não por registros, e na maioria dos casos os registros e suas versões estão na mesma página). Agora o servidor poderá executar duas tarefas principais:

  1. Determinar quais versões estão visíveis para a transação atual e enviar a versão completa ao cliente
  2. Encontrar versões que não são mais necessárias para nenhuma transação e elimina-las.

A primeira tarefa é simples e já falamos sobre ela. A parte complicada da história é que o registro enviado para o cliente é o resultado de uma combinação do registro original e todas as versões visíveis (devido ao fato de que as versões são apenas deltas). Esta é a razão pela qual quanto mais versões mais lento será o retorno de registros pelo servidor (mas esperamos que não proporcionalmente a quantidade de versões).

A próxima tarefa é resolvida usando um outro caminho. O primeiro passo (ou passo zero se preferir) o servidor armazena não apenas o número da next transaction, mas todos os números necessários para dar suporte a TIP.

Que são:

  • Oldest Active Transaction (OAT) – O menor número de transação que ainda está ativa.
  • Oldest Interesting Transaction (OIT) – O menor número de transação que não está comitada. Este pode ser o OAT ou o número da transação mais antiga que esteja em “Rollback” ou “In Limbo”.
  • Oldest Snapshot Transaction (OST) – é um pouco mais complexo do que simplesmente o número do snapshot:
    • "read only read committed" não tem este número
    • "read write read committed" possuem OST igual ao seu próprio número
    • snapshots” possuem OST igual a OAT

Se uma transação encontra versões que estão comitadas e possuem transação menor do que OST, o engine pode remover esta versão. Isto não é feito sobre dados existentes e uma queda do sistema não vai gerar perda de versão..

Além disso – Cooperação também é usada para encontrar transações de conexões interrompidas – Seu estado é convertido de active somente quando outra transação lê versões e lê a TIP para determinar o estado da transação. Como você pode ver, o servidor não força um processo de garbage collection nem detecta conexões interrompidas.
 
Resumo
Isto é tudo o que pode ser dito sobre a MGA de forma sucinta. Esperamos que você tenha entendido tudo desde o início. Se você quiser se aprofundar mais – leia os documentos relacionados.

Ficou com alguma dúvida, pergunte-nos (support@ib-aid.com )


Links


IBTransaction Monitor – Guia de Usuário


De que é composto ?

O pacote do IBTM contém três módulos principais:
  • Monitor – Um pequeno programa Win32 que roda como serviço para monitorar e gravar os estados das transações de uma ou mais bases de dados.
  • Configuration Assistant (Assistente de Configuração) Para configurar as opções do Monitor e do Viewer.
  • Visualizer (Visualizador) Feito para visualizar e analisar a informação gravada pelo Monitor.

Monitor

O Monitor é um pequeno programa Win32 que roda como serviço (ibtm.exe), nas plataformas Windows NT, 2000, XP e Windows 2003. Ao iniciar o Monitor lê as configurações (definidas pelo Configuration Assistant) e monitora os bancos de dados especificados. Durante o monitoramento é feita uma conexão com o banco de dados a cada minuto (intervalo padrão recomendado) e lê o estado das transações.
Se o InterBase ou Firebird está rodando no Windows, o monitor pode ser instalado no próprio servidor onde o banco de dados está. Para servidores baseados em Linux, ele pode ser instalado em qualquer estação com Windows que tenha acesso via rede ao servidor Interbase ou Firebird. Pode-se instalar o Monitor usando os seguintes métodos:
  1. Pelo Configuration Assistant
  2. Usando a linha de comando
Importante! Antes de iniciar a instalação do Monitor é importante se assegurar de que esteja logado no Windows com uma conta com direitos e privilégios de Administrador. O Monitor será instalado como um serviço com início automático na conta de usuário Localsystem.
Uma vez que o Monitor somente se conecta periodicamente ao banco de dados e grava as informações recuperadas em um arquivo de log de texto puro, não há necessidade de criar nenhuma regra adicional. Porém, se você deseja aumentar a segurança do Monitor em relação aos dados de configuração e login no banco de dados, você deverá modificar as configurações do serviço do Monitor e do seu diretório (e do diretório onde é gravado o arquivo de log) de acordo com sua necessidade utilizando o Painel de Controle de Serviços do Windows.
Os parâmetros de configuração do Monitor será discutidos na seção sobre o Configuration Assistant.

O que está sendo gravado ?

O Monitor periodicamente verifica a Header Page do banco de dados e então armazena as informações referentes as transações no arquivo de log, da seguinte maneira:
  • Log time (Momento da informação)
  • Oldest transaction number (Número da Oldest transaction)
  • Oldest snapshot transaction number (Número da Oldest snapshot transaction)
  • Oldest Active transaction number (Número da Oldest Active transaction)
  • Next transaction number (Número da Next Transaction)
  • Active transactions count (depends on server version) (Número de transações ativas – Depende da versão do servidor)
Estas informações são armazenadas no arquivo de log, e podem ser visualizadas e analisadas usando o Visualizer. Usando a visualização das informações dos estados das transações você pode entender por quanto tempo suas aplicações mantém as transações abertas, quantas transações simultâneas são executadas, por quanto tempo as transações do tipo Snapshot são executadas, quando é o período de maior atividade que as aplicações iniciam as transações, etc.
Nota: O Monitor grava as informações do estado das transações em um arquivo local. Estas informações, bem como qualquer outra, não são enviadas pela rede.

Configuration Assistant

O Configuration Assistant (ibtmconfig.exe) é usado para definir os parâmetros de configuração para o Monitor e o Viewer. Ele salva os parâmetros de configuração no arquivo ibtm.ini.
Dica ! O IBTM não usa o Registry para armazenar os parâmetros de configuração, desta forma você pode preparar a configuração em um computador e então copiar o arquivo ibtm.ini para outro computador onde o Monitor esteja instalado (O serviço do Monitor precisa ser re-iniciado para que sejam lidas as novas configurações).
O instalador do IBTM coloca os arquivos do Monitor e do Configuration Assistant no mesmo diretório IBTM, desta forma eles compartilham o mesmo arquivo de configuração (ibtm.ini).

Usando o Configuration Assistant

O Configuration Assistant te auxilia a
  • Gerenciar o serviço do Monitor – instalar, remover, iniciar, parar, pausar e re-iniciar.
  • Especificar quais servidores InterBase ou Firebird possuem bancos de dados que precisam ser monitorados
  • Especificar quais bancos de dados e opções de agendamento e monitoramento para os bancos de dados de forma particular

Gerenciando o serviço do Monitor

IBTM service stopped
Quando o Configuration Assistant inicia, ele determina o estado do serviço do Monitor no sistema.
Instalando o Monitor – Se o serviço do Monitor não estiver instalado, você verá a mensagem "not installed" (“não instalado”) no texto de estado do IBTM no canto superior esquerdo. Para instalar o Monitor clique no botão Install (Instalar) no lado direito da janela. Note que o serviço do Monitor usara o tipo de inicialização (automática ou manual) definido no checkbox. Se a instalação do serviço do Monitor for feita com sucesso você verá o estado do IBTM mudar para “stopped” (“parado”) e o botão "Run service" (“Executar o serviço”) será habilitado.
Você precisará re-iniciar o serviço do Monitor toda vez que modificar os parâmetros de configuração de qualquer base de dados.

Configurando Servidores

O IBTM usa o protocolo de comunicação TCP/IP para acessar as bases de dados para monitoramento. Portanto, se você instalou o Monitor no mesmo computador em que o servidor de banco de dados está você precisa adicionar o servidor como localhost ou com o nome do computador hospedeiro do serviço (host name). Uma vez feito isso você poderá adicionar bases de dados que serão monitoradas neste servidor.

Configurando bases de dados

Com está versão do IBTM você poderá monitorar apenas um servidor. Por favor, contate-nos para maiores detalhes (www.ibsurgeon.com).
Use os botões Add Server (adicionar servidor) e Add Database (adicionar base de dados) no lado esquerdo da lista de bases de dados.
Cada registro de base de dados pode ser configurado com os seguintes parâmetros::
Método de aquisição das estatísticas. Depende de cada versão do servidor e oferece diferentes características. Cada método será descrito mostrando os prós e os contras:

Services API
Firebird API
Tabela TMP$
InterBase 4.x/5.x
não
não
não
InterBase 6.0 Classic
não
não
não
Firebird Classic - 1.0, 1.5.0, 1.5.1, 1.5.2
não
sim
não
InterBase 6.x SuperServer
sim
não
não
InterBase 7.x/2007
sim
não
sim
Firebird Classic e SuperServer: 1.5.3, 1.5.4 /2.0
sim
sim
não
Diferenças entre Services API, Firebird API e tabela TMP$:


Método
Prós
Contras
Services API
API publica que foi introduzida no InterBase 6.0 SuperServer
  • Método universal, funciona com todas as versões do Interbase ou Firebird
  • Não afeta o valor de Next transaction
  • Não oferece a capacidade de saber quantidade real de transações ativas
  • Pode mostrar informações desatualizadas se o servidor não gravar com frequência a página de header da base de dados em disco (InterBase V7.1/V7.5)
Firebird API
Firebird a partir da versão V1.0 possui parâmetros adicionais para a chamada isc_database_info call, que permite o retorno de informação do estado da transação
Permite que seja apurada a informação da quantidade de transações ativas (não mais do que 4676 transações ativas)
  • Não é suportado por nenhuma versão do InterBase
  • Internamente incrementa o contador Next transaction. O Monitor incrementá o contador Next transaction em 1 cada vez que se conectar a base de dados. Se não existir nenhuma transação ativa na base de dados neste momento o contador Oldest Active, Oldest Snapshot e Oldest transaction não serão modificados.
    Se não existir qualquer atividade na base de dados, o Monitor por si só, incrementará o contador Next transaction em 1440 em 24 horas, se estiver configurado para verificar a base de dados a cada minuto.
    (A API do Firebird não tem está característica. Este comportamento é específico do método isc_database_info)
Tabelas TMP$Foram introduzidas no InterBase 7.0 para permitir o monitoramento do estado do servidor
  • Permite o obtenção do estado atual das transações no InterBase V7.x/V2007, incluindo a contagem de transações ativas.
  • Para obter as informações sobre o estado das transações o Monitor inicia uma transação com os parâmetros read_only read_committed rec_version. Este tipo de transação não é tratada como uma transação ativa para efeitos de contabilização, e não afeta o servidor e sua performance.
  • Suportado apenas pelo InterBase V7.0/V7.1/V7.5/V2007
  • Assim como na API do Firebird API incrementa o contador Next transaction, porque para ler os dados das tabelas TMP$ é necessário ser executado dentro de uma transação.
  • Tempo de acesso é um pouco maior do que usando a Services API (< 0.5 seconds).
Se você escolher um método de coleta de estatísticas que é incompatível com seu servidor nada será escrito no log de transações do Monitor. Verifique a aba Error Log no Configuration Assistant para ver os erros que podem acontecer durante o monitoramento das bases de dados.
Check Interval (intervalo de verificação). Determina qual a frequência que o Monitor buscará as informações do estado das transações da base de dados. 1 minute é o intervalo mínimo. Você pode definir em 1, 3, 5, 10, 20, 30 minutos, ou até intervalos de 1 hora, mas o intervalo que oferece dados mais informativos é 1 ou 3 minutos.
Usando o intervalo de 1 minuto, o Monitor verificará os estados das transações da base de dados 1440 vezes a cada 24 horas. O tamanho do arquivo de log será de aproximadamente 150 Kilobytes.
Start time e End Time (Tempo de Inicio e Tempo de Fim). Se não for definido o Monitor buscará as estatísticas do banco especificado o tempo todo que estiver rodando. Se o tempo limite for definido o Monitor buscará as informações apenas dentro deste período especificado.
User name e Password (Nome do usuário e Senha). Definem o nome de usuário e senha da conta que será usada para monitoramento do estado das transações da base de dados. Você pode usar a conta SYSDBA ou qualquer outra conta que quiser. Se for usar uma conta diferente de SYSDBA, para usar os métodos da Services API ou Firebird API não é necessário conceder nenhum tipo de privilégio adicional a esta conta. Você pode criar um usuário como 'TM', e o Monitor funcionará com sucesso.
Para o método que usa Temporary System Tables (IB V7.1, V7.5, V2007) a conta de login precisará ter acesso de leitura as tabelas $TRANSACTIONS e TMP$DATABASES. Se você usa Embedded User Authentification (autenticação embutida de usuário) nas bases de dados do InterBase V7.5/V2007, você precisará criar uma conta e conceder os privilégios de acesso para aquela base de dados explicitamente.

Thursday 18 August 2011

Exemplo de como um índice ruim pode afetar a performance de um banco de dados Firebird


Sumário

Este pequeno artigo mostra através de exemplos como um índice ruim pode afetar a performance de um banco de dados Firebird



Banco de dados de testes


Imagine que você tem um banco de dados bem simples do Firebird

CREATE DATABASE “E:\TESTFB25INDEX.FDB” USER “SYSDBA” PASSWORD “masterkey”;
CREATE TABLE TABLEIND1 (
    I1            INTEGER NOT NULL PRIMARY KEY,
    NAME          VARCHAR(250),
    MALEORFEMALE  SMALLINT
);
CREATE GENERATOR G1;
SET TERM ^ ;

create or alter procedure INS1MLN
returns (
    INSERTED_CNT integer)
as
BEGIN
inserted_cnt = 0;
WHILE (inserted_cnt <1000000) DO
BEGIN
 Insert into tableind1(i1, name, maleorfemale) values(gen_id(g1,1), 'TEST name', (:inserted_cnt - (:inserted_cnt/2)*2));
 inserted_cnt=inserted_cnt+1;
END
suspend;
END^
SET TERM ; ^
GRANT INSERT ON TABLEIND1 TO PROCEDURE INS1MLN;
GRANT EXECUTE ON PROCEDURE INS1MLN TO SYSDBA;
COMMIT;

Por alguma razão você precisa:
·         inserir 1 milhão de registros
·         atualizar todos estes registros
·         apagar todos os registros
·         executar um select count(*) neste banco de dados

Para executar esta tarefa, nós podemos usar o script abaixo (que será executado imediatamente após o script de criação do banco de dados) :

set stat on;  /*show statistics*/
select * from ins1mln;
update tableind1 SET MALEORFEMALE = 3;
delete from tableind1;
select count(*) from tableind1;

guardaremos os resultados para análise posterior.

Então criaremos outro banco de dados com a mesma estrutura, mas adicionaremos um índice na coluna MALEORFEMAIL

CREATE INDEX TABLEIND1_IDX1 ON TABLEIND1 (MALEORFEMALE);

Como pode notar, vamos inserir apenas os valores 0 ou 1 nesta coluna.


Repetiremos a execução do script no banco de dados com este índice e compararemos os resultados.

Resultados estão na tabela a seguir:
Sem o índice para MALEORFEMAIL
Com o índice para MALEORFEMALE
SQL> set stat on; /*show statistics*/
SQL> select * from ins1mln;

INSERTED_CNT
============
     1000000

Current memory = 10487216
Delta memory = 80560
Max memory = 12569996
Elapsed time= 13.33 sec
Buffers = 2048
Reads = 0
Writes 18756
Fetches = 7833503
SQL> update tableind1 SET MALEORFEMALE = 3;
Current memory = 76551788
Delta memory = 66064572
Max memory = 111442520
Elapsed time= 15.04 sec
Buffers = 2048
Reads = 16166
Writes 15852
Fetches = 6032307
SQL> delete from tableind1;
Current memory = 76550240
Delta memory = -1548
Max memory = 111442520
Elapsed time= 3.27 sec
Buffers = 2048
Reads = 16147
Writes 16006
Fetches = 5032277
SQL> select count(*) from tableind1;

       COUNT
============
           0

Current memory = 76552064
Delta memory = 1824
Max memory = 111442520
Elapsed time= 1.35 sec
Buffers = 2048
Reads = 16021
Writes 1791
Fetches = 2032278
SQL> set stat on; /*show statistics*/
SQL> select * from ins1mln;

INSERTED_CNT
============
     1000000

Current memory = 10484140
Delta memory = 75524
Max memory = 12569996
Elapsed time= 23.94 sec
Buffers = 2048
Reads = 1
Writes 23942
Fetches = 11459599
SQL> update tableind1 SET MALEORFEMALE = 3;
Current memory = 76548712
Delta memory = 66064572
Max memory = 111439444
Elapsed time= 29.30 sec
Buffers = 2048
Reads = 16167
Writes 19492
Fetches = 10035948
SQL> delete from tableind1;
Current memory = 76547164
Delta memory = -1548
Max memory = 111439444
Elapsed time= 3.41 sec
Buffers = 2048
Reads = 16147
Writes 15967
Fetches = 5032277
SQL> select count(*) from tableind1;

       COUNT
============
           0

Current memory = 76548988
Delta memory = 1824
Max memory = 111439444
Elapsed time= 0.69 sec
Buffers = 2048
Reads = 16021
Writes 1901
Fetches = 2032278

Portanto, um índice ruim reduz a performance aproximadamente 2 vezes durante inserções e atualizações. Também podemos notar que um índice pouco seletivo aumenta o número de escritas e fetches de registros.


Vamos analisar as estatísticas deste banco de dados de exemplo (com o índice ruim para MALEORFEMALE) e olhar os detalhes.

Para obter as estatísticas, rodaremos o seguinte comando:
gstat -r e:\testfb25index.fdb > e:\teststat.txt


A seção para a tabela TABLEIND1 e seus índices tem informações intrigantes, mas que tipo de informação útil nos fornece ?  


TABLEIND1 (128)
    Primary pointer page: 166, Index root page: 167
    Average record length: 0.00, total records: 1000000
    Average version length: 27.00, total versions: 1000000, max versions: 1
    Data pages: 16130, data page slots: 16130, average fill: 93%
    Fill distribution:
       0 - 19% = 1
      20 - 39% = 0
      40 - 59% = 0
      60 - 79% = 0
      80 - 99% = 16129

    Index RDB$PRIMARY1 (0)
      Depth: 3, leaf buckets: 1463, nodes: 1000000
      Average data length: 1.00, total dup: 0, max dup: 0
      Fill distribution:
           0 - 19% = 0
          20 - 39% = 0
          40 - 59% = 0
          60 - 79% = 1
          80 - 99% = 1462

    Index TABLEIND1_IDX1 (1)
      Depth: 3, leaf buckets: 2873, nodes: 2000000
      Average data length: 0.00, total dup: 1999997, max dup: 999999
      Fill distribution:
           0 - 19% = 0
          20 - 39% = 1
          40 - 59% = 1056
          60 - 79% = 0
          80 - 99% = 1816

Para entender o significado dos números e porcentagens, podemos usar a ferramenta IBAnalyst, que nos oferece uma interpretação visual das estatísticas do banco de dados:


Clicando em "Reports/View recommendations" acharemos a explicação apropriada para este índice:


Bad indices count: 1.
By `bad` we name indices with many duplicate keys (90% of all keys)
and big groups of equal keys (30% of all keys). Big groups of equal
keys slowdown garbage collection - MaxEquals here is % of max groups
of keys having equal values. Index search for such an index is not
efficient. You can drop such indices (if they are not on FK
constraints).
Contagem de índices ruins:1.
Chamamos de "Ruins" os índices com muitos valores duplicados
(90% de todas as chaves) e com grupos grandes de chaves iguais
(30% de todas as chaves). Grupos grandes de chaves iguais fazem
com que o processo de garbage collection seja mais lento - MaxEquals
é o % de grupos que contém chaves com o mesmo valor. Pesquisas indexadas
usando tais índices não são eficientes. Você pode remover estes índices
(se eles não forem criados por constraints de FK)

Index          ( Relation)  Duplicates  MaxEquals
TABLEIND1_IDX1 (TABLEIND1) :   100%,      50%

Em bancos de dados de produção é comum ver muitos índices ruins, o que pode afetar de forma dramática a performance do banco de dados. Neste exemplo vemos uma tabela com 13 milhões de registros que possui 7 índices ruins, que são (provavelmente) inúteis e degradam significativamente a performance do Firebird.




Geralmente a primeira ação do desenvolvedor é remover estes índices ruins. Por outro lado, é possível que o índice ruim seja usado em algumas queries especiais, que precisam de uma combinação específica de índices (incluindo o índice ruim) para que possa ser executada de forma suficientemente rápida.

O Otimizador SQL do Firebird Firebird possui regras complexas, e é comum que removendo o índice ruim faça com que algumas queries tenham a performance significativamente degradada.


Claro que estas queries devem ser reescritas de forma a usar um plano melhor sem o índice ruim, mas ainda temos um problema: como encontrar estas queries que usam o índice ruim ? 


A resposta está no próximo episódio da série de nossos artigos sobre Otimização.