Consolidated Replays no Oracle Real Application Testing
O Oracle Real Application Testing (RAT) é uma option do banco Oracle que possui ferramentas que permitem realizar a captura do workload de um banco, reproduzir esse workload em outro ambiente, e analisar a diferença entre a performance do ambiente de origem, onde a captura foi realizada, e o ambiente destino, onde o replay foi executado. Inclusive, já falei sobre uma das ferramentas do Oracle RAT, no artigo Analisando a performance de queries com o SQL Perfomance Analyzer. Neste artigo, vou falar sobre outra ferramenta da option, o Database Replay, que usei para realizar alguns testes de viabilidade de migração de um Oracle RAC para um novo hardware.
Cenário
O ambiente, como disse, era um Oracle RAC, multitenant, com quatro PDBs. Como ponto de partida, um standby físico foi criado no novo hardware. O standby físico pode ser convertido posteriormente para um snapshot standby, e onde o Database Replay pode ser executado.
O ponto de atenção aqui é a utilização do Oracle RAT em um ambiente multitenant, que necessita de uma abordagem diferente. Neste caso, como são quatro PDBs, é necessário realizar uma captura em cada PDB, e depois, no ambiente onde será executado o replay, fazer a execução de um Consolidated Replay, que basicamente agrupa as quatro capturas e as executa ao mesmo tempo.
Estrutura de diretórios
Como a captura será realizada em um Oracle RAC, utilizei um NFS para armazenar os arquivos de captura do workload. Também é possível utilizar um diretório que exista em cada um dos nós do RAC, mas posteriormente é necessário juntar os dois conjuntos de arquivos gerados em um local central para o replay.
Para a estrutura dos diretórios, visando usar o Consolidated Reaply posteriormente, devemos ter um diretório central que contém os diretórios de cada captura, como no exemplo:
$ tree /u01/rat /u01/rat ├── pdb1 ├── pdb2 ├── pdb3 └── pdb4
Realizando a captura
A captura do workload precisa ser feita em cada um dos PDBs, apontando para seu devido diretório:
ALTER SESSION SET CONTAINER=PDB1; CREATE DIRECTORY PDB1_DIR AS '/u01/rat/pdb1'; EXEC DBMS_WORKLOAD_CAPTURE.START_CAPTURE(name => 'CAPTURA_PDB1', dir => 'PDB1_DIR', duration => 7200); ALTER SESSION SET CONTAINER=PDB2; CREATE OR REPLACE DIRECTORY PDB2_DIR AS '/u01/rat/pdb2'; EXEC DBMS_WORKLOAD_CAPTURE.START_CAPTURE(name => 'CAPTURA_PDB2', dir => 'PDB2_DIR', duration => 7200); ALTER SESSION SET CONTAINER=PDB3; CREATE or replace DIRECTORY PDB3_DIR AS '/u01/rat/pdb3'; EXEC DBMS_WORKLOAD_CAPTURE.START_CAPTURE(name => 'CAPTURA_PDB3', dir => 'PDB3_DIR', duration => 7200); ALTER SESSION SET CONTAINER=PDB4; CREATE OR REPLACE DIRECTORY PDB4_DIR AS '/u01/rat/pdb4'; EXEC DBMS_WORKLOAD_CAPTURE.START_CAPTURE(name => 'CAPTURA_PDB4', dir => 'PDB4_DIR', duration => 7200);
Nesse caso, especifiquei um período de duas horas para a captura, que foi iniciada em um momento de pico da base.
Finalizada a captura, é feita exportação dos dados do AWR do período da captura para posterior comparação com os dados do replay. Essa exportação é feita também em cada um dos PDBs:
ALTER SESSION SET CONTAINER=PDB1; BEGIN DBMS_WORKLOAD_CAPTURE.EXPORT_AWR (capture_id => 4); END; / ALTER SESSION SET CONTAINER=PDB2; BEGIN DBMS_WORKLOAD_CAPTURE.EXPORT_AWR (capture_id => 4); END; / ALTER SESSION SET CONTAINER=PDB3; BEGIN DBMS_WORKLOAD_CAPTURE.EXPORT_AWR (capture_id => 4); END; / ALTER SESSION SET CONTAINER=PDB4; BEGIN DBMS_WORKLOAD_CAPTURE.EXPORT_AWR (capture_id => 4); END; /
Para a identificação do capture_id a query abaixo pode ser usada:
set lines window COLUMN name FORMAT a15 SELECT id capture_id, name, status, to_char(start_time,'dd/mm/yy hh24:mi') start_time, to_char(end_time,'dd/mm/yy hh24:mi') end_time FROM dba_workload_captures ORDER BY id DESC;
Executando o replay
Previamente à execução do replay, converti o standby físico criado para snpashot standby e montei o NFS. O primeiro passo agora é a criação dos diretórios, no CDB$ROOT do snapshot standby, tanto do diretório raiz, quantos dos diretórios onde estão as capturas:
CREATE DIRECTORY RAT_DIR AS '/u01/rat/'; CREATE DIRECTORY PDB4_DIR AS '/u01/rat/pdb4'; CREATE DIRECTORY PDB1_DIR AS '/u01/rat/pdb1'; CREATE DIRECTORY PDB2_DIR AS '/u01/rat/pdb2'; CREATE DIRECTORY PDB3_DIR AS '/u01/rat/pdb3';
Na sequência é necessário processar as capturas:
EXEC DBMS_WORKLOAD_REPLAY.PROCESS_CAPTURE('PDB1_DIR');
EXEC DBMS_WORKLOAD_REPLAY.PROCESS_CAPTURE('PDB2_DIR');
EXEC DBMS_WORKLOAD_REPLAY.PROCESS_CAPTURE('PDB3_DIR');
EXEC DBMS_WORKLOAD_REPLAY.PROCESS_CAPTURE('PDB4_DIR');
Agora, como diversas capturas serão utilizadas no replay, é necessário apontar o diretório raiz das capturas:
EXEC DBMS_WORKLOAD_REPLAY.SET_REPLAY_DIRECTORY('RAT_DIR');
Como estamos trabalhando com diversas capturas, é necessário criar um objeto schedule, e atribuir as capturas a ele, para agrupá-las e associar com o Consolidated Replay:
SQL> EXEC DBMS_WORKLOAD_REPLAY.BEGIN_REPLAY_SCHEDULE('MY_SCHEDULE');
SQL> SELECT DBMS_WORKLOAD_REPLAY.ADD_CAPTURE('PDB1_DIR') FROM DUAL;
ID
-----
1
SQL> SELECT DBMS_WORKLOAD_REPLAY.ADD_CAPTURE('PDB2_DIR') FROM DUAL;
ID
-----
2
SQL> SELECT DBMS_WORKLOAD_REPLAY.ADD_CAPTURE('PDB3_DIR') FROM DUAL;
ID
-----
3
SQL> SELECT DBMS_WORKLOAD_REPLAY.ADD_CAPTURE('PDB4_DIR') FROM DUAL;
ID
-----
4
SQL> EXEC DBMS_WORKLOAD_REPLAY.END_REPLAY_SCHEDULE;
Cada função ADD_CAPTURE retorna o id daquela captura dentro do schedule. Esse é um dado importante que será utilizado posteriormente para realizar o mapeamento das conexões do replay. Esse id também pode ser obtido através da view DBA_WORKLOAD_SCHEDULE_CAPTURES:
SELECT SCHEDULE_CAP_ID, CAPTURE_DIR FROM DBA_WORKLOAD_SCHEDULE_CAPTURES WHERE SCHEDULE_NAME='MY_SCHEDULE';
Com a schedule criada, o Consolidated Replay pode ser incializado através da procedure INITIALIZE_CONSOLIDATED_REPLAY:
EXEC DBMS_WORKLOAD_REPLAY.INITIALIZE_CONSOLIDATED_REPLAY('MY_CONSOLIDATED_REPLAY','MY_SCHEDULE');
Como no replay estamos executando todos os passos no CDB$ROOT, mas durante o replay cada sessão precisará se conectar ao PDB correto, precisamos fazer um remapeamento das conexões. A consulta abaixo pode ser usada para verificar as conexões de um replay específico:
set lines window col replay_conn for a150 select conn_id, schedule_cap_id, replay_conn from dba_workload_connection_map WHERE replay_id=1;
O replay_id pode ser consultado através da view DBA_WORKLOAD_REPLAYS.
Agora, para cada conexão, deve ser feito o remapeamento para o PDB correto, de acordo com o seu schedule_cap_id, que vimos anteriormente ao gerar o schedule. Para isso a procedure REMAP_CONNECTION é usada:
exec DBMS_WORKLOAD_REPLAY.REMAP_CONNECTION (connection_id => 1,SCHEDULE_CAP_ID=>1,replay_connection =>'(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=mydbhost.domain)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=PDB1_SERVICE)))');
Como no meu caso haviam mais de 300 conexões para serem remapeadas, utilizei o PL/SQL abaixo para fazer o remap:
SET SERVEROUTPUT ON
DECLARE
CURSOR c_map IS
SELECT conn_id, schedule_cap_id
FROM dba_workload_connection_map
ORDER BY conn_id;
v_replay_conn VARCHAR2(4000);
FUNCTION get_replay_conn(p_schedule_id NUMBER) RETURN VARCHAR2 IS
BEGIN
CASE p_schedule_id
WHEN 1 THEN RETURN '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=mydbhost.domain)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=PDB1_SERVICE)))';
WHEN 2 THEN RETURN '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=mydbhost.domain)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=PDB2_SERVICE)))';
WHEN 3 THEN RETURN '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=mydbhost.domain)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=PDB3_SERVICE)))';
WHEN 4 THEN RETURN '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=mydbhost.domain)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=PDB4_SERVICE)))';
ELSE RETURN NULL;
END CASE;
END;
BEGIN
FOR r IN c_map LOOP
v_replay_conn := get_replay_conn(r.schedule_cap_id);
IF v_replay_conn IS NULL THEN
DBMS_OUTPUT.PUT_LINE(
'Pulando conn_id='||r.conn_id||' (schedule_cap_id '||r.schedule_cap_id||' não mapeado)'
);
ELSE
DBMS_WORKLOAD_REPLAY.REMAP_CONNECTION(
connection_id => r.conn_id,
schedule_cap_id => r.schedule_cap_id,
replay_connection => v_replay_conn
);
DBMS_OUTPUT.PUT_LINE(
'Remapeado conn_id='||r.conn_id||' (schedule_cap_id='||r.schedule_cap_id||')'
);
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Remapeamento concluído.');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Erro: '||SQLERRM);
RAISE;
END;
/
Após essa execução, os mapeamentos podem ser verificados na consulta:
set lines window col replay_conn for a150 select conn_id,schedule_cap_id, replay_conn from dba_workload_connection_map;
Com o remapeamento completo, colocamos o replay em modo prepare:
EXEC DBMS_WORKLOAD_REPLAY.PREPARE_CONSOLIDATED_REPLAY(SYNCHRONIZATION=>'TIME');
Na sequência, pode ser feita a calibragem dos clients de replay, o WRC. Aqui o calibrate precisa ser executado para cada um dos diretórios contendo os workloads que serão executados:
$ wrc system@my_cdb mode=calibrate replaydir=/u01/rat/PDB1 $ wrc system@my_cdb mode=calibrate replaydir=/u01/rat/PDB2 $ wrc system@my_cdb mode=calibrate replaydir=/u01/rat/PDB3 $ wrc system@my_cdb mode=calibrate replaydir=/u01/rat/PDB4
Para cada uma das execuções, um retorno semelhante ao abaixo será mostrado:
Workload Report ----------------------- Recommendation: Consider using at least 7 clients divided among 2 CPU(s) You will need at least 142 MB of memory per client process. If your machine(s) cannot match that number, consider using more clients. Workload Characteristics: - max concurrency: 38 sessions - total number of sessions: 86 Assumptions: - 100 concurrent sessions per client process - 4 client processes per CPU - 256 KB of memory cache per concurrent session - think time scale = 100 - connect time scale = 100 - synchronization = TRUE
Neste retorno existe a recomendação do número de clients que precisa ser iniciado para aquele diretório. Para o Consolidated Replay é necessário iniciar um número de clients igual a soma de todas as recomendações. No meu caso, foram 11 clients:
$ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out1.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out2.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out3.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out4.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out5.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out6.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out7.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out8.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out9.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out10.log 2>&1 & $ nohup wrc system/senha@my_cdb mode=replay replaydir=/u01/rat > out11.log 2>&1 &
É importante notar que quando iniciamos os clients para um Consolidated Replay, apontamos o diretório raiz (no meu caso /u01/rat) para cada client.
Com os clients conectados, o replay pode ser iniciado:
EXEC DBMS_WORKLOAD_REPLAY.START_CONSOLIDATED_REPLAY;
A consulta abaixo pode ser usada para acompanhar o status do replay:
COL NAME FOR A20 SELECT ID, NAME, START_TIME, END_TIME, STATUS FROM DBA_WORKLOAD_REPLAYS ORDER BY ID DESC;
Gerando as análises
Para finalizar, um schema é criado para realizar o import dos dados de AWR exportados no ambiente de captura:
CREATE USER C##RAT_STAGE IDENTIFIED BY RAT_STAGE DEFAULT TABLESPACE USERS; GRANT DBA TO C##RAT_STAGE;
Na sequência, os IDs das capturas são consultados:
set lines window COLUMN name FORMAT a15 SELECT id capture_id, name, status, to_char(start_time,'dd/mm/yy hh24:mi') start_time, to_char(end_time,'dd/mm/yy hh24:mi') end_time FROM dba_workload_captures ORDER BY id DESC;
E é realizada a importação dos dados de AWR para cada captura:
VAR RET NUMBER EXEC :RET := DBMS_WORKLOAD_CAPTURE.IMPORT_AWR(capture_id => 21, staging_schema => 'C##RAT_STAGE'); PRINT RET VAR RET NUMBER EXEC :RET := DBMS_WORKLOAD_CAPTURE.IMPORT_AWR(capture_id => 22, staging_schema => 'C##RAT_STAGE'); PRINT RET VAR RET NUMBER EXEC :RET := DBMS_WORKLOAD_CAPTURE.IMPORT_AWR(capture_id => 23, staging_schema => 'C##RAT_STAGE'); PRINT RET VAR RET NUMBER EXEC :RET := DBMS_WORKLOAD_CAPTURE.IMPORT_AWR(capture_id => 24, staging_schema => 'C##RAT_STAGE'); PRINT RET
Com os dados de AWR importados, e o replay finalizado, um relatório comparativo entre a captura e o replay pode ser gerado:
var comp_report CLOB; EXEC DBMS_WORKLOAD_REPLAY.COMPARE_PERIOD_REPORT(replay_id1 => 1, replay_id2 => null, format => 'HTML', result => :comp_report); set serveroutput on size unlimited set pagesize 0 long 30000000 longchunksize 2000 linesize 600 spool comparereport.html print comp_report spool off
Conclusão
O Database Replay pode ser muito útil para identificar gargalos do ambiente onde o replay é executado, possibilitando executar melhorias antes de migrações. Aliado com o SQL Performance Analyzer, é possível evitar supresas indesejadas antes de colocar o banco produtivo no novo ambiente.
Além disso, o uso dessas ferramentas em um Snapshot Standby possibilita a implementação de melhorias, como a criação de índices por exemplo, sem afetar o ambiente produtivo, e com a segurança de que essas alterações serão desfeitas uma vez que o standby for convertido de volta para físico.
1 comentário