Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione

Ciao

Ho deciso di condividere la mia scoperta: il frutto di pensieri, tentativi ed errori.
In generale: questa non è una scoperta, ovviamente - tutto questo avrebbe dovuto essere noto da molto tempo a coloro che sono coinvolti nell'elaborazione statistica applicata dei dati e nell'ottimizzazione di qualsiasi sistema, non necessariamente nello specifico del DBMS.
E: sì, lo sanno, scrivono articoli interessanti sulle loro ricerche, esempio (UPD.: nei commenti hanno segnalato un progetto molto interessante: ottundire )
D’altra parte: a prima vista non vedo alcuna menzione o diffusione diffusa di questo approccio su Internet tra gli specialisti IT, DBA.

Quindi, al punto.

Supponiamo di avere un compito: impostare un determinato sistema di servizi per servire qualche tipo di lavoro.

Si sa di questo lavoro: cos'è, come viene misurata la qualità di questo lavoro e qual è il criterio per misurare questa qualità.

Supponiamo anche che sia più o meno noto e compreso: esattamente come viene svolto il lavoro in (o con) questo sistema di servizi.

"Più o meno" - ciò significa che è possibile preparare (o ottenerlo da qualche parte) un determinato strumento, utilità, servizio che può essere sintetizzato e applicato al sistema con un carico di prova sufficientemente adeguato a ciò che sarà in produzione, in condizioni sufficientemente adeguate per lavorare in produzione.

Ebbene, supponiamo che sia nota una serie di parametri di regolazione per questo sistema di servizi, che possono essere utilizzati per configurare questo sistema in termini di produttività del suo lavoro.

E qual è il problema: non esiste una comprensione sufficientemente completa di questo sistema di servizi, che consenta di configurare abilmente le impostazioni di questo sistema per il caricamento futuro su una determinata piattaforma e ottenere la produttività richiesta del sistema.

BENE. Questo è quasi sempre il caso.

Che cosa si può fare qui?

Bene, la prima cosa che mi viene in mente è guardare la documentazione di questo sistema. Comprendere quali sono gli intervalli accettabili per i valori dei parametri di regolazione. E, ad esempio, utilizzando il metodo di discesa delle coordinate, seleziona i valori per i parametri di sistema nei test.

Quelli. dare al sistema una sorta di configurazione, sotto forma di un insieme specifico di valori per i suoi parametri di configurazione.

Applica un carico di prova, utilizzando proprio questo strumento di utilità, il generatore di carico.
E guarda il valore: la risposta o una metrica della qualità del sistema.

Il secondo pensiero potrebbe essere la conclusione che si tratta di un tempo molto lungo.

Ebbene, cioè: se ci sono molti parametri di regolazione, se gli intervalli dei loro valori coperti sono ampi, se ogni singolo test di carico richiede molto tempo per essere completato, allora: sì, tutto ciò potrebbe richiedere una quantità inaccettabile di tempo.

Bene, ecco cosa puoi capire e ricordare.

Puoi scoprire che nell'insieme dei valori dei parametri delle impostazioni del sistema di servizio è presente un vettore, come sequenza di alcuni valori.

Ciascuno di questi vettori, a parità di altre condizioni (in quanto non è influenzato da questo vettore), corrisponde a un valore completamente definito della metrica, un indicatore della qualità del funzionamento del sistema sotto un carico di prova.

ie

Indichiamo il vettore di configurazione del sistema come Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazioneDove Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione; Dove Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione — numero di parametri di configurazione del sistema, quanti di questi parametri ci sono.

E il valore della metrica corrispondente a questo Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione indichiamolo come
Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione, quindi otteniamo una funzione: Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione

Ebbene: nel mio caso tutto si riduce subito a: algoritmi quasi dimenticati dai tempi dello studente per la ricerca dell'estremo di una funzione.

Va bene, ma qui sorge una questione organizzativa e applicativa: quale algoritmo utilizzare.

  1. Nel senso, in modo da poter codificare meno a mano.
  2. E affinché funzioni, ad es. trovato l'estremo (se ce n'è uno), beh, almeno più veloce della discesa coordinata.

Il primo punto suggerisce che dobbiamo guardare verso alcuni ambienti in cui tali algoritmi sono già stati implementati e sono, in qualche modo, pronti per l’uso nel codice.
Beh, lo so python и cran-r

Il secondo punto significa che devi leggere gli algoritmi stessi, cosa sono, quali sono i loro requisiti e le caratteristiche del loro lavoro.

E ciò che danno possono essere utili effetti collaterali: risultati o direttamente dall'algoritmo stesso.

