Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione

Ciao.

Ho deciso di condividere la mia scoperta — il frutto di riflessioni, prove e errori.
In sostanza: non si tratta affatto di una scoperta, naturalmente — tutto questo dovrebbe essere già noto a chi si occupa di elaborazione dei dati e ottimizzazione di vari sistemi, non necessariamente di un DBMS.
E: sì, lo sanno, scrivono articoli interessanti sui loro studi. esempio (UPD.: nei commenti è stato segnalato un progetto molto interessante: ottertune )
D'altra parte: a colpo d'occhio non vedo una vasta menzione o diffusione di questo approccio, su Internet, tra gli specialisti IT e DBA.

Quindi, andiamo al sodo.

Supponiamo che abbiamo il compito di configurare un certo sistema di servizio, per gestire un determinato lavoro.

Di questo lavoro — è noto: quale sia, in cosa consiste la qualità di questo lavoro e quale sia il criterio per misurare questa qualità.

Inoltre, supponiamo che sia più o meno chiaro: come viene eseguito il lavoro in (o con) questo sistema di servizio.

"Più o meno" — significa che c'è la possibilità di preparare (o di ottenere) un certo strumento, utility, servizio con cui è possibile generare e fornire al sistema un carico di test abbastanza adeguato a quello che ci sarà in produzione, in condizioni sufficientemente realistiche per l'operatività in produzione.

E, supponiamo anche che sia noto l'insieme dei parametri di regolazione di questo sistema di servizio, che possono essere utilizzati per configurarlo, in termini di produttività.

E, qual è il problema — non si ha una comprensione sufficientemente completa di questo sistema di servizio, tale da consentire di impostare esperti delle configurazioni di questo sistema per il carico futuro, su questa piattaforma, e ottenere la produttività desiderata del sistema.

Beh. Così va sempre.

Cosa si può fare al riguardo.

Beh, la prima cosa che viene in mente: consultare la documentazione di questo sistema. Comprendere — quali siano i valori accettabili per le configurazioni di tutti i parametri. E, ad esempio, usando il metodo del discesa delle coordinate, adattare i valori per i parametri del sistema nei test.

Cioè, impostare al sistema una certa configurazione, sotto forma di un insieme specifico di valori dei suoi parametri di configurazione.

Fornire un carico di test, utilizzando proprio questo strumento-utility, generatore di carico.
E osservare il valore — la risposta, o la metrica della qualità del lavoro del sistema.

Una seconda riflessione potrebbe essere che — è molto lungo.

Cioè: se i parametri di configurazione sono numerosi, se gli intervalli di valori che possono assumere sono ampi, se ogni singolo test di carico richiede molto tempo per essere eseguito, allora: sì, tutto questo potrebbe richiedere un tempo inaccettabile.

E qui cosa si può comprendere e ricordare.

Si può scoprire che, nell'insieme dei valori dei parametri di configurazione del sistema di servizio — esiste un vettore, come sequenza di certi valori.

A ciascun vettore del genere, a parità di condizioni (a meno che questo vettore non sia toccato), corrisponde un valore ben definito della metrica — indicatore della qualità del lavoro del sistema, sotto carico di test.

Cioè.

Designiamo il vettore di configurazione del sistema come Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione, dove Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione; dove Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione — il numero di parametri di configurazione del sistema, quanti sono, questi parametri.

E il valore della metrica corrispondente a questo Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione indichiamo come
Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione, quindi, otteniamo una funzione: Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione

E, allora: tutto si riduce immediatamente a, nel mio caso: quasi dimenticati dai tempi dell'università, algoritmi di ricerca degli estremi delle funzioni.

Bene, ma qui sorge una questione organizzativa e pratica: quale algoritmo utilizzare.

  1. Nel senso — per non dover scrivere troppo codice a mano.
  2. E per farlo funzionare, cioè trovare un estremo (se c'è) più rapidamente almeno rispetto alla discesa delle coordinate.

Il primo suggerimento indica che bisognerebbe guardare verso ambienti in cui tali algoritmi sono già implementati e disponibili, in qualche forma, pronti per essere utilizzati nel codice.
Beh, mi sono noti python e cran-r

Il secondo suggerimento indica che è necessario leggere riguardo gli algoritmi stessi, quali esistono, quali sono i loro requisiti, caratteristiche operative.

E ciò che offrono, quali risultati collaterali utili possono avere, o direttamente, dall'algoritmo stesso.

Oppure possono provenire dai risultati finali dell'algoritmo.

Qui molto dipende dalle condizioni iniziali.

Ad esempio, se per qualche motivo è necessario ottenere rapidamente un risultato, bisogna guardare verso algoritmi di discesa del gradiente, scegliendone uno.

Oppure, se non è così importante il tempo, si potrebbero utilizzare metodi di ottimizzazione stocastica, come l'algoritmo genetico.

Propongo di considerare il funzionamento di tale approccio, per l'ottimizzazione della configurazione del sistema, utilizzando un algoritmo genetico, nel prossimo, così dire: lavoro di laboratorio.

Origine:

  1. Supponiamo di avere, come sistema di servizio: oracle xe 18c
  2. Supponiamo che gestisca l'attività transazionale con l'obiettivo di ottenere la massima capacità possibile del DBMS, in termini di transazioni al secondo.
  3. Le transazioni possono essere molto diverse, a seconda del modo in cui lavorano con i dati e del contesto operativo.
    Assumiamo che queste transazioni non elaborino un grande numero di dati tabellari.
    Nel senso che non generano dati di undo superiori a quelli di redo e non elaborano percentuali elevate di righe in grandi tabelle.

Si tratta di transazioni che modificano una riga in una tabella di dimensioni ragionevoli, con un numero limitato di indici su di essa.

In questo scenario, la produttività del DBMS nell'elaborazione delle transazioni sarà, con riserva, determinata dalla qualità della gestione della base di dati dei dati di redo.

La riserva si aplica solo a determinate configurazioni del DBMS.

Perché, in generale, possono esserci, ad esempio, blocchi transazionali tra sessioni SQL, a causa del design del lavoro degli utenti con i dati tabellari e/o della modello tabellare.

Questi blocchi influenzeranno inevitabilmente la metrica tps e saranno un fattore esogeno, relativamente al DBMS: il design della modello tabellare e il lavoro dei dati hanno generato blocchi.

Pertanto, per la chiarezza dell'esperimento, escludiamo questo fattore e chiarirò come esattamente.

  1. Assumiamo, per chiarezza, che il 100% dei comandi SQL inviati al DBMS siano comandi DML.
    Assumiamo che le caratteristiche del lavoro degli utenti con il DBMS rimangano identiche nei test.
    Cioè: il numero di sessioni SQL, i dati tabellari, e come queste sessioni SQL interagiscono con i dati.
  2. Il DBMS opera in FORCE LOGGING, ARCHIVELOG modalità. La modalità flashback del database è disattivata, a livello del DBMS.
  3. I redo log: si trovano in un file system separato, su un 'disco' separato;
    Tutta la restante parte della componente fisica del DB: in un altro file system separato, su un 'disco' separato:

Ulteriori dettagli sulla configurazione della componente fisica del database di laboratorio

SQL> select status||' '||name from v$controlfile;
 /db/u14/oradata/XE/control01.ctl
SQL> select GROUP#||' '||MEMBER from v$logfile;
1 /db/u02/oradata/XE/redo01_01.log
2 /db/u02/oradata/XE/redo02_01.log
SQL> select FILE_ID||' '||TABLESPACE_NAME||' '||round(BYTES/1024/1024,2)||' '||FILE_NAME as col from dba_data_files;
4 UNDOTBS1 2208 /db/u14/oradata/XE/undotbs1_01.dbf
2 SLOB 128 /db/u14/oradata/XE/slob01.dbf
7 USERS 5 /db/u14/oradata/XE/users01.dbf
1 SYSTEM 860 /db/u14/oradata/XE/system01.dbf
3 SYSAUX 550 /db/u14/oradata/XE/sysaux01.dbf
5 MONITOR 128 /db/u14/oradata/XE/monitor.dbf
SQL> !cat /proc/mounts | egrep '/db/u[0-2]' 
/dev/vda1 /db/u14 ext4 rw,noatime,nodiratime,data=ordered 0 0
/dev/mapper/vgsys-ora_redo /db/u02 xfs rw,noatime,nodiratime,attr2,nobarrier,inode64,logbsize=256k,noquota 0 0

Inizialmente, per queste condizioni di carico, volevo utilizzare SLOB-utility
Ha questa straordinaria caratteristica, cito l'autore:

At the heart of SLOB is the “SLOB method.” The SLOB Method aims to test platforms
without application contention. One cannot drive maximum hardware performance
using application code that is, for example, bound by application locking or even
sharing Oracle Database blocks. That’s right—there is overhead when sharing data
in data blocks! But SLOB—in its default deployment—is immune to such contention.

Questa affermazione è corretta, così è.
È comodo regolare il grado di parallelismo delle sessioni SQL, questo è la chiave -t per avviare l'utilità runit.sh inclusa in SLOB.
Si può regolare la percentuale di comandi DML, nel numero di sessioni SQL che inviano al DBMS, parametro UPDATE_PCT
Separatamente e molto convenientemente: SLOB stesso, prima e dopo la sessione di carico — prepara i report di stats pack, o istantanee AWR (a seconda di ciò che è stato impostato per essere preparato).

Tuttavia, si è scoperto che SLOB non supporta il funzionamento delle sessioni SQL con una durata inferiore ai 30 secondi.
Pertanto, inizialmente ho sviluppato la mia versione del caricatore, che poi è rimasta in uso.

Chiarirò riguardo al caricatore — cosa fa e come, per chiarezza.
Essenzialmente, il caricatore appare così:

Codice del lavoratore

function dotx()
{
local v_period="$2"
[ -z "v_period" ] && v_period="0"
source "/home/oracle/testingredotracе/config.conf"

$ORACLE_HOME/bin/sqlplus -S system/${v_system_pwd} << __EOF__
whenever sqlerror exit failure
set verify off
set echo off
set feedback off

define wnum="$1"
define period="$v_period"
set appinfo worker_&&wnum

declare
 v_upto number;
 v_key  number;
 v_tots number;
 v_cts  number;
begin
 select max(col1) into v_upto from system.testtab_&&wnum;
 SELECT (( SYSDATE - DATE '1970-01-01' ) * 86400 ) into v_cts FROM DUAL;
 v_tots := &&period + v_cts;
 while v_cts <= v_tots
 loop
  v_key:=abs(mod(dbms_random.random,v_upto));
  if v_key=0 then
   v_key:=1;
  end if;
  update system.testtab_&&wnum t
  set t.object_name=translate(dbms_random.string('a', 120), 'abcXYZ', '158249')
  where t.col1=v_key
  ;
  commit;
  SELECT (( SYSDATE - DATE '1970-01-01' ) * 86400 ) into v_cts FROM DUAL;
 end loop;
end;
/

exit
__EOF__
}
export -f dotx

I lavoratori vengono avviati in questo modo:

Avvio dei lavoratori

echo "avvio del test, durata: ${TEST_DURATION}" >> "$v_logfile"
for((i=1;i> "$v_logfile"
 dotx "$i" "${TEST_DURATION}" &
done
echo "in attesa..." >> "$v_logfile"
wait

E le tabelle per i lavoratori vengono preparate in questo modo:

Creazione delle tabelle

function createtable() {
source "/home/oracle/testingredotracе/config.conf"
$ORACLE_HOME/bin/sqlplus -S system/${v_system_pwd} << __EOF__
whenever sqlerror continue
set verify off
set echo off
set feedback off

define wnum="$1"
define ts_name="slob"

begin
 execute immediate 'drop table system.testtab_&&wnum';
exception when others then null;
end;
/

create table system.testtab_&&wnum tablespace &&ts_name as
select rownum as col1, t.*
from sys.dba_objects t
where rownum> "$v_logfile"

Cioè, per ogni worker (praticamente: una sessione SQL separata nel DB) viene creata una tabella separata, con cui lavora il worker.

Questo porta all'assenza di blocchi transazionali tra le sessioni SQL dei workers.
Ogni worker esegue la stessa operazione con la propria tabella, tutte le tabelle sono identiche.
I workers svolgono tutti il lavoro per la stessa durata di tempo.
In effetti, è un periodo di tempo abbastanza lungo, in modo che, ad esempio, avvenga chiaramente uno switch di log, e non solo una volta.
E a seconda di questo, sorgono i costi e gli effetti correlati.
Nel mio caso, ho impostato la durata del lavoro dei workers a 8 minuti.

Un estratto del rapporto Statspack, con la descrizione del funzionamento del DB sotto carico

Database    DB Id    Instance     Inst Num  Startup Time   Release     RAC
~~~~~~~~ ----------- ------------ -------- --------------- ----------- ---
          2929910313 XE                  1 07-Sep-20 23:12 18.0.0.0.0  NO

Host Name             Platform                CPUs Cores Sockets   Memory (G)
~~~~ ---------------- ---------------------- ----- ----- ------- ------------
     billing.izhevsk1 Linux x86 64-bit           2     2       1         15.6

Snapshot       Snap Id     Snap Time      Sessions Curs/Sess Comment
~~~~~~~~    ---------- ------------------ -------- --------- ------------------
Begin Snap:       1630 07-Sep-20 23:12:27       55        .7
  End Snap:       1631 07-Sep-20 23:20:29       62        .6
   Elapsed:       8.03 (mins) Av Act Sess:       8.4
   DB time:      67.31 (mins)      DB CPU:      15.01 (mins)

Cache Sizes            Begin        End
~~~~~~~~~~~       ---------- ----------
    Buffer Cache:     1,392M              Std Block Size:         8K
     Shared Pool:       288M                  Log Buffer:   103,424K

Load Profile              Per Second    Per Transaction    Per Exec    Per Call
~~~~~~~~~~~~      ------------------  ----------------- ----------- -----------
      DB time(s):                8.4                0.0        0.00        0.20
       DB CPU(s):                1.9                0.0        0.00        0.04
       Redo size:        7,685,765.6              978.4
   Logical reads:           60,447.0                7.7
   Block changes:           47,167.3                6.0
  Physical reads:                8.3                0.0
 Physical writes:              253.4                0.0
      User calls:               42.6                0.0
          Parses:               23.2                0.0
     Hard parses:                1.2                0.0
W/A MB processed:                1.0                0.0
          Logons:                0.5                0.0
        Executes:           15,756.5                2.0
       Rollbacks:                0.0                0.0
    Transactions:            7,855.1

Tornando all'assegnazione del lavoro di laboratorio.
Verrà, a parità di altre condizioni, variato i valori di tali parametri del DB di laboratorio:

  1. Dimensione dei gruppi di log del DB. intervallo di valori: [32, 1024] MB;
  2. Numero di gruppi di log del DB. intervallo di valori: [2,32];
  3. log_archive_max_processes intervallo di valori: [1,8];
  4. commit_logging sono consentiti due valori: batch|immediate;
  5. commit_wait sono consentiti due valori: wait|nowait;
  6. log_buffer intervallo di valori: [2,128] MB.
  7. log_checkpoint_timeout intervallo di valori: [60,1200] secondi
  8. db_writer_processes intervallo di valori: [1,4]
  9. undo_retention intervallo di valori: [30;300] secondi
  10. transactions_per_rollback_segment intervallo di valori: [1,8]
  11. disk_asynch_io sono consentiti due valori: true|false;
  12. filesystemio_options valori consentiti: none|setall|directIO|asynch;
  13. db_block_checking valori consentiti: OFF|LOW|MEDIUM|FULL;
  14. db_block_checksum valori consentiti: OFF|TYPICAL|FULL;

Una persona con esperienza nella manutenzione delle basi di dati Oracle può sicuramente dire fin da ora quali valori impostare, tra i parametri e i loro valori consentiti, per ottenere una maggiore produttività del DB, per il tipo di lavoro con i dati descritto nel codice applicativo sopra.

Ma.

Il senso del lavoro di laboratorio è dimostrare che l'algoritmo di ottimizzazione ci chiarirà ciò rapidamente.

A noi resta solo da consultare la documentazione del sistema configurabile, tanto quanto basta per scoprire quali parametri e in quali intervalli modificare.
E inoltre, codificare il codice che realizzerà il lavoro con il sistema configurabile secondo l'algoritmo di ottimizzazione scelto.

Quindi, ora sul codice.
Ho parlato sopra di cran-r, cioè: tutte le manipolazioni con il sistema configurabile vengono orchestrate sotto forma di script R.

Il compito, l'analisi, la selezione dei valori della metrica, dei vettori di stato del sistema: è un pacchetto GA (documentazione)
Il pacchetto, in questo caso, non è molto adatto, nel senso che si aspetta che i vettori (cromosomi, se si usano termini del pacchetto) siano forniti come numeri in virgola mobile.

E il mio vettore, composto dai valori dei parametri di configurazione: sono 14 grandezze - numeri interi e valori stringa.

Il problema, naturalmente, è facilmente aggirabile, assegnando ai valori stringa dei numeri definiti.

Quindi, alla fine, la parte principale dello script R appare così:

Chiamata GA::ga

cat( "", file=v_logfile, sep="n", append=F)

pSize = 10
elitism_value=1
pmutation_coef=0.8
pcrossover_coef=0.1
iterations=50

gam=GA::ga(type="real-valued", fitness=evaluate,
lower=c(32,2, 1,1,1,2,60,1,30,1,0,0, 0,0), upper=c(1024,32, 8,10,10,128,800,4,300,8,10,40, 40,30),
popSize=pSize,
pcrossover = pcrossover_coef,
pmutation = pmutation_coef,
maxiter=iterations,
run=4,
keepBest=T)
cat( "GA-session is done" , file=v_logfile, sep="n", append=T)
gam@solution

Qui, con l'aiuto di inferiore e superiore attributi della sottoprogramma ga si definisce, in sostanza, l'area dello spazio di ricerca all'interno della quale sarà eseguita la ricerca del vettore (o dei vettori) per i quali si otterrà il valore massimo della funzione di fitness.

La sottoprogramma ga esegue una ricerca massimizzando la funzione di fitness.

Quindi, in questo caso, è necessario che la funzione di fitness, considerando il vettore come un insieme di valori per determinati parametri del database, restituisca una metrica dal database.

Cioè: quante transazioni il database elabora al secondo in base a questa configurazione e a questo carico.

Cioè, svolgendo, è necessario che all'interno della funzione di fitness venga eseguita la seguente procedura:

  1. Elaborazione del vettore di input di numeri — trasformazione in valori per i parametri del database.
  2. Tentativo di creare il numero assegnato di gruppi di redo, di dimensioni specificate. Tuttavia, il tentativo potrebbe fallire.
    I gruppi di log già esistenti nel database, in una certa quantità e di una certa dimensione, devono essere rimossi per la pulizia dell'esperimento.
  3. Se il passo precedente ha successo: assegnazione alla base dei valori dei parametri di configurazione (di nuovo: potrebbe esserci un errore)
  4. Se il passo precedente ha successo: arrestare il database, avviare il database affinché i nuovi valori dei parametri abbiano effetto. (di nuovo: potrebbe esserci un errore)
  5. Se il passo precedente ha successo: eseguire un test di carico e ottenere una metrica dal database.
  6. Riportare il database allo stato originale, cioè rimuovere i gruppi di log aggiuntivi e ripristinare la configurazione originale del database.

Codice della funzione di fitness

evaluate=function(p_par) {
v_module="evaluate"
v_metric=0
opn=NULL
opn$rg_size=round(p_par[1],digit=0)
opn$rg_count=round(p_par[2],digit=0)
opn$log_archive_max_processes=round(p_par[3],digit=0)
opn$commit_logging="BATCH"
if ( round(p_par[4],digit=0) > 5 ) {
 opn$commit_logging="IMMEDIATE"
}
opn$commit_logging=paste("'", opn$commit_logging, "'",sep="")

opn$commit_wait="WAIT"
if ( round(p_par[5],digit=0) > 5 ) {
 opn$commit_wait="NOWAIT"
}
opn$commit_wait=paste("'", opn$commit_wait, "'",sep="")

opn$log_buffer=paste(round(p_par[6],digit=0),"m",sep="")
opn$log_checkpoint_timeout=round(p_par[7],digit=0)
opn$db_writer_processes=round(p_par[8],digit=0)
opn$undo_retention=round(p_par[9],digit=0)
opn$transactions_per_rollback_segment=round(p_par[10],digit=0)
opn$disk_asynch_io="true"
if ( round(p_par[11],digit=0) > 5 ) {
 opn$disk_asynch_io="false"
} 

opn$filesystemio_options="none"
if ( round(p_par[12],digit=0) > 10 && round(p_par[12],digit=0)  20 && round(p_par[12],digit=0)  30 ) {
 opn$filesystemio_options="asynch"
}

opn$db_block_checking="OFF"
if ( round(p_par[13],digit=0) > 10 && round(p_par[13],digit=0)  20 && round(p_par[13],digit=0)  30 ) {
 opn$db_block_checking="FULL"
}

opn$db_block_checksum="OFF"
if ( round(p_par[14],digit=0) > 10 && round(p_par[14],digit=0)  20 ) {
 opn$db_block_checksum="FULL"
}

v_vector=paste(round(p_par[1],digit=0),round(p_par[2],digit=0),round(p_par[3],digit=0),round(p_par[4],digit=0),round(p_par[5],digit=0),round(p_par[6],digit=0),round(p_par[7],digit=0),round(p_par[8],digit=0),round(p_par[9],digit=0),round(p_par[10],digit=0),round(p_par[11],digit=0),round(p_par[12],digit=0),round(p_par[13],digit=0),round(p_par[14],digit=0),sep=";")
cat( paste(v_module," try to evaluate vector: ", v_vector,sep="") , file=v_logfile, sep="n", append=T)

rc=make_additional_rgroups(opn)
if ( rc!=0 ) {
 cat( paste(v_module,"make_additional_rgroups failed",sep="") , file=v_logfile, sep="n", append=T)
 return (0)
}

v_rc=0
rc=set_db_parameter("log_archive_max_processes", opn$log_archive_max_processes)
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("commit_logging", opn$commit_logging )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("commit_wait", opn$commit_wait )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("log_buffer", opn$log_buffer )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("log_checkpoint_timeout", opn$log_checkpoint_timeout )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("db_writer_processes", opn$db_writer_processes )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("undo_retention", opn$undo_retention )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("transactions_per_rollback_segment", opn$transactions_per_rollback_segment )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("disk_asynch_io", opn$disk_asynch_io )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("filesystemio_options", opn$filesystemio_options )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("db_block_checking", opn$db_block_checking )
if ( rc != 0 ) {  v_rc=1 }
rc=set_db_parameter("db_block_checksum", opn$db_block_checksum )
if ( rc != 0 ) {  v_rc=1 }

if ( rc!=0 ) {
 cat( paste(v_module," can not startup db with that vector of settings",sep="") , file=v_logfile, sep="n", append=T)
 rc=stop_db("immediate")
 rc=create_spfile()
 rc=start_db("")
 rc=remove_additional_rgroups(opn)
 return (0)
}

rc=stop_db("immediate")
rc=start_db("")
if ( rc!=0 ) {
 cat( paste(v_module," can not startup db with that vector of settings",sep="") , file=v_logfile, sep="n", append=T)
 rc=stop_db("abort")
 rc=create_spfile()
 rc=start_db("")
 rc=remove_additional_rgroups(opn)
 return (0)
}

rc=run_test()
v_metric=getmetric()

rc=stop_db("immediate")
rc=create_spfile()
rc=start_db("")
rc=remove_additional_rgroups(opn)

cat( paste("result: ",v_metric," ",v_vector,sep="") , file=v_logfile, sep="n", append=T)
return (v_metric)
}

Quindi, tutto il lavoro: viene eseguito nella funzione di fitness.

La sottoprogramma ga elabora i vettori, o, più precisamente, le cromosomi.
In cui, ci interessa maggiormente: la selezione delle cromosomi con tali geni, per i quali la funzione di fitness produce valori elevati.

Questo è sostanzialmente il processo di ricerca del set ottimale di cromosomi come vettore, nello spazio di ricerca N-dimensionale.

Molto chiaro, dettagliato spiegazione, con esempi di codice R, sul funzionamento dell'algoritmo genetico.

Voglio sottolineare due aspetti tecnici.

Chiamate ausiliarie, dalla funzione evaluate, ad esempio avvio-interruzione, impostazione del valore del parametro del database, vengono eseguite sulla base della cran-r funzione system2

Con cui: viene richiamato un qualche script bash o comando.

Ad esempio:

set_db_parameter

set_db_parameter=function(p1, p2) {
v_module="set_db_parameter"
v_cmd="/home/oracle/testingredotrace/set_db_parameter.sh"
v_args=paste(p1," ",p2,sep="")

x=system2(v_cmd, args=v_args, stdout=T, stderr=T, wait=T)
if ( length(attributes(x)) > 0 ) {
 cat(paste(v_module," failed with: ",attributes(x)$status," ",v_cmd," ",v_args,sep=""), file=v_logfile, sep="n", append=T)
 return (attributes(x)$status)
}
else {
 cat(paste(v_module," ok: ",v_cmd," ",v_args,sep=""), file=v_logfile, sep="n", append=T)
 return (0)
}
}

Il secondo punto è la riga della evaluate funzione, mantenendo il valore specifico della metrica e il corrispondente vettore di configurazione, nel file di log:

cat( paste("result: ",v_metric," ",v_vector,sep="") , file=v_logfile, sep="n", append=T)

Questo è importante, poiché da questo array di dati si può ottenere informazioni aggiuntive su quale componente del vettore di configurazione influisca di più o di meno sul valore della metrica.

Cioè: sarà possibile condurre un'analisi sull'importanza degli attributi.

Ecco cosa potrebbe risultare.

In forma di grafico, ordinando i test in base all'aumento della metrica, la situazione è la seguente:

Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione

Alcuni dati corrispondenti ai valori estremi della metrica:
Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione
Qui, nello screenshot dei risultati, chiarisco: i valori del vettore di configurazione sono dati in termini di codice della funzione obiettivo, non in termini di lista dei numeri dei parametri/di intervalli di valori dei parametri che ho formulato sopra nel testo.

Bene. È molto o poco, ~8k tps: la questione è separata.
Nel contesto del lavoro di laboratorio — questo numero non è importante, quello che conta è la dinamica, come questo valore cambia.

La dinamica qui è buona.
È ovvio che, almeno un fattore, influisce significativamente sul valore della metrica, l'algoritmo genetico, esplorando i vettori-cromosomi: ha colpito.
A giudicare dalla dinamica piuttosto vivace dei valori della curva — c'è almeno un altro fattore, che, sebbene di importanza inferiore, influisce.

Qui è necessaria un' analisi dell'importanza degli attributi per comprendere: quali attributi (bene, in questo caso — componenti del vettore di configurazione) e quanto influiscono sul valore della metrica.
E da queste informazioni: capire — quali fattori sono stati coinvolti nei cambiamenti di attributi significativi.

Esegui analisi dell'importanza degli attributi si può fare in vari modi.

A me, per questi scopi, piace l'algoritmo randomForest del pacchetto R omonimo (documentazione)
randomForest, come capisco il suo funzionamento in generale e il suo approccio alla valutazione dell'importanza degli attributi in particolare, costruisce un certo modello di dipendenza della variabile risposta dagli attributi.

Nel nostro caso, la variabile risposta è la metrica ottenuta dal database, nei test di carico: tps;
E gli attributi sono — i componenti del vettore di configurazione.

Ecco perché randomForest valuta l'importanza di ogni attributo del modello con due numeri: %IncMSE — come la presenza/assenza di questo attributo nel modello modifica la qualità di MSE di questo modello (Mean Squared Error);

E IncNodePurity — è un numero che riflette quanto bene, in base ai valori di questo attributo, si può dividere il dataset con le osservazioni, in modo che in una parte ci siano i dati con un certo valore della metrica spiegata, e nell'altra un altro valore della metrica.
Cioè: quanto è questo attributo classificante (la spiegazione più chiara in russo sull'algoritmo random forest l'ho vista qui).

Codice R da lavoratore-contadino, per elaborare il dataset con i risultati dei test di carico:

x=NULL
v_data_file=paste('/tmp/data1.dat',sep="")
x=read.table(v_data_file, header = TRUE, sep = ";", dec=",", quote = ""'", stringsAsFactors=FALSE)
colnames(x)=c('metric','rgsize','rgcount','lamp','cmtl','cmtw','lgbffr','lct','dbwrp','undo_retention','tprs','disk_async_io','filesystemio_options','db_block_checking','db_block_checksum')

idxTrain=sample(nrow(x),as.integer(nrow(x)*0.7))
idxNotTrain=which(! 1:nrow(x) %in% idxTrain )
TrainDS=x[idxTrain,]
ValidateDS=x[idxNotTrain,]

library(randomForest)
#mtry=as.integer( sqrt(dim(x)[2]-1) )
rf=randomForest(metric ~ ., data=TrainDS, ntree=40, mtry=3, replace=T, nodesize=2, importance=T, do.trace=10, localImp=F)
ValidateDS$predicted=predict(rf, newdata=ValidateDS[,colnames(ValidateDS)!="metric"], type="response")
sum((ValidateDS$metric-ValidateDS$predicted)^2)
rf$importance

Si possono modificare manualmente gli iperparametri dell'algoritmo e, basandosi sulla qualità del modello, scegliere un modello più preciso che esegue previsioni sul dataset di validazione.
Si può scrivere qualche funzione per questo lavoro (tra l'altro — di nuovo, su qualche algoritmo di ottimizzazione).

Si può utilizzare il pacchetto R caret, non è rilevante.

Alla fine, in questo caso, si ottiene un risultato del genere, per valutare il grado di importanza degli attributi:

Il metodo del tentativo e errore, o come selezionare la configurazione del database utilizzando benchmark e un algoritmo di ottimizzazione

Bene. Dunque, si può passare a grandi riflessioni:

  1. Si scopre che il parametro più significativo, in queste condizioni di test, è risultato essere commit_wait
    Tecnicamente, definisce la modalità di esecuzione delle operazioni di scrittura dei dati redo, dal buffer di log del database, nel gruppo di registro attuale: sincrono o asincrono.
    Significato nowait in cui si ottiene un aumento praticamente verticale e moltiplicativo del valore della metrica tps: ciò è dovuto all'attivazione della modalità asincrona io nei gruppi redo.
    Un'altra questione è se sia necessario farlo nel database di produzione. Qui mi limito solo a constatare: è un fattore significativo.
  2. È logico che la dimensione del log buffer del DBMS risulti un fattore significativo.
    Minore è la dimensione del log buffer, minore è la sua capacità di buffering, più frequentemente si verificano le sovrapposizioni e/o l’impossibilità di allocare uno spazio libero per una nuova porzione di dati redo.
    E ciò significa: ritardi legati all'allocazione dello spazio nel log buffer e/o al trasferimento dei dati redo da esso ai gruppi redo.
    Questi ritardi, ovviamente, devono influenzare e influenzano la capacità del DBMS di gestire le transazioni.
  3. Caratteristica db_block_checksum: beh, anche questo è chiaro — l'elaborazione delle transazioni porta alla formazione di dirty blocks nella cache del DBMS.
    I quali, con il controllo delle checksum dei data block attivato, il database è costretto a elaborare — calcolare queste checksum dal corpo del data block e confrontarle con ciò che è scritto nell'intestazione del data block: coincide/non coincide.
    Tale operazione, di nuovo, non può non rallentare l'elaborazione dei dati, e di conseguenza, il parametro e il meccanismo che stabilisce questo parametro risultano significativi.
    Per questo motivo il fornitore propone, nella documentazione su questo parametro, diversi suoi valori e sottolinea che — sì, ci sarà un impatto, ma, ecco, vari valori, fino a "disattivato" e diversi impatti, si possono scegliere.

E così, una conclusione globale.

L'approccio, in generale, risulta funzionante.

Consente, nelle prime fasi di test di carico di un certo sistema di servizio, di scegliere la configurazione ottimale di questo sistema per il carico senza dover approfondire le peculiarità della configurazione del sistema per il carico.

Ma non esclude del tutto — almeno a livello di comprensione: è necessario conoscere le "manopole di regolazione" e i range accettabili di rotazione di queste manopole.

In seguito, l'approccio può trovare rapidamente la configurazione ottimale del sistema.
E alla fine dei test, si può ottenere informazioni sulla natura della relazione tra le metriche di qualità del lavoro del sistema e i valori dei parametri di configurazione del sistema.

Questo, ovviamente, dovrebbe contribuire a generare quella profonda comprensione del sistema, del suo funzionamento, almeno — sotto questo carico.

Praticamente si tratta di scambiare i costi per comprendere un sistema configurabile, con i costi per preparare un tale test di funzionamento del sistema.

Sottolineo: in questo approccio, è critico avere un adeguato test del sistema alle condizioni in cui opererà in produzione.

Grazie per la vostra attenzione e tempo.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster