Você está na página 1de 13

Replicao de Dados com InterBase/Firebird

By Matt Hopkins, Dunstan Thomas (UK) Ltd. Borland Developers Conference 1998

Este artigo ir descrever os conceitos bsicos da replicao de dados e como isto pode ser implementado de maneira relativamente simples usando as caractersticas embutidas no InterBase/Firebird.

O que Replicao de Dados?


Replicao de dados copia de dados para uma ou mais Bases de Dados de forma que a informao seja consiste em todas as bases. Existem dois tipos bsicos de replicao sncrona e assncrona. Com replicao sncrona, todas as cpias ou rplicas dos dados so mantidas exatamente sincronizadas e consistentes. Se uma cpia atualizada as mudanas sero imediatamente aplicadas para todas as outras bases de dados com a mesma transao. A replicao sncrona apropriada quando esta exata consistncia importante para o negcio. Com replicao assncrona, ou armazene e replique, as cpias dos dados ficaro temporariamente desincronizadas. Se uma cpia atualizada, a mudana ser aplicada e propagada para as outras cpias num segundo passo, em uma transao separada que pode ocorrer segundos, horas ou dias mais tarde. As cpias por outro lado podem ficar temporariamente sem sincronia, mas a uma certa altura os dados iro convergir para um mesmo valor em todas as cpias. Este artigo descrever exclusivamente a replicao assncrona de dados.

Quais Componentes so necessrios para a Replicao de Dados?


Capturando as mudanas da Origem dos Dados
Todas as estratgias de replicao de dados requerem um mtodo para capturar as mudanas em uma base de dados replicada. Esto aqui dois mecanismos: Log de Transaes e Triggers (Gatilhos). O log de transao se parece com um mecanismo chamado log sniffing que copia as transaes marcadas para replicao para uma rea de preparao para transmisso. Esta tcnica argumentada por ter baixo impacto no servidor da base dados por que ela requer um mnimo de utilizao da CPU quando esta lendo o log em memria e escrevendo para o disco. Isto implementado em produtos de replicao por fornecedores como Informix, Sybase, e MS SQL/Server. O segundo mecanismo usar triggers da base de dados para propagar as mudanas quando elas ocorrem em uma tabela de log da base de dados para transmisso. Como a linguagem procedural da base de dados pode ser usada para estes mtodos, ela prov maior flexibilidade em decidir quais os dados sero replicados. Esta tcnica de replicao implementada por fornecedores como Ingress e Oracle. Esta ser tambm a tcnica que ns utilizaremos quando replicarmos com InterBase/Firebird.

Transmitir Mudanas da Base de Dados Origem para todas as Bases de Dados Alvo
Transmitir mudanas para replicao requer softwares que leiam os logs (registros) de mudanas e se conectem a todos os servidores de bases de dados alvo localizados em uma LAN ou WAN e aplique estas mudanas na base de dados alvo. Este software conhecido como gerenciador de replicao e devem tambm gerar logs de erros e conflitos de atualizao.

Como estes Componentes de Replicao de Dados podem ser Implementados em InterBase/Firebird?


Logando as Mudanas dos Dados
Certo, para criarmos o nosso ENGINE de replicao ns primeiro precisamos criar duas Bases de Dados de exemplo (origem e destino) com duas tabela idnticas em cada um.

CREATE DATABASE "source.gdb" PAGE_SIZE 1024 CREATE TABLE TEAMS ( TEAM_ID INTEGER NOT NULL, TEAM_NAME VARCHAR(100), TEAM_MGR VARCHAR(40), PRIMARY KEY (TEAM_ID)); CREATE TABLE CHANGES ( CHANGE_ID INTEGER NOT NULL, CHANGE_TYPE CHAR(1) NOT NULL, SOURCE_KEY_FIELD VARCHAR(31) NOT NULL, SOURCE_KEY INTEGER NOT NULL, SOURCE_TABLE VARCHAR(31), USERNAME VARCHAR(31), PRIMARY KEY (CHANGE_ID)); CREATE GENERATOR NEW_TEAM_ID; CREATE GENERATOR NEW_CHANGE_ID;

Agora vamos adicionar a trigger para a tabela CHANGES para pegar o valor nico do generator do InterBase/Firebird.
CREATE TRIGGER CHANGES_NEWID FOR CHANGES BEFORE INSERT AS BEGIN NEW.CHANGE_ID = GEN_ID(NEW_CHANGE_ID,1); END

