Tuesday, November 23, 2010

Hot Standby e Streaming Replication

A partir da versão 9.0 do PostgreSQL, é possível combinar hot standby e envio de registros (streaming replication) para compor uma solução de replicação. Os benefícios dessa solução são aqueles citados no artigo anterior (disponibilidade, confiabilidade, desempenho e/ou balanceamento de carga). Neste caso, teremos o envio assíncrono das transações realizadas no servidor principal para o servidor secundário e, além disso, o servidor secundário aceitará consultas somente leitura (consultas que não modificam dados, ou seja, não escrevem no log de transação tais como SELECT, COPY TO, BEGIN, END, ROLLBACK); comandos como INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, nextval() não são permitidos.  A figura abaixo ilustra esse cenário:



 A implementação de tal cenário está descrita abaixo. Vale lembrar que os pré-requisitos apresentados no artigo anterior são utilizados aqui.

No servidor principal, editamos o arquivo /bd/primario/postgresql.conf:

listen_addresses = '*'
wal_level = hot_standby
max_wal_senders = 1
wal_keep_segments = 20

O parâmetro listen_addresses permite que o servidor secundário se conecte ao servidor principal para receber os logs de transação. O parâmetro wal_level indica o nível de log de transação armazenado no servidor principal; minimal (padrão) não permite que este tipo de replicação seja realizada. O parâmetro max_wal_senders indica o número máximo de servidores secundários permitidos. O parâmetro wal_keep_segments só é necessário se (i) for realizar a cópia dos arquivos com o servidor principal em atividade e (ii) se houverem paradas programadas do servidor secundário; este valor deve ser maior do que o número de arquivos de log de transação gerados durante a cópia dos arquivos. Neste caso, 20 é igual a 320 MB (20 * 16 MB).

A modificação desses parâmetros exige que o PostgreSQL do servidor principal seja reiniciado. Assim se a sua janela de manutenção não é flexível, programe-se. A aplicação dessas modificações não implica ter que iniciar a replicação imediatamente.

postgres@principal:~$ pg_ctl restart -D /bd/primario
waiting for server to shut down.... done
server stopped
server starting


No servidor principal, crie uma role (usuário) para replicar os dados. O PostgreSQL exige que o usuário que realiza a replicação seja um super-usuário e possa se conectar ao servidor primário.

postgres@principal:~$ psql
psql (9.0.1)
Type "help" for help.
postgres=# CREATE ROLE usuario SUPERUSER LOGIN;
CREATE ROLE
postgres=# \q


Dependendo da sua política de segurança, você pode precisar definir uma senha para este usuário. Neste caso, a senha deve ser informada no arquivo de senhas (~postgres/.pgpass ou %APPDATA%\postgresql\pgpass.conf no Windows) ou na string de conexão no arquivo /bd/secundario/recovery.conf.

postgres@principal:~$ psql
psql (9.0.1)
Type "help" for help.
postgres=# \password usuario
Enter new password:
Enter it again:
postgres=# \q


No servidor principal, edite o arquivo /bd/primario/pg_hba.conf. O método de autenticação vai depender da sua política de segurança. Se você decidiu colocar senha para o usuário que realiza a replicação, utilize o método de autenticação md5; caso contrário, utilize o método de autenticação trust.

host    replication        usuario        10.1.1.2/32        md5


A adição de uma nova regra de autenticação, exige a recarga das regras de autenticação.

postgres@principal:~$ pg_ctl reload -D /bd/primario
server signaled


No servidor secundário, edite o arquivo /bd/secundario/postgresql.conf:

hot_standby = on


O parâmetro hot_standby indica se o servidor secundário aceita conexões.

No servidor secundário, crie o arquivo /bd/secundario/recovery.conf. Este arquivo conterá informações sobre a aplicação das modificações realizadas no servidor principal e transmitidas para o servidor secundário.

standby_mode = 'on'
primary_conninfo = 'host=10.1.1.1 port=5432 user=usuario password=minhasenha'
trigger_file = '/bd/secundario/failover.trg'


O parâmetro standby_mode especifica se o servidor PostgreSQL é um servidor secundário (standby). O parâmetro primary_conninfo especifica como se conectar ao servidor principal. A senha (especificado em password) pode ser omitida ali e especificada no arquivo de senhas (~postgres/.pgpass ou %APPDATA%\postgresql\pgpass.conf no Windows). Se o parâmetro trigger_file for utilizado, indica que a presença do arquivo citado (/bd/secundario/failover.trg), termina a recuperação, ou seja, o servidor passa a ser um servidor principal (que aceita quaisquer tipos de comandos; não somente aqueles comandos somente leitura).

No servidor principal, teremos que realizar a cópia física dos arquivos para o servidor secundário. Há duas maneiras de realizar esta cópia dependendo da disponibilidade do servidor principal:
  • com o servidor principal parado;
  • com o servidor principal em atividade.
