Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji

Cześć.

Postanowiłem podzielić się swoim odkryciem — owocem przemyśleń, prób i błędów.
Jak na to spojrzeć: to wcale nie jest odkrycie, oczywiście — wszystko to powinno być od dawna znane tym, którzy zajmują się aplikacyjną obróbką danych i optymalizacją różnych systemów, niekoniecznie baz danych.
I: tak, wiedzą, piszą ciekawe artykuły na temat swoich badań, przykład (UPD.: w komentarzach wskazano na bardzo interesujący projekt: ottertune )
Z drugiej strony: na szybko nie dostrzegam szerokiego wspomnienia ani rozpowszechnienia tego podejścia w Internecie wśród specjalistów IT i DBA.

I tak, przejdźmy do sedna.

Załóżmy, że mamy zadanie: skonfigurować pewien system usługowy do obsługi jakiejś pracy.

O tej pracy wiadomo: jaka ona jest, w jaki sposób mierzy się jakość tej pracy oraz jakie są kryteria pomiaru tej jakości.

Załóżmy również, że mniej więcej wiadomo, jak dokładnie wykonywana jest praca w (lub z) tym systemem usługowym.

"Mniej więcej" — oznacza to, że istnieje możliwość przygotowania (lub skądś wzięcia) pewnego narzędzia, które można by wykorzystać do wygenerowania i dostarczenia na system testowego obciążenia, które jest w miarę adekwatne do tego, co będzie w produkcji, w warunkach dostatecznie realistycznych dla pracy w produkcji.

I, powiedzmy, że znany jest zestaw parametrów regulacyjnych tego systemu usługowego, które mogą być używane do dostosowywania tego systemu pod kątem jego wydajności.

I, w czym tkwi problem — brakuje wystarczającego zrozumienia tego systemu usługowego, takiego, które pozwala na eksperckie ustawienie tej konfiguracji pod przyszłe obciążenie na danej platformie, aby uzyskać wymaganą wydajność systemu.

Cóż, tak to już zazwyczaj bywa.

Co można z tym zrobić.

Pierwsze, co przychodzi mi do głowy: zajrzeć do dokumentacji tego systemu. Zrozumieć, jakie są dopuszczalne zakresy wartości parametrów regulacyjnych. I na przykład, metodą spadku współrzędnych, dobierać wartości dla parametrów systemu w testach.

Tzn. ustawić system w pewnej konfiguracji jako konkretny zestaw wartości jego parametrów regulacyjnych.

Poddać go testowemu obciążeniu, tą właśnie narzędziem-utility, generatorem obciążenia.
I obserwować wartość — odpowiedź, lub metrykę jakości pracy systemu.

Drugą myślą może być takie zakończenie, że — to naprawdę bardzo czasochłonne.

To znaczy: jeśli jest wiele parametrów konfiguracyjnych, jeśli zakresy ich wartości są duże, jeśli każdy pojedynczy test obciążeniowy trwa długo, to: tak, to wszystko może zająć nieakceptowalnie dużo czasu.

No i co można z tego zrozumieć i przypomnieć sobie.

Można ustalić, że w zbiorze wartości parametrów konfiguracyjnych systemu usługowego - wektor, jako sekwencja pewnych wartości.

Każdemu takiemu wektorowi, przy równych innych warunkach (w tym, że ten wektor nie jest dotknięty) odpowiada dość określona wartość metryki - wskaźnika jakości pracy systemu pod testowym obciążeniem.

To znaczy.

Oznaczmy wektor konfiguracji systemu jako Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji, gdzie Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji; gdzie Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji — liczba parametrów konfiguracji systemu, ile ich jest, tych parametrów.

A wartość metryki, odpowiadająca temu Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji oznaczmy jako
Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji, więc mamy funkcję: Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji

No a zatem: wszystko sprowadza się do, w moim przypadku: prawie zapomnianych z czasów studenckich algorytmów znajdowania ekstremum funkcji.

Dobrze, ale pojawia się tu pytanie organizacyjno-aplikacyjne: który algorytm użyć.

  1. W sensie - aby samemu jak najmniej pisać kod.
  2. I żeby działało, tzn. znajdowało ekstremum (jeśli istnieje), przynajmniej - szybciej niż spadek współrzędnych.