Repare que ns no estamos adicionando uma trigger para gerar um nico ID para a tabela TEAMS; a razo para isto ser discutida na prxima seo. Agora que temos nossas bases de dados de exemplos, ns precisamos criar um mecanismo para logar todas as mudanas nas nossas tabelas. Como mencionado anteriormente, ns incorporaremos um mtodo trigger para logar as mudanas das nossas tabelas para replicao. Esta tcnica nos traz o benefcio de ser fcil de usar, ao contrrio dos logs de transao, permitem o uso de uma lgica de programao personalizada atravs da linguagem procedural do InterBase/Firebird. Como so trs os tipos de mudanas que podem ocorrer em uma tabela Insero, Atualizao e Deleo, ns precisaremos trs triggers para cada tabela que ser replicada:
/* After an Insert */ CREATE TRIGGER TEAMS_REPL_INSERT FOR TEAMS AFTER INSERT AS BEGIN INSERT INTO CHANGES (CHANGE_TYPE, SOURCE_KEY_FIELD, SOURCE_KEY, SOURCE_TABLE,USERNAME) VALUES ("I","TEAM_ID",NEW.TEAM_ID,"TEAMS",USER); END;

/* After an Update */ CREATE TRIGGER TEAMS_REPL_UPDATE FOR TEAMS AFTER UPDATE AS BEGIN INSERT INTO CHANGES (CHANGE_TYPE, SOURCE_KEY_FIELD, SOURCE_KEY, SOURCE_TABLE,USERNAME) VALUES ("U","TEAM_ID",NEW.TEAM_ID,"TEAMS",USER); END; /* After a Delete */ CREATE TRIGGER TEAMS_REPL_DELETE FOR TEAMS AFTER DELETE AS BEGIN INSERT INTO CHANGES (CHANGE_TYPE, SOURCE_KEY_FIELD, SOURCE_KEY, SOURCE_TABLE,USERNAME) VALUES ("D","TEAM_ID",OLD.TEAM_ID,"TEAMS",USER); END;

Isto tudo o que necessrio para logar mudanas de dados para replicao de dados em InterBase/Firebird. Observe que por causa da simplicidade, ns precisamos que as tabelas replicadas tenham uma chave inteira nica e que somente este o que ser armazenado na tabela CHANGES. Ns podemos armazenar todos valores dos campos que foram mudados, mas como ns replicaremos um SNAPSHOT dos dados armazenando apenas as chaves e a operao, ns podemos pegar o valor corrente em cada tabela com o mecanismo de replicao. Uma coisa a mais antes de passarmos para o mecanismo de replicao ns precisaremos criar a base de dados de destino com o mesmo metadata da origem. Isto pode ser feito atravs da gerao da cpia da base de dados origem com o flag metadadata-only marcado e restaurar esta cpia no destino.gdb.

Replicando as Mudanas nos Dados


Agora ns iremos criar o servidor de replicao utilizando Delphi 32-bit. A idia que o usurio possa configurar a replicao para um certo tempo ou acion-la manualmente pressionando um boto Replicar Agora. Um log de todas as atividades ser mostrado em Memo e todos os erros que ocorrerem sero mostrados outro Memo. O alias de origem e o alias alvo sero passados como parmetros de linha de comando, algo como isto:
C:\REPLD SOURCEDB TARGETDB

O fonte para garantir que sero sempre dois parmetros este:


if (ParamStr(1) = '') or (ParamStr(2) = '') then MessageDlg('Usage: "REPLD.EXE SOURCE_ALIAS TARGET_ALIAS"',mtError,[mbOK],0) Else begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end;

Toda replicao ser feita em um procedimento Replicate, como este:


procedure TForm1.Replicate; var qryChangeList : TQuery; strChangeType : String; begin qryChangeList := TQuery.create(Application); try with qryChangeList do begin DatabaseName := 'SourceDB'; sql.add('select * from changes'); open; while not eof do begin if (fieldByName('CHANGE_TYPE').asString = 'I') then begin strChangeType := 'Insert'; ReplicateInsert(fieldByName('SOURCE_TABLE').asString, fieldByName('SOURCE_KEY_FIELD').asString, fieldByName('SOURCE_KEY').asInteger) end else if (fieldByName('CHANGE_TYPE').asString = 'U') then begin strChangeType := 'Update'; ReplicateUpdate(fieldByName('SOURCE_TABLE').asString, fieldByName('SOURCE_KEY_FIELD').asString, fieldByName('SOURCE_KEY').asInteger) end else if (fieldByName('CHANGE_TYPE').asString = 'D') then begin strChangeType := 'Delete'; ReplicateDelete(fieldByName('SOURCE_TABLE').asString, fieldByName('SOURCE_KEY_FIELD').asString, fieldByName('SOURCE_KEY').asInteger) end; memoLog.lines.add( DateTimeToStr(Now)+' '+strChangeType+ ' '+fieldByName('SOURCE_KEY').asString+ ' on '+fieldByName('SOURCE_TABLE').asString); DeleteFromChanges(fieldByName('CHANGE_ID').asInteger); next;

end; close; end; finally qryChangeList.free; end; end;

E para cada ao (Inserir, Deletar, Atualizar) um procedimento individual chamado desta forma: Replicar Inseres:
procedure TForm1.ReplicateInsert(const strTableName, strKeyField: String; const iKey: Integer); var qrySource, qryTarget : TQuery; i : SmallInt; begin qrySource := TQuery.create(Application); try with qrySource do begin DatabaseName := 'SourceDB'; with sql do begin add('select * from '+strTableName); add(' where ('+strKeyField+' = '+IntToStr(iKey)+');'); end; try open; while not eof do begin qryTarget := TQuery.create(Application); try with qryTarget do begin DatabaseName := 'TargetDB'; with sql do begin add('insert into '+strTableName); add('('); for i := 0 to qrySource.fieldCount-1 do begin if (i < qrySource.fieldCount-1) then add(' '+qrySource.Fields[i].FieldName+',') else add(' '+qrySource.Fields[i].FieldName) end; add(')'); add('values'); add('('); for i := 0 to qrySource.fieldCount-1 do begin

if (i < qrySource.fieldCount-1) then add(' :'+qrySource.Fields[i].FieldName+',') else add(' :'+qrySource.Fields[i].FieldName) end; add(')'); end; prepare; for i := 0 to qrySource.fieldCount-1 do begin qryTarget.Params[i].asString := qrySource.Fields[i].asString; end; execSQL; end; finally qryTarget.free; end; next; end; close; except on e: Exception do memoErrors.lines.add(DateTimeToStr(Now)+' '+e.message); end; end; finally qrySource.free; end; end;

Replicar Atualizaes:
procedure TForm1.ReplicateUpdate( const strTableName, strKeyField: String; const iKey: Integer); var qrySource, qryTarget : TQuery; i : SmallInt; begin qrySource := TQuery.create(Application); try with qrySource do begin DatabaseName := 'SourceDB'; with sql do begin add('select * from '+strTableName); add(' where ('+strKeyField+' = '+IntToStr(iKey)+');'); end; try open; while not eof do begin

qryTarget := TQuery.create(Application); try with qryTarget do begin DatabaseName := 'TargetDB'; with sql do begin add('update '+strTableName); add('set'); for i := 0 to qrySource.fieldCount-1 do begin if (i < qrySource.fieldCount-1) then add(' '+qrySource.Fields[i].FieldName+ ' = :'+qrySource.Fields[i].FieldName+',') else add(' '+qrySource.Fields[i].FieldName+ ' = :'+qrySource.Fields[i].FieldName) end; add(' where ('+strKeyField+' = '+IntToStr(iKey)+');'); end; prepare; for i := 0 to qrySource.fieldCount-1 do begin qryTarget.Params[i].asString := qrySource.Fields[i].asString; end; execSQL; end; finally qryTarget.free; end; next; end; close; except on e: Exception do memoErrors.lines.add(DateTimeToStr(Now)+' '+e.message); end; end; finally qrySource.free; end; end;

Replicando Delees:
procedure TForm1.ReplicateDelete(const strTableName, strKeyField: String; const iKey: Integer); var qrySource : TQuery; i : SmallInt; begin qrySource := TQuery.create(Application); try with qrySource do begin DatabaseName := 'TargetDB'; sql.add('delete from '+strTableName); sql.add(' where ('+strKeyField+' = '+IntToStr(iKey)+')'); execSQL; try execSQL; except on e: Exception do memoErrors.lines.add(DateTimeToStr(Now)+' '+e.message); end; end; finally qrySource.free; end; end;

