Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione

Salve.

Ho deciso di condividere la mia scoperta: il frutto di riflessioni, prove ed errori.
In linea di massima: non si tratta affatto di una scoperta, ovviamente: tutto ciò dovrebbe essere già ben noto a chi si occupa di elaborazione statistica dei dati e ottimizzazione di sistemi, non necessariamente di DBMS.
E: sì, lo sanno, scrivono articoli interessanti sulle loro ricerche, un esempio (UPD.: nei commenti è stato indicato un progetto molto interessante: ottertune )
D'altro canto: a colpo d'occhio non vedo un'ampia menzione o diffusione di questo approccio, su Internet, tra i professionisti IT e i DBA.

Quindi, andiamo al sodo.

Immaginiamo di avere il seguente compito: configurare un certo sistema di servizio, per gestire un certo lavoro.

Su questo lavoro è noto: di che tipo è, come viene misurata la qualità di questo lavoro e quale è il criterio per misurare questa qualità.

Supponiamo anche che sia più o meno noto e comprensibile: come viene eseguito il lavoro in (o con) questo sistema di servizio.

"Più o meno" significa che esiste la possibilità di preparare (o avere da qualche parte) un certo strumento, un'utility, un servizio con cui si può sintetizzare e fornire al sistema un carico di prova abbastanza adeguato a quello che sarà in produzione, in condizioni abbastanza adeguate al lavoro in produzione.

E, supponiamo che sia conosciuto l'insieme dei parametri di regolazione di questo sistema di servizio, che possono essere utilizzati per configurare il sistema, nel senso della sua produttività.

E, qual è il problema - non c'è una comprensione sufficientemente completa di questo sistema di servizio, tale da permettere una configurazione esperta di questo sistema, per il futuro carico su questa piattaforma e ottenere la produttività richiesta del lavoro del sistema.

Beh. Così, quasi sempre è.

Cosa si può fare qui.

Beh, la prima cosa che viene in mente: dare un'occhiata alla documentazione su questo sistema. Capire - quali sono i range accettabili per i valori dei parametri di regolazione. E, per esempio, usando il metodo del discesa coordinata, cercare di selezionare 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 regolazione.

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

La seconda idea potrebbe essere la conclusione che - è molto lungo.

Cioè: se ci sono molti parametri di configurazione, se gli intervalli che possono adottare i loro valori sono ampi, se ciascun test di carico richiede molto tempo, allora: sì, tutto ciò può richiedere un tempo inaccettabilmente lungo.

E qui cosa si può capire e ricordare.

È possibile sapere, nell'insieme di valori dei parametri di configurazione del sistema di servizio, un vettore, come sequenza di alcuni valori.

A ciascun vettore del genere, a parità di altre condizioni (in quanto non viene toccato da questo vettore) corrisponde un valore ben definito di una metrica — l'indicatore di qualità del lavoro del sistema, sotto carico di prova.

Cioè.

Indichiamo il vettore di configurazione del sistema come Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione, dove Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione; Dove Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione è il numero di parametri di configurazione del sistema, quanti sono, questi parametri.

E il valore della metrica, corrispondente a questo Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione lo indicheremo come
Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione, quindi otteniamo una funzione: Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione

E quindi: tutto si riduce immediatamente a, nel mio caso: a quasi dimenticati algoritmi di ricerca dell'estremo della funzione, risalenti ai tempi universitari.

Bene, ma qui sorge una questione organizzativo-pratica: quale algoritmo utilizzare esattamente.

  1. Cioè — per dover scrivere meno codice a mano.
  2. E per far sì che funzioni, cioè trovi l'estremo (se esiste), beh, almeno — più velocemente di un metodo di discesa delle coordinate.

Il primo punto suggerisce di guardare verso alcuni ambienti in cui tali algoritmi sono già stati implementati, e ci sono, in qualche modo, pronti per essere utilizzati nel codice.
Bene, conosco python e cran-r

Il secondo punto significa che devo leggere sui vari algoritmi, quali ci sono, quali sono i loro requisiti, particolarità di funzionamento.

E cosa forniscono, potrebbero esserci effetti collaterali utili — risultati, sia direttamente dal algoritmo stesso.

Oppure possono essere ottenuti dai risultati del lavoro dell'algoritmo.

Molto dipende dalle condizioni di entrata.

Ad esempio, se per qualche motivo è necessario ottenere il risultato più rapidamente, beh, bisogna guardare verso gli algoritmi di discesa del gradiente, scegliendone uno tra essi.

Oppure, se il tempo non è così importante, si possono utilizzare metodi di ottimizzazione stocastica, per esempio un algoritmo genetico.

Propongo di esaminare il funzionamento di questo approccio, per la selezione della configurazione del sistema, utilizzando un algoritmo genetico, nel prossimo, così da dire: lavoro di laboratorio.

Iniziali:

  1. Supponiamo che ci sia, come sistema di servizio: oracle xe 18c
  2. Supponiamo che essa gestisca l'attività transazionale e l'obiettivo: ottenere la massima capacità di elaborazione del database per transazioni al secondo.
  3. Le transazioni possono essere molto diverse, a seconda della loro natura nell'elaborazione dei dati e del contesto di utilizzo.
    Conveniamo che si tratta di transazioni che non elaborano grandi quantità di dati tabellari.
    Nel senso che non generano dati di undo superiori a quelli di redo e non elaborano grandi percentuali di righe di tabelle estese.

Si tratta di transazioni che modificano una riga in tabelle più o meno grandi, con un numero limitato di indici su queste tabelle.

In questa situazione, la produttività del database nell'elaborazione delle transazioni sarà, con riserva, determinata dalla qualità del trattamento dei dati di redo.

La riserva è se si parla specificamente delle impostazioni del database.

Perché, in generale, potrebbero esserci, ad esempio, blocchi transazionali tra le sessioni SQL, a causa del design dell'interazione dell'utente con i dati tabellari e/o il modello tabellare.

Questi, ovviamente, avranno un impatto negativo sulla metrica tps e questo sarà un fattore esogeno rispetto al database: beh, il modello tabellare e il modo in cui si interagisce con i dati sono stati progettati in modo tale da provocare dei blocchi.

Pertanto, per la pulizia dell'esperimento, escluderemo questo fattore, di seguito chiarirò come esattamente.

  1. Supponiamo, per maggiore chiarezza, che il 100% dei comandi SQL inviati al database siano comandi DML.
    Poniamo che le caratteristiche del lavoro degli utenti con il database siano le stesse nei test.
    E precisamente: numero di sessioni SQL, dati tabellari, come le sessioni SQL interagiscono con essi.
  2. Il database opera in FORCE LOGGING, ARCHIVELOG modalità. La modalità Flashback del database è disattivata, a livello di database.
  3. I redo log: sono situati in un file system separato, su un "disco" separato;
    Il resto della componente fisica del database: in un altro file system separato, su un "disco" separato:

Maggiore dettaglio sull'architettura 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 volevo utilizzare un SGBD per le transazioni in queste condizioni di carico SLOB-utility
Ha una caratteristica molto interessante, cito l'autore:

Al centro di SLOB c'è il “metodo SLOB.” Il metodo SLOB ha lo scopo di testare le piattaforme
senza contesa di applicazione. Non si può ottenere il massimo delle prestazioni hardware
utilizzando codice applicativo che è, ad esempio, vincolato da locking applicativo o addirittura
dalla condivisione di blocchi del database Oracle. Esatto: c'è un overhead quando si condividono dati
nei blocchi dati! Ma SLOB, nella sua implementazione predefinita, è immune a tale contesa.

Questa dichiarazione: corrisponde, ed è così.
È comodo regolare il grado di parallelismo delle sessioni SCL, è la chiave -t per avviare l'utilità runit.sh come parte di SLOB
Si regola la percentuale di comandi DML, nel numero di SCL che invia al SGBD, ogni sessione SCL, parametro UPDATE_PCT
Separatamente e molto comodamente: SLOB stesso, prima e dopo la sessione di carico — prepara statspack, o istantanee AWR (ciò che è stato impostato per preparare).

Tuttavia, è emerso che SLOB non supporta sessioni SCL con una durata inferiore a 30 secondi.
Pertanto, inizialmente ho scritto la mia versione lavorativa e contadina del carico, e poi è rimasta in uso.

Specifico riguardo al carico — cosa fa e come lo fa, per chiarezza.
Essenzialmente, il caricatore appare così:

Codice del worker

function dotx()
{
local v_period="$2"
[ -z "v_period" ] && v_period="0"
source "/home/oracle/testingredotrace/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 worker vengono avviati in questo modo:

Avvio dei worker

echo "avvio 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 worker 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;
\/\n
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: sessione sql separata nel DB) viene creata una tabella separata, con cui lavora il worker.

Questo consente di evitare blocchi transazionali tra le sessioni sql dei worker.
Ogni worker esegue la stessa operazione, sulla propria tabella; tutte le tabelle sono identiche.
Tutti i worker eseguono il lavoro per lo stesso periodo di tempo.
In effetti, un periodo di tempo sufficientemente lungo da garantire, ad esempio, che ci sia stato, e non solo una volta, un log-switch.
E di conseguenza, si generano costi ed effetti correlati.
Nel mio caso, la durata del lavoro dei worker è stata impostata 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

Riprendendo la questione della tesi di laboratorio.
Variamo, a parità di condizioni, i valori di questi parametri del sottosistema di laboratorio:

  1. Dimensione dei gruppi di log del database. Intervallo di valori: [32, 1024] MB;
  2. Numero di gruppi di log del database. Intervallo di valori: [2,32];
  3. log_archive_max_processes intervallo di valori: [1,8];
  4. commit_logging sono ammessi due valori: batch|immediate;
  5. commit_wait sono ammessi 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 ammessi due valori: true|false;
  12. filesystemio_options sono ammessi i seguenti valori: none|setall|directIO|asynch;
  13. db_block_checking sono ammessi i seguenti valori: OFF|LOW|MEDIUM|FULL;
  14. db_block_checksum sono ammessi i seguenti valori: OFF|TYPICAL|FULL;

Un professionista con esperienza nella gestione di database Oracle può sicuramente ora dire quali e quali valori impostare, tra i parametri indicati e i loro valori consentiti, per ottenere una maggiore produttività del sistema di gestione dei dati per il lavoro con i dati delineato dal codice applicativo qui sopra.

Ma.

Il senso del lavoro di laboratorio è dimostrare che l'algoritmo di ottimizzazione, di per sé, li definirà in modo relativamente veloce.

Rimane solo da dare un'occhiata alla documentazione, sul sistema configurabile, esattamente quanto basta per determinare: quali parametri e in quali intervalli modificare.
E inoltre: codificare il codice, con cui sarà realizzato il lavoro con il sistema configurabile dell'algoritmo di ottimizzazione scelto.

Pertanto, ora parliamo del codice.
Ho già parlato di cran-r, cioè: tutte le operazioni con il sistema configurabile sono orchestrate sotto forma di uno script R.

In effetti il compito, l'analisi, la selezione in base al valore della metrica, degli stati di vettore del sistema: questo è il pacchetto GA (documentazione)
Il pacchetto, in questo caso, non si adatta molto, nel senso che si aspetta di ricevere i vettori (cromosomi, se usiamo i termini del pacchetto) come numeri reali con parte frazionaria.

E il mio vettore, con i valori dei parametri di configurazione: sono 14 grandezze — numeri interi e valori stringa.

Il problema, ovviamente, può essere facilmente eluso, assegnando ai valori stringa alcuni numeri specifici.

Pertanto, alla fine, il pezzo 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( "La sessione GA è completata" , file=v_logfile, sep="n", append=T)
gam@solution

Qui, usando lower e upper degli attributi della sottoprogramma ga si definisce, di fatto, l'area dello spazio di ricerca, all'interno della quale verrà eseguita la ricerca di un vettore (o vettori) per il quale verrà ottenuto il valore massimo della funzione di fitness.

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