Oppure possono essere ottenuti dai risultati dell'algoritmo.

Molto dipende dalle condizioni di ingresso.

Ad esempio, se, per qualche motivo, hai bisogno di ottenere un risultato più velocemente, beh, devi guardare agli algoritmi di discesa del gradiente e sceglierne uno.

Oppure, se il tempo non è così importante, puoi, ad esempio, utilizzare metodi di ottimizzazione stocastica, come un algoritmo genetico.

Propongo di considerare il lavoro di questo approccio, selezionando la configurazione del sistema, utilizzando un algoritmo genetico, nel prossimo, per così dire: lavoro di laboratorio.

Fonte:

  1. Sia, come sistema di servizi: oracle xe 18c
  2. Lascia che serva all'attività transazionale e all'obiettivo: ottenere il massimo rendimento possibile del sottodatabase, in transazioni/sec.
  3. Le transazioni possono essere molto diverse nella natura del lavoro con i dati e nel contesto di lavoro.
    Conveniamo che si tratta di transazioni che non elaborano una grande quantità di dati tabulari.
    Nel senso che non generano più dati di annullamento che di ripristino e non elaborano grandi percentuali di righe e tabelle di grandi dimensioni.

Si tratta di transazioni che modificano una riga in una tabella più o meno grande, con un numero limitato di indici su questa tabella.

In questa situazione: la produttività del sottodatabase per l'elaborazione delle transazioni sarà, con una riserva, determinata dalla qualità dell'elaborazione da parte del database redox.

Dichiarazione di non responsabilità: se parliamo specificamente delle impostazioni del subdb.

Perché, nel caso generale, potrebbero esserci, ad esempio, blocchi transazionali tra sessioni SQL, a causa della progettazione del lavoro dell'utente con dati tabulari e/o del modello tabulare.

Il che, ovviamente, avrà un effetto deprimente sulla metrica TPS e questo sarà un fattore esogeno, relativo al sottodatabase: beh, è ​​così che è stato progettato il modello tabulare e il lavoro con i dati in esso contenuti che si verificano dei blocchi.

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

  1. Supponiamo, per certezza, che il 100% dei comandi SQL inviati al database siano comandi DML.
    Lascia che le caratteristiche dell'utente che lavora con il sottodatabase siano le stesse nei test.
    Vale a dire: il numero di sessioni skl, dati tabulari, come funzionano le sessioni skl.
  2. Subd funziona FORCE LOGGING, ARCHIVELOG mod. La modalità database Flashback è disattivata a livello di sottocategoria.
  3. Registri di ripetizione: 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:

Maggiori dettagli sul dispositivo fisico. componenti 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, in queste condizioni di carico, volevo utilizzare la transazione subd Utilità SLOB
Ha una caratteristica così meravigliosa, citerò l'autore:

Il cuore di SLOB è il “metodo SLOB”. Il Metodo SLOB mira a testare le piattaforme
senza contesa applicativa. Non è possibile ottenere le massime prestazioni hardware
utilizzando il codice dell'applicazione che è, ad esempio, vincolato dal blocco dell'applicazione o addirittura
condividere i blocchi del database Oracle. Esatto: si verifica un sovraccarico durante la condivisione dei dati
nei blocchi dati! Ma SLOB – nella sua implementazione predefinita – è immune da tale contesa.

Questa dichiarazione: corrisponde, lo è.
È conveniente regolare il grado di parallelismo delle sessioni cl, questa è la chiave -t avviare l'utilità runit.sh da SLOB
La percentuale di comandi DML è regolata, nel numero di SMS che vengono inviati al subd, in ogni sessione di testo, parametro UPDATE_PCT
Separatamente e molto comodamente: SLOB stesso, prima e dopo la sessione di caricamento - prepara uno statspack o awr-snapshots (ciò che è impostato per essere preparato).

Tuttavia, si è scoperto che SLOB non supporta sessioni SQL con una durata inferiore a 30 secondi.
Pertanto, ho prima codificato la mia versione operaia-contadina del caricatore, e poi è rimasta in funzione.

Vorrei chiarire cosa fa il caricatore e come lo fa, per chiarezza.
Essenzialmente il caricatore si presenta 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 lanciati in questo modo:

Lavoratori in corsa

echo "starting test, duration: ${TEST_DURATION}" >> "$v_logfile"
for((i=1;i<="$SQLSESS_COUNT";i++))
do
 echo "sql-session: ${i}" >> "$v_logfile"
 dotx "$i" "${TEST_DURATION}" &
done
echo "waiting..." >> "$v_logfile"
wait

E i tavoli per i lavoratori sono preparati così:

Creazione di 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<1000
;
create index testtab_&&wnum._idx on system.testtab_&&wnum (col1);
--alter table system.testtab_&&wnum nologging;
--alter index system.testtab_&&wnum._idx nologging;
exit
__EOF__
}
export -f createtable

seq 1 1 "$SQLSESS_COUNT" | xargs -n 1 -P 4 -I {} -t bash -c "createtable "{}"" | tee -a "$v_logfile"
echo "createtable done" >> "$v_logfile"

Quelli. Per ogni lavoratore (in pratica: una sessione SQL separata nel DB) viene creata una tabella separata, con la quale lavora.

Ciò garantisce l'assenza di blocchi transazionali tra le sessioni di lavoro.
Ogni lavoratore: fa la stessa cosa, con il proprio tavolo, i tavoli sono tutti uguali.
Tutti i lavoratori svolgono il lavoro per lo stesso periodo di tempo.
Inoltre, per un tempo sufficientemente lungo in modo che, ad esempio, si verifichi sicuramente un cambio di registro e più di una volta.
Ebbene, di conseguenza, sono sorti i costi e gli effetti associati.
Nel mio caso ho configurato la durata del lavoro dei lavoratori in 8 minuti.

Una parte di un report di statspack che descrive il funzionamento del subd 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

Ritornando al lavoro di laboratorio.
Varieremo, a parità di altre condizioni, i valori dei seguenti parametri del sottodatabase di laboratorio:

  1. Dimensioni dei gruppi di log del database. intervallo valori: [32, 1024] MB;
  2. Numero di gruppi di riviste nel database. intervallo valori: [2,32];
  3. log_archive_max_processes intervallo 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 valori: [1,4]
  9. undo_retention intervallo valori: [30;300] secondi
  10. transactions_per_rollback_segment intervallo 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;

Una persona con esperienza nella manutenzione dei database Oracle può certamente già dire quali e quali valori dovrebbero essere impostati, dai parametri specificati e dai loro valori accettabili, al fine di ottenere una maggiore produttività del database per il lavoro con i dati indicati da il codice dell'applicazione, qui sopra.

Ma.

Lo scopo del lavoro di laboratorio è mostrare che l'algoritmo di ottimizzazione stesso ci chiarirà questo problema in tempi relativamente brevi.

A noi non resta che guardare nel documento, attraverso il sistema personalizzabile, quanto basta per scoprire quali parametri modificare e in quali intervalli.
E anche: codificare il codice che verrà utilizzato per funzionare con il sistema personalizzato dell'algoritmo di ottimizzazione selezionato.

Quindi, ora parliamo del codice.
Ne ho parlato sopra cran-r, cioè: tutte le manipolazioni con il sistema personalizzato vengono orchestrate sotto forma di uno script R.

Il compito effettivo, l'analisi, la selezione in base al valore metrico, i vettori dello stato del sistema: questo è un pacchetto GA (la documentazione)
Il pacchetto, in questo caso, è poco adatto, nel senso che prevede che i vettori (cromosomi, se in termini di pacchetto) siano specificati sotto forma di stringhe di numeri con parte frazionaria.

E il mio vettore, dai valori dei parametri di impostazione: si tratta di 14 quantità: numeri interi e valori stringa.

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

Pertanto, alla fine, la parte principale dello script R assomiglia a questa:

Chiama 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 lower и upper attributi della subroutine ga in sostanza viene specificata un'area dello spazio di ricerca, all'interno della quale verrà effettuata la ricerca di tale vettore (o vettori) per il quale si otterrà il valore massimo della funzione di fitness.

La subroutine ga esegue una ricerca massimizzando la funzione fitness.

Ebbene, risulta che, in questo caso, è necessario che la funzione fitness, intendendo il vettore come un insieme di valori per determinati parametri del sottod, riceva una metrica dal sottod.

Cioè: quante, con una determinata configurazione di sottodisco e un dato carico sul sottodisco: il sottodisco elabora le transazioni al secondo.

Cioè, durante l'apertura, è necessario eseguire i seguenti passaggi multipli all'interno della funzione fitness:

  1. Elaborazione del vettore di input dei numeri, conversione in valori per i parametri dei dati secondari.
  2. Un tentativo di creare un determinato numero di gruppi di ripetizione di una determinata dimensione. Inoltre, il tentativo potrebbe non avere successo.
    Gruppi rivista che già esistevano nel sottod., in una certa quantità e di una certa dimensione, per la purezza dell'esperimento - d.b. cancellato.
  3. Se il punto precedente ha esito positivo: specificare i valori dei parametri di configurazione nel database (di nuovo: potrebbe esserci un errore)
  4. Se il passaggio precedente ha esito positivo: interrompere il subd, avviare il subd in modo che i valori dei parametri appena specificati abbiano effetto. (di nuovo: potrebbe esserci un problema tecnico)
  5. Se il passaggio precedente ha esito positivo: eseguire un test di carico. ottenere le metriche da subd.
  6. Riporta il sottotitolo al suo stato originale, ad es. eliminare ulteriori gruppi di log, ripristinare il funzionamento della configurazione del database secondario originale.

Codice funzione 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 ) {
 opn$filesystemio_options="setall"
}
if ( round(p_par[12],digit=0) > 20 && round(p_par[12],digit=0) <= 30 ) {
 opn$filesystemio_options="directIO"
}
if ( 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 ) {
 opn$db_block_checking="LOW"
}
if ( round(p_par[13],digit=0) > 20 && round(p_par[13],digit=0) <= 30 ) {
 opn$db_block_checking="MEDIUM"
}
if ( 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="TYPICAL"
}
if ( 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)
}

Quello. tutto il lavoro: eseguito nella funzione fitness.

La subroutine ga elabora i vettori o, più correttamente, i cromosomi.
In cui, ciò che è più importante per noi è la selezione di cromosomi con geni per i quali la funzione fitness produce grandi valori.

Questo, in sostanza, è il processo di ricerca dell'insieme ottimale di cromosomi utilizzando un vettore in uno spazio di ricerca N-dimensionale.

Molto chiaro, dettagliato spiegazione, con esempi di R-code, il lavoro di un algoritmo genetico.

Vorrei sottolineare separatamente due punti tecnici.

Chiamate ausiliarie dalla funzione evaluate, ad esempio, stop-start, impostando il valore del parametro subd, vengono eseguiti in base a cran-r funzioni system2

Con l'aiuto di cui: viene chiamato uno script o un comando bash.

Per esempio:

set_db_parametro

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 punto è la linea, evaluate funzioni, con il salvataggio di un valore metrico specifico e del corrispondente vettore di ottimizzazione in un file di registro:

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

Questo è importante perché da questo array di dati sarà possibile ottenere ulteriori informazioni su quale dei componenti del vettore di tuning ha un effetto maggiore o minore sul valore della metrica.

Cioè: sarà possibile condurre analisi di importanza-attributo.

Allora cosa può succedere?

In forma grafica, se si ordinano i test in ordine metrico crescente, l'immagine è la seguente:

Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione

Alcuni dati corrispondenti ai valori estremi della metrica:
Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione
Qui, nello screenshot con i risultati, chiarirò: i valori del vettore di tuning sono dati in termini di codice della funzione fitness, non in termini di elenco numerico di parametri/intervalli di valori di parametri, che è stato formulato sopra nel testo.

BENE. È tanto o poco, ~8mila tps: una questione a parte.
Nell'ambito del lavoro di laboratorio, questa cifra non è importante, ciò che è importante è la dinamica, come cambia questo valore.

Le dinamiche qui sono buone.
È ovvio che almeno un fattore influenza in modo significativo il valore della metrica, l'algoritmo ga, che seleziona i vettori cromosomici: coperto.
A giudicare dalla dinamica piuttosto vigorosa dei valori della curva, c'è almeno un altro fattore che, sebbene significativamente più piccolo, ha un'influenza.

Qui è dove ne hai bisogno attribute-importance analisi per capire quali attributi (beh, in questo caso, componenti del vettore di ottimizzazione) e quanto influenzano il valore della metrica.
E da queste informazioni: capire quali fattori sono stati influenzati dai cambiamenti negli attributi significativi.

corsa attribute-importance possibile in diversi modi.

Per questi scopi, mi piace l'algoritmo randomForest Pacchetto R con lo stesso nome (la documentazione)
randomForest, da quanto ho capito il suo lavoro in generale e il suo approccio alla valutazione dell'importanza degli attributi in particolare, costruisce un certo modello della dipendenza della variabile di risposta dagli attributi.

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

ecco randomForest valuta l'importanza di ciascun attributo del modello con due numeri: %IncMSE — come la presenza/assenza di questo attributo in un modello modifica la qualità MSE di questo modello (errore quadratico medio);

E IncNodePurity è un numero che riflette quanto bene, in base ai valori di questo attributo, è possibile dividere un set di dati con osservazioni, in modo che in una parte ci siano dati con un valore della metrica spiegato e nell'altra con un altro valore della metrica.
Bene, cioè: fino a che punto questo è un attributo di classificazione (ho visto la spiegazione più chiara in lingua russa su RandomForest qui).

Codice R lavoratore-contadino per l'elaborazione di un set di dati con i risultati delle prove 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

Puoi selezionare direttamente con le mani gli iperparametri dell'algoritmo e, concentrandoti sulla qualità del modello, selezionare un modello che soddisfi in modo più accurato le previsioni sul set di dati di validazione.
Puoi scrivere una sorta di funzione per questo lavoro (a proposito, ancora una volta, utilizzando una sorta di algoritmo di ottimizzazione).

Puoi usare il pacchetto R caret, non il punto è importante.

Di conseguenza, in questo caso, per valutare il grado di importanza degli attributi si ottiene il seguente risultato:

Il metodo scientifico del poke, ovvero come selezionare la configurazione di un database utilizzando benchmark e un algoritmo di ottimizzazione

BENE. Possiamo quindi avviare una riflessione globale:

  1. Si scopre che il più significativo, in queste condizioni di test, è stato il parametro commit_wait
    Tecnicamente, specifica la modalità di esecuzione dell'operazione io di scrittura dei dati di redo dal buffer di log subdb al gruppo di log corrente: sincrono o asincrono.
    Valore nowait che si traduce in un aumento quasi verticale e multiplo del valore della metrica tps: si tratta dell'inclusione della modalità io asincrona nei gruppi di ripetizione.
    Una domanda a parte è se dovresti farlo o meno in un database alimentare. Qui mi limito ad affermare semplicemente: questo è un fattore significativo.
  2. È logico che la dimensione del buffer di log del subd: risulti essere un fattore significativo.
    Minore è la dimensione del buffer di registro, minore è la sua capacità di buffering, più spesso si verifica un overflow e/o l'impossibilità di allocare un'area libera al suo interno per una porzione di nuovi dati redox.
    Ciò significa: ritardi associati all'allocazione dello spazio nel buffer di registro e/o al trasferimento dei dati di ripetizione da esso nei gruppi di ripetizione.
    Questi ritardi, ovviamente, dovrebbero influenzare la velocità di elaborazione delle transazioni del database.
  3. Parametro db_block_checksum: beh, inoltre, in generale è chiaro: l'elaborazione delle transazioni porta alla formazione di blocchi dannosi nella cache del buffer del database secondario.
    Che, quando il controllo dei checksum dei blocchi dati è abilitato, il database deve elaborare: calcolare questi checksum dal corpo del blocco dati, controllarli con ciò che è scritto nell'intestazione del blocco dati: corrisponde/non corrisponde.
    Tale lavoro, ancora una volta, non può che ritardare l'elaborazione dei dati e, di conseguenza, il parametro e il meccanismo che imposta questo parametro risultano significativi.
    Ecco perché il venditore offre, nella documentazione per questo parametro, valori diversi per esso (il parametro) e nota che sì, ci sarà un impatto, ma, beh, puoi scegliere valori diversi, fino a "off" e impatti diversi.

Bene, una conclusione globale.

L’approccio, in generale, risulta essere abbastanza funzionante.

Si permette abbastanza, nelle prime fasi del test di carico di un determinato sistema di servizio, di selezionare la sua configurazione ottimale (di sistema) per il carico, di non approfondire troppo le specifiche della configurazione del sistema per il carico.

Ma questo non lo esclude del tutto, almeno a livello di comprensione: il sistema deve conoscere le "manopole di regolazione" e gli intervalli di rotazione consentiti di queste manopole.

L'approccio può quindi trovare in tempi relativamente brevi la configurazione ottimale del sistema.
E in base ai risultati dei test, è possibile ottenere informazioni sulla natura della relazione tra i parametri delle prestazioni del sistema e i valori dei parametri delle impostazioni di sistema.

Il che, ovviamente, dovrebbe contribuire all'emergere di questa comprensione molto profonda del sistema, del suo funzionamento, almeno sotto un determinato carico.

In pratica, si tratta di uno scambio dei costi di comprensione del sistema personalizzato con i costi di preparazione di tale test del sistema.

Vorrei notare separatamente: in questo approccio, il grado di adeguatezza dei test del sistema alle condizioni operative che avrà nell'operazione commerciale è di fondamentale importanza.

Grazie per l'attenzione e il tempo.

Fonte: habr.com

Aggiungi un commento