Note que este exemplo no suporta tipos BLOB. necessrio um pouco mais de cdigo para implementar esta funcionalidade, mas possvel utiliz-los. Agora, conecte o value do SpinBox com o timer e ento voc ajusta o ciclo de replicao:
procedure TForm1.SpinEdit1Change(Sender: TObject); begin Timer1.Interval := (SpinEdit1.Value * 1000); end;

Execute isto, e enquanto REPLD est rodando, faa algumas mudanas na tabela TEAMS na base de dados de origem certificando-se de adicionar um valor de chave nica e ento cheque a base de dados alvo (depois do ciclo de replicao) para ver as mudanas replicadas.

Replicao Bi-direcional
Enquanto estiver na base de dados alvo, cheque as mudanas da tabela. Voc pode observar que as mudanas replicadas da base de dados de origem tambm aparecem nesta tabela. O problema que o mecanismo para logar nossas mudanas no diferencia entre aquelas mudanas feitas pelo usurio e as mudanas feitas pelo mecanismo de replicao. Se ns iniciarmos uma replicao bi-direcional usando nosso REPLD sem especificar um usurio de replicao ns podemos criar um loop infinito. Portanto, ns necessitaremos criar um usurio de replicao para este exemplo ns o chamaremos de REPLICATE em cada servidor InterBase/Firebird que estiver rodando e recusar as mudanas feitas por este usurio. Isto faz que tenhamos que mudar as triggers de log de mudanas para cada tabela que necessite replicao como segue:

/* After an Insert */ ALTER TRIGGER TEAMS_REPL_INSERT FOR TEAMS AFTER INSERT AS BEGIN IF (USER <> "REPLICATE") THEN INSERT INTO CHANGES (CHANGE_TYPE, SOURCE_KEY_FIELD, SOURCE_KEY, SOURCE_TABLE,USERNAME) VALUES ("I","TEAM_ID",NEW.TEAM_ID,"TEAMS",USER); END; /* After an Update */ ALTER TRIGGER TEAMS_REPL_UPDATE FOR TEAMS AFTER UPDATE AS BEGIN IF (USER <> "REPLICATE") THEN INSERT INTO CHANGES (CHANGE_TYPE, SOURCE_KEY_FIELD, SOURCE_KEY, SOURCE_TABLE,USERNAME) VALUES ("U","TEAM_ID",NEW.TEAM_ID,"TEAMS",USER); END; /* After a Delete */ ALTER TRIGGER TEAMS_REPL_DELETE FOR TEAMS AFTER DELETE AS BEGIN IF (USER <> "REPLICATE") THEN INSERT INTO CHANGES (CHANGE_TYPE, SOURCE_KEY_FIELD, SOURCE_KEY, SOURCE_TABLE,USERNAME) VALUES ("D","TEAM_ID",OLD.TEAM_ID,"TEAMS",USER); END;

Agora ns rodaremos duas verses do REPLD:


C:\REPLD SOURCEDB TARGETDB C:\REPLD TARGETDB SOURCEDB

E quando ns formos logar nestes bancos, ns precisaremos logar como REPLICATE. Isto nos prover uma replicao bi-direcional. Voc pode notar que quando voc move de um modelo de replicao mestre-escravo para um modelo ponto-a-ponto muitas complexidades ao redor devem ser consideradas antes de implementar a replicao. Estas dicas, e como fazer uso de algumas delas so descritas na prxima seo.

Replicando as Mudanas Quando Ocorrem (prximo replicao sncrona)


Se ns precisarmos replicar as tabelas quando elas so alteradas e no em um determinado ciclo de tempo, ns termos que utilizar os mecanismos de eventos do InterBase/Firebird. Isto requer dois passos gerar (ou postar) um evento e responder a este evento. Para gerar o evento, ns precisaremos adicionar uma nova trigger na tabela CHANGES:
CREATE TRIGGER CHANGES_ALERT FOR TEAMS AFTER INSERT AS BEGIN POST_EVENT "NEW_CHANGE"; END;

