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

comments user
Rodrigo T. Martins

EXCELENTE Post meu amigo, parabens pelo trabalho, ainda não tinha lido nada sobre o CONSOLIDATED REPLAY.
Com relação ao RAT, tenho algumas considerações que tenho anotado nas minhas anotações aqui:
– Deixar numa maquina separada os arquivos da captura para realizar o replay, pois o processo de replay consome muitos recursos da maquina onde estão os arquivos, o que pode concorrer com os processos do banco
– Tem uma dica bacana que o Sueco PM do RAT nos passou que é no prepare do replay incluir o parametro exec dbms_workload_replay.prepare_replay(synchronization=>TIME, “query_only=> true”); que dessa forma ele “esquenta” o cache pois vai pegar somente os SELECTs da captura e rodar no ambiente

Publicar comentário