Se o servidor principal puder ficar parado durante a cópia, podemos fazer:

postgres@principal:~$ pg_ctl stop -D /bd/primario
waiting for server to shut down.... done
server stopped
postgres@principal:~$ rsync -av --exclude postgresql.conf \
--exclude pg_hba.conf --exclude pg_xlog/* --exclude pg_log/* \
/bd/primario/ postgres@10.1.1.2:/bd/secundario
postgres@principal:~$ pg_ctl start -D /bd/primario
server starting


Caso o servidor principal não possa parar, podemos fazer todo processo online. Neste caso precisamos executar os seguintes comandos no servidor principal:

postgres@principal:~$ psql
psql (9.0.1)
Type "help" for help.
postgres=# select pg_start_backup('replicacao', true);
pg_start_backup
-----------------
0/5044CB4
(1 row)

postgres=# \q
postgres@principal:~$ rsync -av --exclude postmaster.pid \
--exclude postgresql.conf --exclude pg_hba.conf \
--exclude backup_label --exclude pg_xlog/* --exclude pg_log/* \
/bd/primario/ postgres@10.1.1.2:/bd/secundario
postgres@principal:~$ psql
psql (9.0.1)
Type "help" for help.
postgres=# select pg_stop_backup();
NOTICE:  WAL archiving is not enabled; you must ensure that all required
WAL segments are copied through other means to complete the backup
pg_stop_backup
----------------
0/90D7950
(1 row)

postgres=# \q


A primeira consulta (select pg_start_backup()) prepara o servidor para iniciar a cópia dos arquivos. O próximo passo é copiar os arquivos (utilizamos o rsync mas pode ser qualquer outro aplicativo) e, neste caso, temos que excluir alguns arquivos (postmaster.pid, postgresql.conf, pg_hba.conf, backup_label, pg_xlog/* e pg_log/*). Indicamos que a cópia já foi concluída executando a função (pg_stop_backup()). Vale lembrar que o parâmetro wal_keep_segments no servidor principal deve ser maior do que o número de arquivos de log de transação gerados durante a cópia. Isto porque o servidor secundário precisará destes arquivos (eles não podem ser reciclados) para "acompanhar" o servidor principal. Caso o parâmetro wal_keep_segments não seja suficiente, (i) você terá que aumentar o valor e realizar todo este passo novamente ou (ii) caso você esteja arquivando os arquivos de log de transação basta copiá-los para o diretório pg_xlog do servidor secundário.

Neste momento você pode iniciar o servidor secundário. Não se esqueça de criar os arquivos postgresql.conf e pg_hba.conf no servidor secundário.

postgres@secundario:~$ pg_ctl start -D /bd/secundario
server starting


Caso os logs estejam habilitados (logging_collector = on) no servidor secundário, as seguintes mensagens indicam que a replicação está acontecendo com sucesso:

LOG:  database system was interrupted; last known up at 2010-11-14 14:08:19 BRT
LOG:  entering standby mode
LOG:  redo starts at 0/5044CB4
LOG:  restartpoint starting: xlog
LOG:  consistent recovery state reached at 0/A000000
LOG:  database system is ready to accept read only connections
LOG:  streaming replication successfully connected to primary

Neste momento, espera-se que o servidor secundário esteja "acompanhando" o servidor principal mas sempre em um estado consistente. Assim, a mesma consulta em ambos servidores podem retornar resultados diferentes (lembre-se que a replicação é assíncrona).

Se você tiver perguntas, as listas de discussão pgbr-geral (português) e pgsql-general (inglês) são bons lugares para perguntar.

18 comments:

  1. Bom dia nem sei se este blog ainda esta ativo, mais queria deixar aqui meu agradecimento por este post foi de muita ajuda para mim. Obrigado Euler!

    ReplyDelete
  2. Olá Euler,

    Excelente post. Fiquei apenas com uma dúvida: Quando eu precisar colocar o servidor secudário em produção o que devo desligar nas configurações?

    ReplyDelete
    Replies
    1. Nada. Os parâmetros que não são do servidor de produção (quando ele passar a ser) serão ignorados. Para promover o servidor secundário a servidor de produção:

      Na versão 9.1 ou superior execute o comando:

      pg_ctl promote -D /path/to/pgdata

      Na versão 9.0, você terá que configurar o parâmetro trigger_file no recovery.conf. Assim, basta criar o arquivo no local especificado no parâmetro que em alguns segundos o servidor secundário passará a ser servidor de produção.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. Olá Euler,

      Estou com uma dúvida parecida com a do Renato. Depois que eu promovi o slave para master, como faço para o master voltar a ser master e o slave para slave?
      Existe uma maneira de promoção automática (master caiu automaticamente slave assume) ?

      Grato.

      Delete
    4. Falar em transferência em caso de falha (aka failover) e volta ao cenário anterior (aka failback) em bancos de dados é complicado porque se a replicação for assíncrona, ao promover o servidor secundário a servidor principal, o servidor principal antigo poderá conter dados que não foram replicados. Neste caso a volta fica comprometida porque não é possível determinar quais os dados estavam no servidor principal antigo que devem ser descartados (ou mesmo considerados). Neste caso, não é possível voltar ao cenário anterior sem montar todo cenário de replicação (duas vezes). Supondo que o servidor A seja o principal e o servidor B seja o secundário (slave), se você realiza o failover (A -> B), para fazer a volta (B -> A) você terá que obrigatoriamente:

      (i) montar uma replicação com A sendo o secundário de B (é aconselhado realizar um rsync de B para A para minimizar o tempo -- já que os dados estão quase iguais);
      (ii) promover o servidor A para principal (failover de B -> A);
      (iii) montar uma replicação com B sendo o secundário de A (novamente é aconselhável realizar um rsync).

      Quanto a promoção automática, isso deve ser feito com algum software de HA (por exemplo, heartbeat ou corosync/pacemaker).

      Delete
    5. Obrigado pela ajuda, era isso o que eu imaginava que deveria fazer.
      Só mais uma dúvida, fui realizar um teste com um script onde eu criei uma sequence que realizava um auto incremento de 1 em 1.
      Quando eu promovia o slave para mestre, o incremento pulou a sequencia quando começou a ser inserido pelo antigo slave.

      Você sabe me dizer o por que acontece isso?
      Andei pesquisando e vi que é "normal" acontecer isso na replicação.

      Grato.

      Delete
    6. Isso é um detalhe de implementação. Há uma cache de valores da sequência (geralmente 32) para evitar ter que escrever a cada incremento da sequência no WAL. O efeito colateral disso é deixar um "buraco" nas suas sequências mas como sequências podem ter "buracos" não vejo problema nisso. Vide uma longa discussão sobre o assunto em [1][2].

      [1] http://postgresql.1045698.n5.nabble.com/Coluna-quot-log-cnt-quot-de-uma-Sequence-td2049816.html
      [2] http://www.postgresql.org/message-id/27779.1249500343@sss.pgh.pa.us

      Delete
    7. Obrigado pela resposta Euler, deu pra entender o conceito.

      Abraços.

      Delete
  3. Parabéns pelo artigo, como perguntado pelo colega anterior, esse bloq ainda está em atividade?

    ReplyDelete
    Replies
    1. Está mas ando meio sem tempo de revisar os textos que já estão prontos e ainda não foram publicados. :(

      Delete
  4. Ola pessoal, estou tentando dar um comando no shell no postgresql (windows) e estou utilizando a seguinte sintaxe...
    select pg_start_backup('nome do meu banco de dados', true);

    ele responde um registro

    agora digito:
    pg_basebackup -U postgres -D /var/lib/pgsql/9.4/data/db/secundario -P -h Ip do Slave -Ft
    e nao acontece nada

    quando dou esse comando

    pg_stop_backup();
    aparece a seguinte mensagem:
    ERROR: syntax error at or near "ph_basebackup"
    LINHA 1: pg_basebackup -U postgres -D /var/li/psql/9.4/data/db/secu...

    Alguém sabe o que esta acontecendo?

    desde já agradeço... Obrigado!!!

    ReplyDelete
  5. Muito bom seu tutorial.
    Me tira uma duvida.
    boa noite tem como configuro a replicação Streaming para ele replicar ao fim de cada transação?

    ReplyDelete
    Replies
    1. Na replicação assíncrona, você não escolhe o momento de replicar. Contudo, se a replicação estiver fazendo "streaming", a transação que acabou de ser efetivada será enviada. Porém, se o servidor réplica parar por algum tempo, quando ele voltar terá que aplicar todas as transações pendentes. Nesse tempo parado, a garantia de replicar ao fim de cada transação não poderá ser cumprida (nada que um monitoramento não possa alertá-lo).

      Na replicação síncrona, você tem a garantia que a transação chegará no outro servidor ANTES da efetivação da mesma no servidor principal. É um modelo que produz uma dependência entre os servidores, ou seja, se um deles parar o postgres para de efetivar transações (no assíncrono não há essa dependência). Assim, eu consigo garantir que a transação estará no servidor réplica mas você será penalizado no tempo da transação que será no mínimo o dobro da latência entre os servidores.

      Delete
  6. Ótimo o tutorial, porem tenho uma duvida.
    Existe a possibilidade de realizar essa configuração mas evitar a replicação de alguma tabela especifica ?

    ReplyDelete
    Replies
    1. Não. Replicação física envia tudo. Para replicar objetos específicos você precisa de replicação lógica.

      Delete
  7. Como faz, se o servidor standby perder sincronismo, para que este retome o processo de sincronização? Sera necessario recriar tudo?

    OBrigado!

    ReplyDelete