Pierwszy moment wskazuje, że warto spojrzeć w stronę jakichś środowisk, w których takie algorytmy już są wdrożone i są, w jakiejś formie, gotowe do użycia w kodzie.
No, znane mi są python i cran-r

Drugi moment oznacza, że trzeba przeczytać o samych algorytmach, jakie istnieją, jakie mają wymagania, szczególne cechy w pracy.

I co one dają, mogą być użyteczne efekty uboczne-wyniki, albo bezpośrednio, od samego algorytmu.

Lub można je uzyskać na podstawie wyników działania algorytmu.

Tu wiele zależy od warunków początkowych.

Na przykład, jeśli z jakichś powodów, trzeba szybciej uzyskać wynik, to warto spojrzeć w stronę algorytmów spadku gradientu, wybrać któryś z nich.

Albo, jeśli czas nie jest tak ważny, można na przykład skorzystać z metod optymalizacji stochastycznej, na przykład algorytmu genetycznego.

Proponuję rozważyć działanie takiego podejścia, przy doborze konfiguracji systemu, z wykorzystaniem algorytmu genetycznego, na następnej, że tak powiem: pracy laboratoryjnej.

Źródła:

  1. Niech będzie, jako system serwisowy: oracle xe 18c
  2. Niech on — obsługuje aktywność transakcyjną z celem: uzyskać jak największą przepustowość bazy danych, na transakcjach/sek.
  3. Transakcje — są bardzo różne, w swoim charakterze pracy z danymi i kontekście pracy.
    Umówmy się, że to transakcje, które nie przetwarzają dużej ilości danych tabelarycznych.
    W tym sensie, że nie generują danych undo więcej niż redo i nie przetwarzają dużych procentów wierszy z dużych tabel.

To transakcje, które zmieniają jeden wiersz w mniej więcej dużej tabeli, z niewielką ilością indeksów nad tą tabelą.

W takim układzie: wydajność bazy danych przy przetwarzaniu transakcji będzie, z zastrzeżeniem, określana jakością przetwarzania bazy danych redo.

Zastrzeżenie — jeśli mówimy konkretnie o ustawieniach bazy danych.

Bo, w ogólnym przypadku, mogą występować, na przykład, blokady transakcyjne, między sesjami SQL, z powodu projektowania użytkowników pracy z danymi tabelarycznymi i/lub modelu tabelarycznego.

Które, oczywiście, będą negatywnie wpływać na metrykę tps i będzie to egzogeniczny, w odniesieniu do bazy danych, czynnik: tak zaprojektowano model tabelaryczny i pracę z danymi w nim, że pojawiają się blokady.

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

  1. Załóżmy, dla pewności, że 100% przesyłanych do bazy danych komend SQL: to komendy dml.
    Niech charakterystyki pracy użytkowników z bazą danych: będą takie same w testach.
    A dokładnie: liczba sesji SQL, dane tabelaryczne, sposób w jaki sesje SQL z nimi pracują.
  2. Baza danych działa w FORCE LOGGING, ARCHIVELOG trybach. Tryb flashback bazy danych jest wyłączony, na poziomie bazy danych.
  3. Dzienniki redo: umieszczone w osobnym systemie plików, na osobnym "dysku";
    Cała reszta fizycznych komponentów bazy danych: w innym, osobnym systemie plików, na osobnym "dysku":

Szczegóły dotyczące budowy fizycznych komponentów 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 chciałem użyć systemu baz danych do obciążeń transakcyjnych w tych warunkach. SLOB-uzyskanie
Ma taką wspaniałą cechę, zacytuję autora:

W centrum SLOB znajduje się "metoda SLOB". Metoda SLOB ma na celu testowanie platform
bez rywalizacji aplikacji. Nie można osiągnąć maksymalnej wydajności sprzętu
używając kodu aplikacji, który na przykład jest ograniczony przez blokowanie aplikacji lub nawet
dzielenie bloków bazy danych Oracle. Dokładnie tak — przy dzieleniu danych
w blokach danych występuje nadmiar! Ale SLOB — w swoim domyślnym wdrożeniu — jest odporny na tego typu rywalizację.

Ta deklaracja: jest zgodna, rzeczywiście tak jest.
Wygodnie regulować poziom równoległości sesji SLOB, to klucz -t uruchomienia narzędzia runit.sh w składzie SLOB-a
Regulowany jest procent poleceń DML, w tym liczba sesji SLOB, które wysyła do systemu baz danych, każda sesja SLOB, parametr UPDATE_PCT
Oddzielnie i bardzo wygodne: SLOB sam, przed i po sesji obciążenia — przygotowuje statspak lub zrzuty awr (co ma być przygotowane).

Jednak okazało się, że SLOB nie wspiera pracy sesji SLOB dłużej niż 30 sekund.
Dlatego najpierw napisałem swoją, prosto-robotniczą wersję narzędzia obciążającego, a potem tak już pozostała w użyciu.

Wyjaśnię, co robi narzędzie obciążające, dla jasności.
Zasadniczo narzędzie obciążające wygląda tak:

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

Wykonujemy pracowników w ten sposób:

Uruchamianie pracowników

echo "rozpoczęcie testu, czas trwania: ${TEST_DURATION}" >> "$v_logfile"
for((i=1;i> "$v_logfile"
 dotx "$i" "${TEST_DURATION}" &
done
echo "oczekiwanie..." >> "$v_logfile"
wait

A tabele dla pracowników przygotowuje się tak:

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> "$v_logfile"

Tzn. dla każdego pracownika (praktycznie: osobna sesja SQL w DB) tworzona jest osobna tabela, z którą pracuje pracownik.

Osiąga się to brakiem blokad transakcyjnych między sesjami SQL pracowników.
Każdy pracownik: wykonuje to samo na swojej tabeli, wszystkie tabele są identyczne.
Wszyscy pracownicy wykonują pracę przez ten sam czas.
Przy czym - wystarczająco długi czas, aby na przykład dokładnie wystąpił i nie raz przełączanie logów.
No i odpowiednio pojawiały się z tym związane koszty i efekty.
W moim przypadku - czas trwania pracy pracowników skonfigurowałem na 8 minut.

Kawałek raportu statspak, z opisem pracy DB pod obciążeniem

Baza danych    Id DB    Instancja     Nr inst   Czas uruchomienia   Wydanie     RAC
~~~~~~~~ ----------- ------------ -------- --------------- ----------- ---
          2929910313 XE                  1 07-Wrz-20 23:12 18.0.0.0.0  NIE

Nazwa hosta             Platforma                Procesory Rdzeni Gniazda   Pamięć (G)
~~~~ ---------------- ---------------------- ----- ----- ------- ------------
     billing.izhevsk1 Linux x86 64-bit           2     2       1         15.6

Zrzut       Id zrzutu     Czas zrzutu      Sesje Curs/Sess Komentarz
~~~~~~~~    ---------- ------------------ -------- --------- ------------------
Początek zrzutu:       1630 07-Wrz-20 23:12:27       55        .7
  Koniec zrzutu:       1631 07-Wrz-20 23:20:29       62        .6
   Czas trwania:       8.03 (min) Średnia aktywna sesja:       8.4
   Czas DB:      67.31 (min)      Czas CPU DB:      15.01 (min)

Rozmiary pamięci podręcznej            Początek        Koniec
~~~~~~~~~~~       ---------- ----------
    Pamięć podręczna:     1,392M              Rozmiar bloku Std:         8K
     Współdzielona pula:       288M                  Pamięć bufora logów:   103,424K

Profil obciążenia              Na sekundę    Na transakcję    Na wykonanie    Na wywołanie
~~~~~~~~~~~~      ------------------  ----------------- ----------- -----------
      Czas DB(s):                8.4                0.0        0.00        0.20
       Czas CPU DB(s):                1.9                0.0        0.00        0.04
       Rozmiar redo:        7,685,765.6              978.4
   Logiczne odczyty:           60,447.0                7.7
   Zmiany bloków:           47,167.3                6.0
  Fizyczne odczyty:                8.3                0.0
 Fizyczne zapisy:              253.4                0.0
      Wywołania użytkownika:               42.6                0.0
          Parsowania:               23.2                0.0
     Trudne parsowania:                1.2                0.0
W/A MB przetworzono:                1.0                0.0
          Logowania:                0.5                0.0
        Wykonania:           15,756.5                2.0
       Wycofania:                0.0                0.0
    Transakcje:            7,855.1

Wracając do postawienia pracy laboratoryjnej.
Będziemy, przy tych samych warunkach, zmieniać wartości takich parametrów laboratorium bazy danych:

  1. Rozmiar grup dzienników bazy danych. Zakres wartości: [32, 1024] MB;
  2. Liczba grup dzienników bazy danych. Zakres wartości: [2,32];
  3. log_archive_max_processes zakres wartości: [1,8];
  4. commit_logging dopuszczalne są dwie wartości: batch|immediate;
  5. commit_wait dopuszczalne 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 dopuszczalne są dwie wartości: true|false;
  12. filesystemio_options dopuszczalne są takie wartości: none|setall|directIO|asynch;
  13. db_block_checking dopuszczalne są takie wartości: OFF|LOW|MEDIUM|FULL;
  14. db_block_checksum dopuszczalne są takie wartości: OFF|TYPICAL|FULL;

Człowiek z doświadczeniem w zarządzaniu bazami danych Oracle może bez wątpliwości powiedzieć — jakie wartości powinny być ustawione wśród podanych parametrów i ich dopuszczalnych wartości, aby uzyskać większą wydajność bazy danych do pracy z danymi zdefiniowanymi w aplikacyjnym kodzie powyżej.

Ale.

Celem laboratorium jest pokazanie, że algorytm optymalizacyjny działa samodzielnie i stosunkowo szybko.

Musimy jedynie zajrzeć do dokumentacji dotyczącej konfigurowanego systemu, aby ustalić, które parametry i w jakich zakresach zmieniać.
Należy również napisać kod, który będzie realizował współpracę z konfigurowanym systemem wybranego algorytmu optymalizacji.

Zatem, przechodząc do kodu.
Wcześniej mówiłem o cran-r, tj.: wszystkie operacje na konfigurowanym systemie są orkiestrane w postaci skryptu R.

Właściwe zadanie, analiza, dobór według wartości metryki oraz wektorów stanu systemu: to pakiet GA (文档)
Pakiet, w tym przypadku, niezbyt pasuje, ponieważ oczekuje on zadania wektorów (chromosomów, jeśli stosować terminologię pakietu) w postaci liczb rzeczywistych.

A mój wektor z wartości parametrów konfiguracyjnych to 14 wartości — liczby całkowite i wartości tekstowe.

Problem jest łatwy do obejścia, przypisując wartości tekstowe do określonych liczb.

Zatem, ostatecznie, główna część skryptu R wygląda tak:

Wywołanie 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, przy użyciu lower i upper atrybutów podprogramu ga określa się praktycznie obszar przestrzeni poszukiwań, w którym będzie poszukiwany taki wektor (lub wektory), dla którego uzyskane będzie maksymalne wartość funkcji dopasowania.

Podprogram ga wykonuje poszukiwanie, maksymalizując funkcję dopasowania.

Zatem, w tym przypadku, należy dopilnować, aby funkcja dopasowania, interpretując wektor jako zestaw wartości dla określonych parametrów bazy danych, wygenerowała metrykę z bazy danych.

Tj.: ile, przy danej konfiguracji bazy danych i danym obciążeniu bazy danych, baza przetwarza transakcji na sekundę.

Innymi słowy, należy zadbać, aby wewnątrz funkcji dopasowania realizowana była taka wieloetapowa operacja:

  1. Przetwarzanie wejściowego wektora liczb — przekształcenie go w wartości dla parametrów bazy danych.
  2. Próba utworzenia określonej liczby grup redo o określonym rozmiarze. Przy czym próba może zakończyć się niepowodzeniem.
    Już istniejące w subd grupy dziennikowe, w jakiejś ilości i jakiegoś rozmiaru, dla czystości eksperymentu — powinny zostać usunięte.
  3. W przypadku sukcesu poprzedniego punktu: zadanie bazie wartości parametrów konfiguracyjnych (wciąż możliwe są błędy)
  4. W przypadku sukcesu poprzedniego punktu: zatrzymanie subd, uruchomienie subd, aby ponownie zadane wartości parametrów — weszły w życie. (wciąż możliwe są błędy)
  5. W przypadku sukcesu poprzedniego punktu: przeprowadzenie testu obciążeniowego. uzyskanie metryki od subd.
  6. Przywrócenie subd do stanu początkowego, tzn. usunięcie dodatkowych grup dziennikowych, przywrócenie pierwotnej konfiguracji subd.

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 && round(p_par[12],digit=0)  30 ) {
 opn$filesystemio_options="asynch"
}

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

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

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

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

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

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

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

rc=run_test()
v_metric=getmetric()

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

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

Tak więc, cała praca: odbywa się w funkcji dopasowania.

Podprogram ga, wykonuje przetwarzanie wektorów, lub, dokładniej mówiąc — chromosomów.
W której najbardziej interesuje nas: selekcja chromosomów z takimi genami, przy których funkcja dopasowania wydaje dużą wartość.

To tak naprawdę proces poszukiwania optymalnego zestawu chromosomów wektorem w N-wymiarowej przestrzeni poszukiwania.

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

Osobno zaznaczę dwa techniczne punkty.

Pomocnicze wywołania, z funkcji evaluate, na przykład zatrzymanie-uruchomienie, ustawienie wartości parametru bazy danych, są realizowane na podstawie cran-r funkcji system2

Dzięki której: wywoływana jest już jakakolwiek skrypt bash lub polecenie.

Na przykład:

set_db_parameter

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

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

Drugi punkt — linia, evaluate funkcji, z zachowaniem konkretnej wartości metryki oraz odpowiadającego jej wektora ustawień, do logu:

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

To ważne, ponieważ z tej tablicy danych można uzyskać dodatkowe informacje na temat tego, który z komponentów wektora ustawień ma większy lub mniejszy wpływ na wartość metryki.

Czyli: można przeprowadzić analizę istotności atrybutów.

Zatem, co może się zdarzyć.

W postaci wykresu, jeśli uporządkujesz testy według rosnącej wartości metryki, obraz będzie taki:

Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji

Niektóre dane, odpowiadające skrajnym wartościom metryki:
Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji
Tutaj, na zrzucie ekranu z wynikami, doprecyzuję: wartości wektora ustawień — podane są w terminach kodu funkcji dopasowania, a nie w terminach listy parametrów/zasięgów wartości parametrów, którą przedstawiłem powyżej w tekście.

Cóż. Dużo to, czy mało, ~8 tys. tps: osobny temat.
W ramach pracy laboratoryjnej — ta liczba nie ma znaczenia, ważna jest dynamika, jak ta wartość się zmienia.

Dynamika tutaj jest dobra.
Jasne, że co najmniej jeden czynnik istotnie wpływa na wartość metryki, algorytm ga, przeszukując wektory-chromosomy: został pokryty.
Sądząc po dość dynamicznej wartości krzywej — istnieje jeszcze co najmniej jeden czynnik, który, choć znacznie mniejszy, wpływa.

Tutaj potrzebna jest attribute-importance analiza, aby zrozumieć: jakie atrybuty (w tym przypadku — komponenty wektora konfiguracyjnego) i jak mocno wpływają na wartość metryki.
A na podstawie tych informacji: zrozumieć — jakie czynniki były poruszane przez zmiany znaczących atrybutów.

Wykonaj attribute-importance można to zrobić na różne sposoby.

Dla tych celów, podoba mi się algorytm randomForest tej samej nazwy pakietu R (文档)
randomForest, jak rozumiem jego działanie w ogóle oraz 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 uzyskiwana z systemów baz danych w testach obciążeniowych: tps;
A atrybuty to — komponenty wektora konfiguracyjnego.

Otóż to randomForest ocenia ważność każdego atrybutu modelu dwoma liczbami: %IncMSE — jak obecność/nieobecność tego atrybutu w modelu zmienia jakość modelu MSE (średni błąd kwadratowy);

A IncNodePurity — to liczba, która pokazuje, jak dobrze, według wartości danego atrybutu, można podzielić zbiór danych z obserwacjami, tak aby w jednej części znalazły się dane z jednym wartością wyjaśnianej metryki, a w drugiej z inną wartością metryki.
Czyli: na ile jest to atrybut klasyfikujący (najlepsze wyjaśnienie w języku rosyjskim, które widziałem dotyczące random forest). tutaj).

Roboczy 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żna ręcznie dobrać hiperparametry algorytmu i, kierując się jakością modelu, wybrać model, który dokładniej wykonuje prognozy na zbiorze danych walidacyjnych.
Można napisać jakąś funkcję do tej pracy (przy okazji — znów, na jakimś algorytmie optymalizacyjnym).

Można skorzystać z pakietu R caret, nie ma to większego znaczenia.

W rezultacie, w tym przypadku, otrzymujemy taki wynik, który pozwala ocenić znaczenie atrybutów:

Metoda prób i błędów, czyli jak dobrać konfigurację bazy danych za pomocą benchmarków i algorytmu optymalizacji

Cóż, w ten sposób można przejść do globalnych rozważań:

  1. Okazuje się, że najbardziej znaczącym parametrem w tych warunkach testowania był commit_wait
    Technicznie określa on tryb wykonywania operacji I/O zapisu danych redo z log-buforu bazy danych do aktualnej grupy journal: synchroniczny lub asynchroniczny.
    Wartość nowait w którym osiągany jest praktycznie pionowy, wielokrotny wzrost wartości metryki TPS: to włączenie trybu asynchronicznego I/O w grupach redo.
    Osobne pytanie — warto, czy nie warto tak robić w produkcyjnej bazie danych. W tej kwestii ograniczam się jedynie do stwierdzenia: to znaczący czynnik.
  2. Logiczne jest, że rozmiar log-buforu bazy danych: okazuje się znaczącym czynnikiem.
    Im mniejszy rozmiar log-buforu, tym mniejsza jego zdolność buforowania, tym częściej zdarzają się jego przepełnienia i/lub brak możliwości przydzielenia w nim wolnej przestrzeni na porcję nowych danych redo.
    A to oznacza: opóźnienia związane z alokowaniem przestrzeni w log-buforze i/lub zrzucaniem danych redo z niego do grup redo.
    Te opóźnienia oczywiście powinny wpływać i wpływają na przepustowość bazy danych w kwestii transakcji.
  3. Parametr db_block_checksum: no cóż, to również w zasadzie jasne — przetwarzanie transakcji prowadzi do powstawania bloków dirty w buforze podrzędnym bazy danych.
    Które, przy aktywowanej kontroli sum kontrolnych bloków danych, baza musi przetwarzać — obliczając te sumy kontrolne dla treści bloku danych, porównując je z tym, co jest zapisane w nagłówku bloku danych: zgadza się/nie zgadza się.
    Taka praca, znów, nie może nie opóźnić przetwarzania danych, a więc parametry i mechanizm, który ten parametr definiuje — okazują się istotne.
    Dlatego też dostawca w dokumentacji tego parametru proponuje różne jego wartości i zauważa, że – tak, wpływ będzie, ale, oto, różne wartości, aż do 'wyłączone' i różny wpływ, można wybrać.

No i ogólny wniosek.

Podejście, generalnie rzecz biorąc, okazuje się całkiem skuteczne.

Pozwala ono, na wczesnych etapach testowania obciążeniowego pewnego systemu serwisowego, na dobór jego (systemu) optymalnej konfiguracji pod obciążenie bez wnikania zbytnio w szczegóły dostosowania systemu do obciążenia.

Ale nie wyklucza tego całkowicie — przynajmniej na poziomie rozumienia: "regulacyjnych pokręteł" i dopuszczalnych zakresów obrotu tych pokręteł system znać trzeba.

Podejście to może dość szybko znaleźć optymalną konfigurację systemu.
I w wyniku testowania można uzyskać informacje o naturze zależności między metryką jakości działania systemu a wartościami parametrów konfiguracyjnych systemu.

Co oczywiście powinno sprzyjać powstawaniu głębokiego zrozumienia systemu, jego działania, przynajmniej — w ramach określonego obciążenia.

Praktycznie oznacza to: rozłożenie kosztów na zrozumienie konfigurowanego systemu oraz wydatków związanych z przygotowaniem takiego testowania działania systemu.

Osobno zauważę: w tym podejściu kluczowa jest adekwatność testowania systemu do warunków jego pracy, które będą miały miejsce w eksploatacji produkcyjnej.

Dziękuję za uwagę, czas.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster