Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego

Halo

Postanowiłem podzielić się swoim znaleziskiem - owocem przemyśleń, prób i błędów.
Ogólnie rzecz biorąc: nie jest to oczywiście odkrycie - wszystko to powinno być znane od dawna osobom zajmującym się stosowanym przetwarzaniem danych statystycznych i optymalizacją dowolnych systemów, niekoniecznie konkretnie DBMS.
I: tak, wiedzą, piszą ciekawe artykuły na temat swoich badań, przykład (UPD.: w komentarzach zwrócili uwagę na bardzo ciekawy projekt: dostroić )
Z drugiej strony: na pierwszy rzut oka nie widzę powszechnej wzmianki ani upowszechnienia tego podejścia w Internecie wśród specjalistów IT, DBA.

A więc do rzeczy.

Załóżmy, że mamy zadanie: skonfigurować określony system usług do obsługi pewnego rodzaju pracy.

Wiadomo o tej pracy: czym jest, jak mierzy się jakość tej pracy i jakie jest kryterium pomiaru tej jakości.

Załóżmy też, że jest mniej lub bardziej znane i rozumiane: dokładnie, w jaki sposób wykonywana jest praca w tym systemie usług (lub z nim).

„Mniej więcej” – oznacza to, że możliwe jest przygotowanie (lub zdobycie skądś) określonego narzędzia, narzędzia, usługi, które można zsyntetyzować i zastosować w systemie przy obciążeniu testowym dostatecznie adekwatnym do tego, co będzie produkowane, w warunkach dostatecznie odpowiednich do pracy na produkcji.

No cóż, załóżmy, że znany jest zestaw parametrów dostosowawczych dla tego systemu usług, które można wykorzystać do skonfigurowania tego systemu pod kątem produktywności jego pracy.

I w czym problem - nie ma wystarczająco pełnego zrozumienia tego systemu obsługi, takiego, który pozwala fachowo skonfigurować ustawienia tego systemu pod kątem przyszłego obciążenia na danej platformie i uzyskać wymaganą produktywność systemu.

Dobrze. Prawie zawsze tak jest.

Co możesz tutaj zrobić?

Cóż, pierwsze co przychodzi na myśl to zajrzenie do dokumentacji tego systemu. Zrozum, jakie są dopuszczalne zakresy wartości parametrów regulacji. I np. metodą opadania współrzędnych wybierz wartości parametrów systemu w testach.

Te. nadać systemowi jakąś konfigurację w postaci określonego zestawu wartości jego parametrów konfiguracyjnych.

Zastosuj do niego obciążenie testowe, korzystając z tego narzędzia, generatora obciążenia.
I spójrz na wartość - reakcję lub metrykę jakości systemu.

Drugą myślą może być wniosek, że to bardzo długo.

A mianowicie: jeśli parametrów ustawień jest dużo, jeśli zakresy ich wartości są uruchamiane, jeśli wykonanie każdego pojedynczego testu obciążeniowego zajmuje dużo czasu, to: tak, to wszystko może zająć niedopuszczalnie długo długi czas.

Oto, co możesz zrozumieć i zapamiętać.

Można dowiedzieć się, że w zbiorze wartości parametrów ustawień systemu serwisowego znajduje się wektor, będący ciągiem niektórych wartości.

Każdemu takiemu wektorowi, przy innych parametrach równych (w tym sensie, że wektor ten nie ma na niego wpływu), odpowiada całkowicie określona wartość metryki – wskaźnika jakości działania systemu pod obciążeniem testowym.

Ie

Oznaczmy wektor konfiguracji systemu jako Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnegoGdzie Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego; Gdzie Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego — liczba parametrów konfiguracyjnych systemu, ile jest tych parametrów.

Oraz wartość metryki odpowiadającej temu Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego oznaczmy to jako
Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego, wówczas otrzymujemy funkcję: Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego

No cóż: wszystko od razu sprowadza się do, w moim przypadku, już prawie zapomnianych z czasów studenckich, algorytmów poszukiwania ekstremum funkcji.

No dobrze, ale tutaj pojawia się pytanie organizacyjne i aplikacyjne: jakiego algorytmu użyć.

  1. W sensie - żeby móc mniej kodować ręcznie.
  2. I żeby to zadziałało, tj. znalazł ekstremum (jeśli takie istnieje), cóż, przynajmniej szybciej niż opadanie współrzędnych.

Punkt pierwszy wskazuje, że musimy przyjrzeć się środowiskom, w których takie algorytmy zostały już zaimplementowane i są w jakiejś formie gotowe do użycia w kodzie.
Dobrze wiem python и cran-r

Punkt drugi oznacza, że ​​trzeba przeczytać o samych algorytmach, czym są, jakie są ich wymagania i cechy ich działania.

A to, co dają, mogą być użytecznymi efektami ubocznymi - wynikami lub bezpośrednio z samego algorytmu.

Lub można je uzyskać z wyników algorytmu.

Wiele zależy od warunków wejściowych.

Na przykład, jeśli z jakiegoś powodu chcesz uzyskać wynik szybciej, musisz zwrócić uwagę na algorytmy opadania gradientowego i wybrać jeden z nich.

Lub, jeśli czas nie jest tak ważny, możesz na przykład zastosować stochastyczne metody optymalizacji, takie jak algorytm genetyczny.

Proponuję rozważyć pracę tego podejścia, dobierając konfigurację systemu, wykorzystując algorytm genetyczny, w kolejnej, że tak powiem: pracy laboratoryjnej.

Wstępny:

  1. Niech będzie, jako system usług: oracle xe 18c
  2. Niech służy działalności transakcyjnej i celowi: uzyskaniu jak największej przepustowości subbazy, w transakcjach/sek.
  3. Transakcje mogą mieć bardzo różny charakter pracy z danymi i kontekst pracy.
    Umówmy się, że są to transakcje, które nie przetwarzają dużej ilości danych tabelarycznych.
    W tym sensie, że nie generują więcej danych cofania niż ponawiania i nie przetwarzają dużego odsetka wierszy i dużych tabel.

Są to transakcje zmieniające jeden wiersz w mniej lub bardziej dużej tabeli, z małą liczbą indeksów na tej tabeli.

W tej sytuacji: o wydajności subbazy do przetwarzania transakcji, z zastrzeżeniem, decydować będzie jakość przetwarzania przez bazę redox.

Zastrzeżenie - jeśli mówimy konkretnie o ustawieniach subdb.

Ponieważ w ogólnym przypadku mogą wystąpić np. blokady transakcyjne pomiędzy sesjami SQL, wynikające z konstrukcji pracy użytkownika z danymi tabelarycznymi i/lub modelem tabelarycznym.

Co oczywiście będzie miało przygnębiający wpływ na metrykę TPS i będzie to czynnik egzogeniczny w stosunku do subbazy: cóż, tak został zaprojektowany model tabelaryczny i praca z danymi w nim, że występują blokady.

Dlatego dla czystości eksperymentu wykluczymy ten czynnik, a poniżej wyjaśnię dokładnie, jak.

  1. Załóżmy dla pewności, że 100% poleceń SQL przesłanych do bazy danych to polecenia DML.
    Niech charakterystyka pracy użytkownika z subbazą będzie taka sama w testach.
    Mianowicie: liczba sesji skl, dane tabelaryczne, jak sesje skl z nimi współpracują.
  2. Subd pracuje w FORCE LOGGING, ARCHIVELOG Mody. Tryb bazy danych Flashback jest wyłączony na poziomie subd.
  3. Logi powtórzeń: zlokalizowane w oddzielnym systemie plików, na osobnym „dysku”;
    Pozostała część fizycznego komponentu bazy danych: w innym, osobnym systemie plików, na osobnym „dysku”:

Więcej szczegółów na temat urządzenia fizycznego. komponenty laboratoryjnej bazy danych

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

Początkowo w tych warunkach obciążenia chciałem użyć subd transakcji Narzędzie SLOB
Ma tak cudowną cechę, że zacytuję autora:

Sercem SLOB jest „metoda SLOB”. Metoda SLOB ma na celu testowanie platform
bez rywalizacji aplikacyjnej. Nie można zapewnić maksymalnej wydajności sprzętu
przy użyciu kodu aplikacji, który jest na przykład ograniczony przez blokowanie aplikacji lub nawet
udostępnianie bloków bazy danych Oracle. Zgadza się — udostępnianie danych wiąże się z dodatkowymi kosztami
w blokach danych! Ale SLOB – w swoim domyślnym wdrożeniu – jest odporny na takie spory.

Ta deklaracja: odpowiada, to prawda.
Wygodnie jest regulować stopień równoległości sesji cl, to jest klucz -t uruchom narzędzie runit.sh z SLOBA
Regulowany jest procent poleceń DML, liczba wiadomości tekstowych wysyłanych do subd, każda sesja tekstowa, parametr UPDATE_PCT
Oddzielnie i bardzo wygodnie: SLOB sam, przed i po sesji ładowania - przygotowuje pakiet statystyk, czyli migawki awr (co ma zostać przygotowane).

Okazało się jednak, że SLOB nie obsługuje sesji SQL trwających krócej niż 30 sekund.
Dlatego najpierw zakodowałem własną, robotniczo-chłopską wersję ładowarki, a potem pozostała ona w działaniu.

Dla jasności wyjaśnię, co robi moduł ładujący i jak to robi.
Zasadniczo moduł ładujący wygląda następująco:

Kod pracownika

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

Pracownicy są uruchamiani w ten sposób:

Działający pracownicy

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

A stoły dla pracowników są przygotowane w ten sposób:

Tworzenie tabel

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"

Te. Dla każdego workera (praktycznie: osobna sesja SQL w bazie danych) tworzona jest osobna tabela, z którą pracownik pracuje.

Zapewnia to brak blokad transakcyjnych pomiędzy sesjami roboczymi.
Każdy pracownik: robi to samo, przy swoim własnym stole, wszystkie stoły są takie same.
Wszyscy pracownicy wykonują pracę przez ten sam czas.
Co więcej, na tyle długo, aby np. przełączenie logu na pewno nastąpiło i to więcej niż raz.
Cóż, w związku z tym powstały związane z tym koszty i skutki.
W moim przypadku czas pracy pracowników skonfigurowałem na 8 minut.

Fragment raportu Statspack opisującego działanie subd pod obciążeniem

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

Wracając do pracy laboratoryjnej.
Będziemy, przy innych zasadach, zmieniać wartości następujących parametrów subbazy laboratoryjnej:

  1. Rozmiar grup dzienników bazy danych. zakres wartości: [32, 1024] MB;
  2. Liczba grup czasopism w bazie danych. zakres wartości: [2,32];
  3. log_archive_max_processes zakres wartości: [1,8];
  4. commit_logging dozwolone są dwie wartości: batch|immediate;
  5. commit_wait dozwolone są dwie wartości: wait|nowait;
  6. log_buffer zakres wartości: [2,128] MB.
  7. log_checkpoint_timeout zakres wartości: [60,1200] sekund
  8. db_writer_processes zakres wartości: [1,4]
  9. undo_retention zakres wartości: [30;300] sekund
  10. transactions_per_rollback_segment zakres wartości: [1,8]
  11. disk_asynch_io dozwolone są dwie wartości: true|false;
  12. filesystemio_options dozwolone są następujące wartości: none|setall|directIO|asynch;
  13. db_block_checking dozwolone są następujące wartości: OFF|LOW|MEDIUM|FULL;
  14. db_block_checksum dozwolone są następujące wartości: OFF|TYPICAL|FULL;

Osoba mająca doświadczenie w utrzymaniu baz danych Oracle z pewnością potrafi już powiedzieć, jakie i na jakie wartości należy ustawić, z podanych parametrów i ich akceptowalnych wartości, aby uzyskać większą produktywność bazy do pracy z danymi, na co wskazuje kod aplikacji, tutaj powyżej.

