Upgrade de Aplicação com Zero Downtime usando Edition-Based Redefinition
É uma situação relativamente comum : vc, desenvolvedor de aplicações que utilizam os serviços de um database Oracle, possui parte do código da sua aplicação (seja código SQL, na forma de views, por exemplo, seja código PL/SQL na forma de Libraries, Packages, Procedures, Functions, Triggers, etc) desenvolvido, salvo e executando DENTRO do banco de dados Oracle – isso é quase SEMPRE uma idéia Inteligente, pois não só o seu código está Seguramente Armazenado no database (Evitando necessidade de ter que gerir inúmeros arquivinhos-texto espalhados por aí, Evitando Múltiplas cópias conflitantes do mesmo código, possível de acontecer quando devs ‘esquecem’ de Atualizar repositório central de código-fonte) MAS ele também é Acessível via Qualquer tool client de Qualquer Máquina cliente que possa conectar no banco E o mais importante de tudo, Apresenta vantagens CLARAS para a Performance, principalmente porque nessa situação o código está rodando LOCALMENTE NO DATABASE, necessitando portanto de ZERO Tráfego de Rede para manipular dados no banco, E por outros motivos, como o CACHE DEDICADO para PL/SQL que o database mantém….
Porém, OBVIAMENTE o database Oracle, sendo Totalmente Focado para a Integridade de Dados quase que Acima de Tudo como é, LOGICAMENTE não permite alteração de código database-stored enquanto alguém está executando esse código – caso contrário, as sessões que estão executando esse código que não mais existe poderiam ficar num estado não íntegro, pois executaram comandos que talvez não existam mais no código, ou não executaram comandos que agora passaram a exitir no código alterado…. Justamente por isso, Antigamente via de regra o cronograma para subida de novas versões de código em PRODUÇÃO sempre exigia que alterações fossem feitas num período de Inatividade da Aplicação mas com a Globalização e ambientes de alta-demanda de hoje em dia, onde Facilmente vc pode ter usuários executando a aplicação em diferentes fusos horários e países, isso não é mais viável – o que fazer nesses casos ?
A resposta é : desde o Oracle 11gR2 (lançado há mais de 15 anos atrás, em Setembro/2009, mas Inacreditavelmente Pouco conhecida e usada) a Oracle já providenciou a resposta, que se chama EDITION-BASED REDEFINITION…. Podemos pensar nessa funcionalidade como um “Rótulo”, uma “Etiqueta”, que é adicionada ao código database-stored , e a cada alteração que precisa ser feita, SEM mudar o nome do objeto, uma nova “Etiqueta” é adicionada – assim, num dado momento, podemos ter uma sessão rodando a versão ANTERIOR da (digamos) procedure P , que tem o “rótulo” de ‘VERSAO1’ , mas Foi feita uma edição nessa procedure P que ganhou um “rótulo” de ‘VERSAO2’ : nesse cenário, não só a Edição da procedure P foi permitida normalmente MAS quando Novas sessões tentarem executar a procedure P, será ESSA versão com o rótulo de ‘VERSAO2’ que será executada, tudo isso SEM afetar a sessão antiga que está rodando a “versão 1” da procedure P…
Exemplo :
=> vou criar no usuário SCOTT uma procedure P , NÃO VERSIONADA (o que normalmente É o default num database Oracle) que vai demorar 5 minutos / 300 segundos, para que eu tenha tempo de tentar uma Edição enquanto uma sessão a executa – há muitas maneiras de se fazer isso, mas vou escolher a função SLEEP da package DBMS_LOCK, a qual, já que meu usuário de testes chamado SCOTT não tem acesso, ele Precisa receber :
SYS@xepdb1::CONT=XEPDB1> grant execute on DBMS_LOCK to scott;
Concessão bem-sucedida.
SYS@xepdb1::CONT=XEPDB1>
=> agora sim, crio a procedure :
SCOTT@xepdb1::CONT=XEPDB1> CREATE OR REPLACE PROCEDURE P is
2 BEGIN
3 dbms_lock.sleep(300);
4 RETURN;
5 END;
6 /
Procedimento criado.
SCOTT@xepdb1::CONT=XEPDB1>
=> executo a procedure numa sessão A (só para Clareza, previamente alterei com o comando SQLPROMPT o prompt do sqlplus em cada sessão) :
SCOTT@xepdb1::CONT=XEPDB1#A> exec p;
==> e enquanto ela está ativa, abro uma Outra sessão B e tento editar/recriar/recompilar a procedure P:
SCOTT@xepdb1::CONT=XEPDB1#B> CREATE OR REPLACE PROCEDURE P is
2 BEGIN
3 dbms_output.put_line('Nova versão');
4 RETURN;
5 END;
6 /
==> Pronto, Automaticamente a sessão B VAI FICAR LOCKADA, Bloqueada, enquanto A não terminar de usar/executar a procedure P (OU enquanto não passar o tempo estipulado no parâmetro de banco DDL_LOCK_TIMEOUT , se ele estiver setado com valor maior que zero)… Neste exemplinho, tudo bem esperar 5 minutos, mas Suponha que P na verdade demorasse meia hora, ou até mais que isso, E não seja viável nem esperar esse tempo todo nem matar a sessão que está executando P – vamos então alterar o cenário… Primeiro ponto, EDITIONs são objetos criados (e também dropados/eliminados) a nível de DATABASE, então devem OU ser criados/dropados pelo usuário SYS , OU ser criados/dropados por um usuário que recebeu privilégios de CREATE ANY EDITION / DROP ANY EDITION, vou Optar por dar para meu usuário SCOTT os privilégios :
SYSTEM@xepdb1::CONT=XEPDB1> GRANT CREATE ANY EDITION TO SCOTT;
Concessão bem-sucedida.
SYSTEM@xepdb1::CONT=XEPDB1> GRANT DROP ANY EDITION TO SCOTT;
Concessão bem-sucedida.
SYSTEM@xepdb1::CONT=XEPDB1>
=> além disso, Não É qualquer usuário que pode criar código versionado, a permissão deve ser Associada diretamente ao usuário, no meu caso SCOTT :
SYSTEM@xepdb1::CONT=XEPDB1> alter user scott ENABLE EDITIONS;
Usuário alterado.
=> crio as (no meu caso) duas Editions que vou precisar :
SCOTT@xepdb1::CONT=XEPDB1#A> create edition P_Versao1;
Edição criada.
SCOTT@xepdb1::CONT=XEPDB1#A> create edition P_Versao2 as CHILD OF P_versao1;
Edição criada.
==> DEPOIS que as execuções todas já se encerraram, crio a procedure Editionable/versionada :
SCOTT@xepdb1::CONT=XEPDB1#A> ALTER SESSION SET EDITION = P_Versao1;
Sessão alterada.
=> vou colocar um tempo MAIOR de duração do comando de SLEEP, apenas para exemplo :
SCOTT@xepdb1::CONT=XEPDB1#A> CREATE OR REPLACE EDITIONABLE PROCEDURE P is
2 BEGIN
3 dbms_lock.sleep(900);
4 RETURN;
5 END;
6 /
Procedimento criado.
=> confirmando a EDITION, a ‘Etiqueta’ associada com a procedure :
SCOTT@xepdb1::CONT=XEPDB1#A> select object_name, object_type, editionable, edition_name from user_objects where object_name='P';
OBJECT_NAME OBJECT_TYPE EDITIONABLE EDITION_NAME
----------- ----------- ----------- ------------
P PROCEDURE Y P_VERSAO1
=> executo a procedure :
SCOTT@xepdb1::CONT=XEPDB1#1> exec P;
=> ENQUANTO a procedure P está sendo usada/executada por uma sessão (E portanto, estaria Bloqueada se não fosse Editionable), vou recriar essa mesma procedure P mas em OUTRA Edition :
SCOTT@xepdb1::CONT=XEPDB1#B> alter session set EDITION = P_Versao2;
Sessão alterada.
SCOTT@xepdb1::CONT=XEPDB1#B> CREATE OR REPLACE EDITIONABLE PROCEDURE P is
2 BEGIN
3 dbms_output.put_line('Nova versão');
4 RETURN;
5 END;
6 /
Procedimento criado.
=> para consultar Todos os Objetos versionados, já que uma EDITION é um Objeto a nível de banco, TEMOS que usar as views administrativas que começam com DBA_xxxx, e normalmente só são acessíveis para os usuários DBA / privilegiados : assim, OU eu teria que dar as permissões de DBA para o SCOTT, OU teria que fazer as consultas como SYS (o que fiz abaixo), OU (uma alternativa mais razoável) poderia ter criado como SYS uma view chamada, digamos, EDITIONS_DO_SCOTT que faz a consulta necessária (na DBA_OBJECTS_AE , onde AE = All Editions, no caso) e depois ter dado o GRANT de SELECT nessa view para o SCOTT… COnsultado como SYS :
SYS@xepdb1::CONT=XEPDB1> select owner, object_type, object_name, editionable, edition_name from DBA_OBJECTS_AE where object_name='P';
OWNER OBJECT_TYPE OBJECT_NAME EDITIONABLE EDITION_NAME
----- ----------------------- ----------- ----------- ------------
SCOTT PROCEDURE P Y ORA$BASE
SCOTT PROCEDURE P Y P_VERSAO1
SCOTT PROCEDURE P Y P_VERSAO2
SYS@xepdb1::CONT=XEPDB1>
=> Obs : a Edition ORA$BASE é uma Edition default, criada sozinha pelo database Oracle, será registrada internamente no dicionário de dados sempre que não for indicada uma Edition – ignore-a…
==> A partir deste ponto, as próximas sessões que logarem e executarem o objeto versionado, vão o fazer na EDITION mais recente – abaixo conecto no banco via sessão:
SCOTT@xepdb1::CONT=XEPDB1#C> exec p;
Nova versão
Procedimento PL/SQL concluído com sucesso.
SCOTT@xepdb1::CONT=XEPDB1>
Há Diversos Outros detalhes que precisam ser Estudados na Documentação Oracle sobre o tópico de EDITION BASED REDEFINITION, mas Espero que esta breve e simples Introdução tenha sido de Ajuda…
Abraços
Da hora!
Legal né ? Eu sempre achei uma coisa bem bolada (já que os objetos ficam gravados em tabelas internas do banco, simplesmente insere uma linha a mais pro objeto , mantendo o mesmo nome na coluna de nome do objeto MAS colocando numa coluna adicional um “rótulo”, uma “etiqueta” indicando a Edition – a “versão” mais recente – , e temporariamente fica com dois códigos-fonte gravados, um com a edition antiga que está em execução e outro com a mais recente), e bem facinho de usar – realmente não sei porque essa facilidade não é mais usada, acho que desconhecimento mesmo….
Um dia vou ser que nem você José. Tô estudando para isso..kkkkkk