Pertanto, in questo caso, è necessario che la funzione di fitness, comprendendo il vettore come un insieme di valori per determinati parametri del DBMS, ottenga la metrica dal DBMS.

Cioè: quante transazioni al secondo gestisce il DBMS con questa configurazione e questo carico sul DBMS.

Cioè, esplicitando, è necessario che all'interno della funzione di fitness venga eseguita una tale manovra complessa:

  1. Elaborazione del vettore di input di numeri — trasformandolo in valori per i parametri del DBMS.
  2. Tentativo di creare un numero specificato di gruppi redo, di dimensione specificata. Tuttavia il tentativo potrebbe non avere successo.
    I gruppi di log già esistenti nel db, in una certa quantità e di una certa dimensione, devono essere eliminati per la purezza dell'esperimento.
  3. In caso di successo del punto precedente: assegnazione alla base dei valori dei parametri di configurazione (anche qui: potrebbe esserci un errore)
  4. In caso di successo del punto precedente: arresta il db, riavvia il db affinché i nuovi valori dei parametri diventino effettivi. (anche qui: potrebbe esserci un errore)
  5. In caso di successo del punto precedente: eseguire un test di carico. ottenere metriche dal db.
  6. Riportare il db allo stato iniziale, cioè eliminare i gruppi di log aggiuntivi, ripristinare la configurazione iniziale del db.

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," prova a valutare il vettore: ", 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 fallita",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," non posso avviare il db con quel vettore di impostazioni",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," non posso avviare il db con quel vettore di impostazioni",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("risultato: ",v_metric," ",v_vector,sep=""), file=v_logfile, sep="n", append=T)
return (v_metric)
}

Dunque, tutto il lavoro: viene svolto nella funzione di fitness.

Sottoprogramma ga, esegue l'elaborazione di vettori, o, per essere più precisi, di cromosomi.
In cui, a noi interessa soprattutto: la selezione di cromosomi con geni tali che la funzione di fitness restituisca valori elevati.

Questo, in sostanza è il processo di ricerca del set ottimale di cromosomi tramite un vettore, in uno spazio di ricerca N-dimensionale.

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

Sottolineo due aspetti tecnici.

Chiamate ausiliarie, dalla funzione evaluate, ad esempio avvio-arresto, assegnazione del valore del parametro db, vengono eseguite sulla base della cran-r funzione system2

Con cui: viene eseguito 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/testingredotracе/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 aspetto è la stringa, evaluate della funzione, con il salvataggio del valore specifico della metrica e il rispettivo vettore di configurazione, nel file log:

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

Questo è importante, poiché da questo array di dati, sarà possibile ottenere ulteriori informazioni su quale dei componenti del vettore di configurazione influisca di più o di meno sul valore della metrica.

Vale a dire: sarà possibile condurre un'analisi di importanza degli attributi.

Ecco cosa può risultare.

Sotto forma di grafico, se si ordinano i test per aumento della metrica, il quadro è questo:

Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione

Alcuni dati relativi ai valori estremi della metrica:
Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione
Qui, nello screenshot con i risultati, chiarisco: i valori del vettore di configurazione sono forniti in termini di codice della funzione di fitness, non in termini di elenco numerico di parametri/intervalli di valori dei parametri, che ho formulato sopra nel testo.

Beh. Che sia molto o poco, ~8k tps: è una questione a parte.
Nel contesto del lavoro di laboratorio, non è questa cifra a essere importante, ma la dinamica, come cambia questo valore.

La dinamica è buona.
È evidente che, come minimo, un fattore influisce significativamente sul valore della metrica, l'algoritmo ga, esaminando i vettori-cromosomi: l'ha coperto.
A giudicare dalla dinamica piuttosto vivace dei valori della curva, c'è almeno un altro fattore che, sebbene in misura minore, ha comunque un impatto.

Qui ci vuole attribute-importance un'analisi per comprendere: quali attributi (beh, in questo caso - componenti del vettore di configurazione) e quanto pesantemente influiscono sul valore della metrica.
E da queste informazioni: capire - quali fattori sono stati coinvolti nelle modifiche degli attributi significativi.

Esegui attribute-importance si possono utilizzare vari metodi.

A me, per questi scopi, piace l'algoritmo randomForest dell'omonimo pacchetto R (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 di risposta dagli attributi.

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

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

E IncNodePurity è un numero che riflette quanto bene, secondo i valori di questo attributo, si può suddividere 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, con un altro valore della metrica.
Cioè: quanto è un attributo classificante (la spiegazione più chiara, in russo, che ho visto sul random forest è stata qui).

Codice R da lavoro, per elaborare un 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 regolare manualmente gli iperparametri dell'algoritmo e, in base alla qualità del modello, scegliere un modello più preciso per effettuare 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 è importante.

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

Metodo dell'approccio scientifico, o come ottimizzare la configurazione del DBMS attraverso benchmark e algoritmi di ottimizzazione

Quindi, si può iniziare a riflessioni globali:

  1. Risulta che, in queste condizioni di test, il parametro più significativo è stato commit_wait
    Teoricamente, esso definisce la modalità di esecuzione dell'operazione io di scrittura dei dati redo, dal log buffer del database, nel gruppo di log corrente: sincrono o asincrono.
    Valore nowait in cui si ottiene un incremento praticamente verticale e multiplo del valore della metrica tps: questo è l'inclusione della modalità asincrona di io nei gruppi redo.
    Un'altra questione è se sia necessario o meno farlo nel database di produzione. Qui mi limito a constatare: è un fattore significativo.
  2. È logico che la dimensione del log buffer del database si riveli un fattore significativo.
    Più la dimensione del log buffer è piccola, minore è la sua capacità di buffering, più spesso si verifica il suo overflow e/o la mancanza di spazio libero per una nuova porzione di dati redo.
    E questo significa: ritardi legati all'allocazione di spazio nel log buffer e/o allo scarico dei dati redo da esso nei gruppi redo.
    Questi ritardi, ovviamente, devono influenzare e influenzano la capacità di transazione del database.
  3. Parametro db_block_checksum: beh, è anche abbastanza chiaro — l'elaborazione delle transazioni porta alla formazione di blocchi dati nel cache buffer del database.
    Questi, con il controllo attivato dei checksum dei blocchi dati, devono essere elaborati dalla base — calcolando questi checksum dal corpo del blocco dati e confrontandoli con ciò che è scritto nell'intestazione del blocco dati: corrisponde/non corrisponde.
    Tale operazione, nuovamente, non può non ritardare l'elaborazione dei dati, e di conseguenza, il parametro e il meccanismo che definisce questo parametro risultano significativi.
    Pertanto, il fornitore offre, nella documentazione su questo parametro, diversi valori per esso e sottolinea che — sì, ci sarà un impatto, ma, ecco, diversi valori, fino a "spento" e diverso impatto, potete scegliere.

E la conclusione globale.

L'approccio, in generale: si rivela piuttosto funzionante.

Permette, nelle fasi iniziali di un test di carico di un certo sistema di servizio, di scegliere la sua (sistema) configurazione ottimale per il carico senza dover approfondire eccessivamente le caratteristiche della configurazione del sistema per il carico.

Ma non esclude del tutto — almeno a livello di comprensione: "manopole di regolazione" e le gamme ammissibili di rotazione di queste manopole il sistema deve conoscerlo.

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

Questo, naturalmente, dovrebbe contribuire a creare questa profonda comprensione del sistema, del suo funzionamento, almeno — sotto questo carico.

Praticamente questo significa: scambiare i costi per comprendere il sistema configurabile con i costi per la preparazione di tale test di funzionamento del sistema.

Segnalo separatamente: in questo approccio — è fondamentale che il grado di adeguatezza del test del sistema sia rispetto alle condizioni in cui lavorerà nella produzione.

Grazie per la vostra attenzione, tempo.

Fonte: habr.com

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