Ale.

Celem pracy laboratoryjnej jest pokazanie, że sam algorytm optymalizacji stosunkowo szybko nam to wyjaśni.

Nam pozostaje tylko zajrzeć do dokumentu, poprzez konfigurowalny system, tylko na tyle, aby dowiedzieć się, jakie parametry zmienić i w jakim zakresie.
A także: zakoduj kod, który będzie używany do pracy z niestandardowym systemem wybranego algorytmu optymalizacji.

A więc teraz o kodzie.
mówiłem powyżej cran-r, tj.: wszystkie manipulacje w dostosowanym systemie są organizowane w formie skryptu R.

Rzeczywiste zadanie, analiza, wybór według wartości metryki, wektorów stanu systemu: to jest pakiet GA (dokumentacja)
Pakiet w tym przypadku nie jest zbyt odpowiedni w tym sensie, że oczekuje, że wektory (chromosomy, jeśli chodzi o pakiet) będą określone w postaci ciągów liczb z częścią ułamkową.

I mój wektor, z wartości parametrów ustawień: jest to 14 wielkości - liczb całkowitych i wartości łańcuchowych.

Problemu można oczywiście łatwo uniknąć, przypisując wartościom ciągów określone liczby.

Ostatecznie główny fragment skryptu R wygląda następująco:

Zadzwoń do 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

Tutaj, z pomocą lower и upper atrybuty podprogramu ga zasadniczo określa się obszar przestrzeni poszukiwań, w obrębie którego będzie prowadzone wyszukiwanie takiego wektora (lub wektorów), dla którego uzyskana zostanie maksymalna wartość funkcji przystosowania.

Podprogram ga przeprowadza wyszukiwanie maksymalizujące funkcję przystosowania.

Cóż, więc okazuje się, że w tym przypadku konieczne jest, aby funkcja dopasowania, rozumiejąc wektor jako zbiór wartości dla pewnych parametrów subd, otrzymała metrykę od subd.

To znaczy: ile, przy danej konfiguracji subd i danym obciążeniu subd: subd przetwarza transakcje na sekundę.

Oznacza to, że podczas rozkładania należy wykonać następujący wieloetapowy proces w ramach funkcji fitness:

  1. Przetwarzanie wektora wejściowego liczb - konwersja go na wartości parametrów subdanych.
  2. Próba utworzenia określonej liczby grup powtórzeń o zadanym rozmiarze. Co więcej, próba może zakończyć się niepowodzeniem.
    Grupy magazynów, które już istniały w subd, w pewnej ilości i pewnej wielkości, dla czystości eksperymentu - or.r. usunięte.
  3. Jeśli poprzedni punkt się powiedzie: podanie wartości parametrów konfiguracyjnych do bazy danych (znowu: może wystąpić awaria)
  4. Jeśli poprzedni krok się powiedzie: zatrzymanie subd, uruchomienie subd, aby nowo określone wartości parametrów zaczęły obowiązywać. (ponownie: może wystąpić usterka)
  5. Jeśli poprzedni krok zakończył się pomyślnie: wykonaj test obciążenia. pobierz dane z subd.
  6. Przywróć subd do jego pierwotnego stanu, tj. usuń dodatkowe grupy dzienników, przywróć pierwotną konfigurację subbazy do działania.

Kod funkcji 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)
}

To. cała praca: wykonywana w funkcji fitness.

Podprogram ga przetwarza wektory, lub, ściślej, chromosomy.
W którym najważniejsza jest dla nas selekcja chromosomów z genami, dla których funkcja przystosowania daje duże wartości.

Zasadniczo jest to proces poszukiwania optymalnego zestawu chromosomów za pomocą wektora w N-wymiarowej przestrzeni poszukiwań.

Bardzo jasne, szczegółowe wyjaśnienie, z przykładami kodu R, działanie algorytmu genetycznego.

Chciałbym osobno zwrócić uwagę na dwie kwestie techniczne.

Wywołania pomocnicze z funkcji evaluatena podstawie których wykonywane są np. stop-start, ustawianie wartości parametru subd cran-r Funkcje system2

Za pomocą którego: wywoływany jest jakiś skrypt lub polecenie bash.

Na przykład:

set_db_parametr

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)
}
}

Drugi punkt to linia, evaluate funkcje, z zapisaniem określonej wartości metryki i odpowiadającego jej wektora strojenia do pliku dziennika:

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

Jest to istotne, ponieważ z tej tablicy danych będzie można uzyskać dodatkową informację o tym, która ze składowych wektora strojenia ma większy, a który mniejszy wpływ na wartość metryki.

Czyli: możliwe będzie przeprowadzenie analizy ważności atrybutów.

Co więc może się wydarzyć?

W formie wykresu, jeśli zamówisz testy w rosnącej kolejności metrycznej, obraz będzie wyglądał następująco:

Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego

Niektóre dane odpowiadające skrajnym wartościom metryki:
Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego
Tutaj na zrzucie ekranu z wynikami wyjaśnię: wartości wektora strojenia podawane są w formie kodu funkcji przystosowania, a nie w formie liczbowej listy parametrów/zakresów wartości parametrów, która została sformułowana powyżej w tekście.

Dobrze. Czy to dużo czy mało, ~8 tys. t/s: osobne pytanie.
W ramach pracy laboratoryjnej liczba ta nie jest istotna, ważna jest dynamika, to jak ta wartość się zmienia.

Dynamika jest tutaj dobra.
Jest oczywiste, że co najmniej jeden czynnik znacząco wpływa na wartość metryki, algorytm ga, sortujący wektory chromosomowe: objęty.
Sądząc po dość energicznej dynamice wartości krzywych, istnieje jeszcze co najmniej jeden czynnik, który, choć znacznie mniejszy, ma wpływ.

Tutaj tego potrzebujesz attribute-importance analizę, aby zrozumieć, jakie atrybuty (cóż, w tym przypadku składowe wektora strojenia) i w jakim stopniu wpływają one na wartość metryki.
Na podstawie tych informacji: zrozum, na jakie czynniki wpłynęły zmiany znaczących atrybutów.

run attribute-importance możliwe na różne sposoby.

Do tych celów podoba mi się algorytm randomForest Pakiet R o tej samej nazwie (dokumentacja)
randomForest, jak rozumiem jego twórczość w ogóle, a jego podejście do oceny ważności atrybutów w szczególności, buduje pewien model zależności zmiennej odpowiedzi od atrybutów.

W naszym przypadku zmienną odpowiedzi jest metryka uzyskana z bazy danych w testach obciążeniowych: tps;
A atrybuty są składnikami wektora strojenia.

Więc tu randomForest ocenia ważność każdego atrybutu modelu za pomocą dwóch liczb: %IncMSE — jak obecność/brak tego atrybutu w modelu zmienia jakość MSE tego modelu (średni błąd kwadratowy);

Natomiast IncNodePurity to liczba, która odzwierciedla jak dobrze, na podstawie wartości tego atrybutu, można podzielić zbiór danych z obserwacjami, tak aby w jednej części znalazły się dane z jedną wartością wyjaśnianej metryki, a w drugiej z inna wartość metryki.
No cóż, to znaczy: w jakim stopniu jest to atrybut klasyfikujący (najbardziej przejrzyste, rosyjskojęzyczne wyjaśnienie widziałem na RandomForest tutaj).

Robotniczo-chłopski kod R do przetwarzania zbioru danych z wynikami testów obciążeniowych:

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

Możesz bezpośrednio ręcznie wybrać hiperparametry algorytmu i skupiając się na jakości modelu, wybrać model, który dokładniej spełnia przewidywania na zbiorze danych walidacyjnych.
Możesz napisać jakąś funkcję do tej pracy (nawiasem mówiąc, znowu używając jakiegoś algorytmu optymalizacji).

Możesz użyć pakietu R caret, nie ten punkt jest ważny.

W rezultacie w tym przypadku uzyskuje się następujący wynik oceny stopnia ważności atrybutów:

Naukowa metoda poke, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacyjnego

Dobrze. Zatem możemy rozpocząć globalną refleksję:

  1. Okazuje się, że w tych warunkach testowych najistotniejszy był parametr commit_wait
    Technicznie rzecz biorąc, określa tryb wykonania operacji io polegającej na zapisywaniu danych powtórzenia z bufora dziennika subdb do bieżącej grupy dzienników: synchroniczny lub asynchroniczny.
    Wartość nowait co skutkuje niemal pionowym, wielokrotnym wzrostem wartości metryki tps: jest to włączenie asynchronicznego trybu io do grup powtórzeń.
    Osobną kwestią jest to, czy należy to zrobić w bazie danych żywności. Tutaj ograniczę się do stwierdzenia: jest to czynnik istotny.
  2. Logiczne jest, że rozmiar bufora dziennika subd: okazuje się istotnym czynnikiem.
    Im mniejszy rozmiar bufora logu, tym mniejsza jego pojemność buforowa, tym częściej następuje jego przepełnienie i/lub brak możliwości przydzielenia w nim wolnego obszaru na porcję nowych danych redoks.
    Oznacza to: opóźnienia związane z alokacją miejsca w buforze dziennika i/lub zrzucaniem z niego danych powtórzeń do grup powtórzeń.
    Opóźnienia te oczywiście powinny i wpływają na przepustowość bazy danych dla transakcji.
  3. Parametr db_block_checksum: cóż, ogólnie rzecz biorąc jest to jasne - przetwarzanie transakcji prowadzi do tworzenia bloków darty w pamięci podręcznej bufora subbazy.
    Które, gdy sprawdzanie sum kontrolnych bloków danych jest włączone, baza danych musi przetworzyć - obliczyć te sumy kontrolne z treści bloku danych, sprawdzić je z tym, co jest napisane w nagłówku bloku danych: pasuje/nie pasuje.
    Taka praca ponownie nie może opóźnić przetwarzania danych, w związku z czym parametr i mechanizm ustawiający ten parametr okazują się istotne.
    Dlatego sprzedawca oferuje w dokumentacji tego parametru różne wartości dla niego (parametr) i zauważa, że ​​tak, będzie wpływ, ale cóż, możesz wybrać różne wartości, aż do „wyłączonego” i różne skutki.

Cóż, wniosek globalny.

Ogólnie rzecz biorąc, podejście okazuje się całkiem skuteczne.

Całkiem pozwala sobie, już na wczesnych etapach testów obciążeniowych danego systemu serwisowego, na wybranie jego (systemowej) optymalnej konfiguracji pod obciążeniem, nie zagłębiając się zbytnio w specyfikę ustawiania systemu pod obciążenie.

Nie wyklucza to jednak całkowicie – przynajmniej na poziomie zrozumienia: system musi znać „pokrętła regulacyjne” i dopuszczalne zakresy obrotu tych pokręteł.

Dzięki temu podejściu stosunkowo szybko można znaleźć optymalną konfigurację systemu.
Na podstawie wyników testów można uzyskać informacje o charakterze związku między metrykami wydajności systemu a wartościami parametrów ustawień systemu.

Co oczywiście powinno przyczynić się do powstania tego bardzo głębokiego zrozumienia systemu, jego działania, przynajmniej pod danym obciążeniem.

W praktyce jest to wymiana kosztów zrozumienia dostosowanego systemu na koszty przygotowania takiego przetestowania systemu.

Chciałbym osobno zauważyć: w tym podejściu niezwykle ważny jest stopień adekwatności testów systemu do warunków pracy, jakie będzie on miał w eksploatacji komercyjnej.

Dziękuję za uwagę i czas.

Źródło: www.habr.com

Dodaj komentarz