Para responder ao evento, ns precisaremos adicionar um componente IBEventAlerter a nossa aplicao REPLD, registrando o interesse no evento NEW_CHANGE pela adio disto na propriedade Events, e ligando o evento OnEventAlert ao procedimento replicate:
procedure TForm1.IBEventAlerter1EventAlert(Sender: TObject; EventName: string; EventCount: Longint; var CancelAlerts: Boolean); begin Replicate; end;

Tente isto.

Replicando Tabelas com Chaves Compostas


Para simplificarmos, no exemplo seguinte ns iremos replicar somente tabelas com ndice simples (inteiros). A mesma tcnica, com pequenas alteraes, pode ser usada para manusear tabelas com ndices compostos. A principal diferena entre manusear chaves compostas mistas e chaves inteiras simples a forma como as mudanas so logadas. Ao invs de uma simples tabela CHANGES, no caso de chaves compostas necessrio uma tabela de "log" para cada tabela base que ser replicada. Replicar atravs uma WAN com RAS (Remote Access Services) O servio de Acesso Remoto (RAS) em Windows NT e Rede Discada (Dial-Up) em Windows 95 permitem que muitos servios possam estar disponveis em uma rede que pode ser acessada atravs de um modem. Isto quer dizer que se quisermos podemos modificar o mecanismo de replicao para replicarmos bases remotas atravs de um modem, adicionando suporte RAS. O caminho mais fcil em Delphi para implementar RAS atravs de um componente. Existe uma enorme quantidade de componentes shareware e freeware para isto na Internet. Para estes exemplo, ns usaremos TRas do Daniel Polistchuck que est disponvel em http://www.delphi32.com . Como o componente e a API RAS descomplicou as coisas para ns, adicionar RAS ao REPLD precisar somente algumas poucas linhas de cdigo. Primeiro ns precisamos modificar o procedimento Replicate e ento este chamar a conexo RAS quando as mudanas forem replicadas.

procedure TForm1.Replicate; begin with qryChangeList do begin DatabaseName := 'SourceDB'; sql.add('select * from changes'); if active then close; open; if (recordCount > 0) then begin memoLog.lines.add(DateTimeToStr(Now)+' Dialing DT'); RAS1.EntryName := 'DT'; RAS1.Connect; end; end; end;

Como voc pode ver, ns adicionamos um teste para verificar se existem registros e chamamos o componente RAS para chamar a conexo DT. Ns tambm teremos que mover o resto do cdigo que existia anteriormente para o evento OnConnect do componente RAS e tornar a query (qryChangeList) global para o form:
procedure TForm1.RAS1Connect(Sender: TObject); var strChangeType : String; begin with qryChangeList do begin memoLog.lines.add(DateTimeToStr(Now)+' Connected to DT'); try while not eof do begin if (fieldByName('CHANGE_TYPE').asString = 'I') then begin strChangeType := 'Insert'; ReplicateInsert( fieldByName('SOURCE_TABLE').asString, fieldByName('SOURCE_KEY_FIELD').asString, fieldByName('SOURCE_KEY').asInteger) end else if (fieldByName('CHANGE_TYPE').asString = 'U') then begin strChangeType := 'Update'; ReplicateUpdate( fieldByName('SOURCE_TABLE').asString, fieldByName('SOURCE_KEY_FIELD').asString, fieldByName('SOURCE_KEY').asInteger) end else if (fieldByName('CHANGE_TYPE').asString = 'D') then begin strChangeType := 'Delete'; ReplicateDelete( fieldByName('SOURCE_TABLE').asString, fieldByName('SOURCE_KEY_FIELD').asString, fieldByName('SOURCE_KEY').asInteger)

end; memoLog.lines.add( DateTimeToStr(Now)+' '+strChangeType+' '+ fieldByName('SOURCE_KEY').asString+ ' on '+fieldByName('SOURCE_TABLE').asString); DeleteFromChanges(fieldByName('CHANGE_ID').asInteger); next; end; close; finally RAS1.Disconnect; memoLog.lines.add(DateTimeToStr(Now)+' Disconnected from DT'); end; end; end;

Isto tudo que ns precisamos para conectar a uma base remota, replicando qualquer mudana, e ento desconectando.

Isto simples?
No. Com a replicao vem um amontoado de problemas que necessitam ser prevenidos atravs de boas decises, planejamento adequado e algumas vezes uma boa programao no software. Seguem algumas dicas:

Conflitos de Atualizao
Assegurar a convergncia em ambientes de replicao assncrona crtico para toda aplicao. De qualquer forma, o que acontece se o mesmo dado (Ex.: a mesma coluna na mesma linha) atualizado em duas bases ao mesmo tempo, ou o mais precisamente dentro do mesmo intervalo de replicao? Isto conhecido como um conflito de atualizao. Para assegurar a convergncia, conflitos de atualizao devem ser detectados e resolvidos para que ento o dado tenha o mesmo valor em cada base. Quanto mais freqente voc propagar as suas mudanas (isto , menos intervalo de replicao ou perodo de tempo), menos conflitos de atualizao ocorrero. Alternativamente, conflitos de atualizao podem ser evitados pela limitao de posse ou o direito de atualizao dado um elemento para uma base. De fato, muitos acreditam que a resoluo de conflitos um processo que preferencialmente cabe ao desenvolvedor de software. Pela eliminao de possibilidades de conflitos atravs de planejamento e procedimento, o software no precisar resolver conflitos, que em teoria nunca ocorrero. Esta viso, por outro lado, um tanto quanto limitada quando clientes precisam que vrias opes para resoluo de conflitos sejam construdas na ferramenta de replicao.

Chaves nicas e Generators


Tipicamente quando trabalhamos com chaves inteiras nicas, uma trigger before insert (antes da insero) adicionada com chamadas a geradores de nmeros para adicionar novos valores. Quando trabalhamos com replicao isto pode gerar problemas. O problema manter a sincronizao entre duas ou mais bases de dados. D uma olhada no seguinte exemplo: Na primeira base, o gerador usado para uma certa tabela 5. Quando um novo registro adicionado, um trigger incrementa to valor do gerador em 1 e fornece 6 para a chave do novo registro. O registro replicado para uma outra base de dados identica. Na base dois, o valor do gerador 100. Quando o registro da primeira base inserido na tabela correspondente, o trigger sobrepe o 6 com o novo valor incrementado, valor 101. A soluo tem duas partes. A primeira confiar no cliente para dar valores s chaves e no usar triggers. Segundo, reservar blocos de nmeros para as vrias base e ento cada uma ser dona de certos valores de chave. Um integerem Interbase/Firebird pode armazenar at 2 bilhes de nmeros, ento, cada bloco pode ser dividido em um conjunto de milhes, se necessrio.

Os geradores de cada base podem ser inicializados com o nmero inicial de cada bloco usando-se os comando SET GENERATOR:
SET GENERATOR GEN_NAME TO 1000000;

Inicializao
Se voc replicar a fonte de dados nos alvos, ento voc deve inicialiazar aqueles alvos de maneira que iniciem em sincronia com a fonte. Tipicamente, voc pode fazer isto com uma cpia completa dos dados para a base alvo; simplesmente implementando um dump da base origem e recarregando isto na base alvo.

Mudanas na DDL
O que acontece quando voc muda o metadata? Se voc adicionar um campo a uma tabela em uma base, estas mudanas tambm precisaro ser feitas em todas as bases replicadas.

Sumrio
Replicao de bases de dados um tpico interessante e est sendo requisitado (ou preferencialmente exigido) por mais e mais clientes em todo o mundo, de fato, mais de 50% dos projetos iniciados em dezembro de 1996 na Dunstan Thomas tem replicao como um requerimento de sistema. A replicao tem o potencial de prover maior rendimento, tempo de resposta rpido e reduzir custo de comunicao e gargalos nas redes para usurios-finais geograficamente dispersos. Com InterBase/Firebird e o poder do Microsoft RAS, replicao de dados no somente possvel, mas est disponvel hoje. Artigo Original:
http://www.ibphoenix.com/main.nfs?a=ibphoenixApp&page=ibp_howto10

Matt Hopkins Traduo e adaptao: Jiancarlos Kleinebing polo@softline.com.br


Comunidade Firebird de Lngua Portuguesa Visite a Comunidade em:

http://www.comunidade-firebird.org A Comunidade Firebird de Lngua Portuguesa foi autorizada pelo Autor do Original para elaborar esta traduo.

Você também pode gostar