Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму

Здравствуйте.

Вирішив поділитися своєю знахідкою — плодом роздумів, спроб і помилок.
За великим рахунком: це ніяка не знахідка, звичайно ж, все це має бути давно відомо, тим, хто займається прикладною стат-обробкою даних та оптимізацією будь-яких систем, не обов'язково саме СУБД.
І: нехай знають, пишуть цікаві статті за своїми ресерчами, приклад (UPD.: у коментарях вказали на дуже цікавий проект: ottertune )
З іншого боку: навскідку не вбачаю широкої згадки, поширення такого підходу, в інтернеті, серед ІТ-фахівців, ДБА.

Отже, насправді.

Припустимо, що ось у нас завдання: налаштувати якусь сервісну систему, на обслуговування якоїсь роботи.

Про цю роботу відомо: яка вона, в чому вимірюється якість цієї роботи і який критерій показник вимірів цієї якості.

Також припустимо що, більш-менш відомо-зрозуміло: як саме виконується робота (або з) цією сервісною системою.

"Більше-менш" - це означає що є можливість підготувати (або десь взяти) якийсь тулз, утиліту, сервіс яким можна синтезувати і подати на систему тестове навантаження досить адекватне до того, що буде в проді, в досить адекватних до роботи в проде умовах .

Та й припустимо, що відомий набір регулювальних параметрів цієї сервісної системи, якими можна налаштовувати цю систему, у сенсі продуктивності її роботи.

І, в чому проблема — немає достатньо повного розуміння цієї сервісної системи, що дозволяє експертно виставити налаштування цієї системи, під майбутнє навантаження, на даній платформі і отримати потрібно продуктивність роботи системи.

Ну. Так, майже завжди буває.

Що тут можна зробити?

Ну перше, що спадає на думку: зазирнути в доку за цією системою. Зрозуміти, які там допустимі діапазони на значення регулювальних параметрів. І, наприклад, методом координатного спуску, підбирати значення для параметрів системи у тестах.

Тобто. ставити системі якусь конфігурацію, як конкретного набору значень її настроювальних пар-ров.

Подавати на неї тестове навантаження, ось цією самою тулзою-утилітою, лоад-генератором.
І дивитися величину - відгук, ну чи метрику якості роботи системи.

Другою думкою може бути висновок такий, що — це дуже довго.

Ну тобто: якщо настроювальних параметрів - багато, якщо діапазони їх значень, що пробігаються, великі, якщо кожен окремий навантажувальний тест - виконується багато часу, то: так, це все може зайняти не прийнятно багато часу.

Ну і тут, що можна зрозуміти і згадати.

Можна дізнатися, в наборі значень настроювальних параметрів сервісної системи вектор, як послідовність якихось значень.

Кожному такому вектору, за інших рівних (у тому, що цим вектором не торкається) відповідає цілком певне значення метрики — показника якості роботи системи, під тестовим навантаженням.

Тобто

Ось позначимо вектор конфігурації системи як Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму, Де Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму; Де Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму - Кількість параметрів конфігурації системи, скільки їх, цих параметрів.

А значення метрики, що відповідає даному Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму позначимо як
Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму, то, у нас виходить функція: Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму

Ну і тоді: все негайно зводиться до, в моєму випадку: майже забутих зі студентської лави, алгоритмів пошуку екстремуму функції.

Добре, але тут виникає організаційно-прикладне питання: а який саме алгоритм використовувати.

  1. У сенсі — щоб найменше руками кодити.
  2. І щоб працювало, тобто. знаходило екстремум (якщо він є), ну, принаймні, швидше за координатний спуск.

Перший момент натякає що треба подивитися в бік якихось середовищ, у яких такі алгоритми вже реалізовані, і є, в якомусь вигляді, готовому до використання в коді.
Ну, мені відомі python и cran-r

Другий момент означає всі почитати про власне алгоритми, які вони є, які вони вимоги, особливості у роботі.

І що вони дають, можуть бути корисні побічні ефекти-результати, або безпосередньо від самого алгоритму.

Або їх можна одержати на підсумках роботи алгоритму.

Тут багато що залежить від вхідних умов.

Наприклад якщо, з якихось причин, потрібно швидше отримати результат, ну, потрібно дивитися у бік алгоритмів градієнтного спуску, вибирати якийсь із них.

Або, якщо не так важливий час, можна, наприклад, користуватися методами стохастичної оптимізації, наприклад генетичним алгоритмом.

Пропоную розглянути роботу такого підходу, щодо підбору конфігурації системи, з використанням генетичного алгоритму, на наступній, так би мовити: лабораторній роботі.

Вихідні:

  1. Нехай є як сервісна система: oracle xe 18c
  2. Нехай вона обслуговує транзакційну активність і ціль: отримати можливо велику пропускну здатність субд, по транзакціях/сек.
  3. Транзакції - бувають дуже різні, за своїм характером роботи з даними та контекстом роботи.
    Умовимося так що це транзакції які не обробляють велику кількість табличних даних.
    У тому сенсі, що не генерують ундо-даних більше ніж редо і не обробляють великі відсотки рядків, великих таблиць.

Це транзакції, які змінюють один рядок у більш-менш великій таблиці, з невеликою кількістю індексів над цією таблицею.

У такому розкладі: продуктивність субд з обробки транзакцій, з застереженням, визначатиметься якістю обробки базою даних.

Застереження - якщо говорити саме про налаштування субд.

Тому що, в загальному випадку, можуть бути, наприклад, транзакційні блокування, між скл-сесіями, через дизайн користувальницької роботи з табличними даними та/або табличної моделі.

Які, звичайно ж, будуть гнітюче позначатися на tps-метриці і це буде екзогенний, щодо субд, фактор: ну ось так задизайнили табличну модель і роботу з даними в ній, що виникають блокування.

Тому для чистоти експерименту виключимо цей фактор нижче уточню як саме.

  1. Припустимо, для певності, що 100% поданих у субд sql-команд: це dml-команди.
    Нехай характеристики роботи з субд: одні й ті ж, у тестах.
    А саме: у скл-сесій, табличні дані, то як з ними працюють скл-сесії.
  2. Субд працює в FORCE LOGGING, ARCHIVELOG моди. Флешбак-датабейс режим вимкнено, на рівні субд.
  3. Редологи: розташовані в окремій файловій системі, на окремому "диску";
    Вся решта фізичної компоненти бд: в іншій, окремій фс, на окремому "диску":

Докладніше, для визначення фіз. компоненти лабораторної бд

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

Спочатку під ці умови навантаження суб транзакціями хотів використовувати SLOB-утиту
Вона має таку чудову особливість, процитую автора:

At the heart of SLOB є “SLOB method.” The SLOB Method aims to test platforms
безвикористання вмісту. One cannot drive maximum hardware performance
using application code that is, for example, bound by application locking or even
sharing Oracle Database blocks. That's right—there is overhead when sharing data
in data blocks! Але SLOB-in its default deployment-is immune to such contention.

Ця декларація відповідає, так і є.
Зручно регулювати рівень паралелізму скл-сесій, це ключ -t запуску утиліти runit.sh зі складу SLOB-а
Регулюється відсоток дмл-команд, у тому числі скл-ей які відправляє в субд, кожна скл-сесія, параметр UPDATE_PCT
Окремо та дуже зручно: SLOB сам, перед і після сесії навантаження - готує статспак, або awr-снапшоти (що задано готувати).

Однак з'ясувалося що SLOB не підтримує роботу скл-сесій із тривалістю менше 30 секунд.
Тому спочатку накодував свій, робітничо-селянський варіант навантажувача, а потім він так і залишився в роботі.

Уточню за навантажувачем — що і як він робить, для ясності.
Фактично навантажувач виглядає так:

Код воркера

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

Запускаються воркери таким чином:

Запуск воркерів

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

А таблиці для воркерів готуються так:

Створення таблиць

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"

Тобто. під кожен воркер (практично: окрема скл-сесія в субд) створюється окрема таблиця, з якою працює воркер.

Цим досягається відсутність транзакційних блокувань між скл-сесіями воркерів.
Кожен воркер: робить те саме, зі своєю таблицею, таблиці однакові.
Воркери все - виконують роботу на протязі однієї й тієї ж кількості часу.
Причому досить довго, щоб, наприклад, точно стався, і не один раз лог-свічинг.
Ну і відповідно виникали, із цим пов'язані, витрати та ефекти.
У моєму випадку – тривалість роботи воркерів накофігурував у 8 хвилин.

Шматок статспак-звіту, з описом роботи субд під навантаженням

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

Повертаючись до постановки лабораторної роботи.
Будемо, за інших рівних, варіювати значення таких параметрів лабораторної субд:

  1. Розмір журнальних груп бд. діапазон значень: [32, 1024] Мбайт;
  2. Кількість журнальних груп бд. діапазон значень: [2,32];
  3. log_archive_max_processes діапазон значень: [1,8];
  4. commit_logging допускається два значення: batch|immediate;
  5. commit_wait допускається два значення: wait|nowait;
  6. log_buffer Діапазаон значень: [2,128] Мбайт.
  7. log_checkpoint_timeout діапазаон значень: [60,1200] секунд
  8. db_writer_processes діапазаон значень: [1,4]
  9. undo_retention Діапазаон значень: [30; 300] секунд
  10. transactions_per_rollback_segment діапазаон значень: [1,8]
  11. disk_asynch_io допускається два значення: true|false;
  12. filesystemio_options допускаються такі значення: none|setall|directIO|asynch;
  13. db_block_checking допускаються такі значення: OFF|LOW|MEDIUM|FULL;
  14. db_block_checksum допускаються такі значення: OFF|TYPICAL|FULL;

Людина, з досвідом супроводу oracle-баз даних, безумовно, вже зараз може сказати - що і в які значення потрібно виставляти, із зазначених параметрів та їх допустимих значень, щоб отримати більшу продуктивність субд, під ту роботу з даними, яка позначена прикладним кодом , Тут, вище.

Але.

Сенс лабораторної роботи показати що оптимізаційний алгоритм сам і відносно швидко це нам уточнить.

Нам же: залишається тільки заглянути в доку, по системі, що налаштовується, рівно настільки, наскільки потрібно щоб з'ясувати: які параметри і в яких діапазонах змінювати.
А також: накодувати код, яким буде реалізована робота з системою обраного алгоритму оптимізації, що настроюється.

Т.ч., тепер про код.
Вище говорив про cran-r, тобто: всі маніпуляції, з системою, що настроюється - оркеструються у вигляді R-скрипту.

Власне завдання, аналіз, підбір за значенням метрики, векторів стану системи: це пакет GA (документація)
Пакет, в даному випадку, не дуже підходить, тому що він чекає завдання векторів (хромосом, якщо в термінах пакета) у вигляді пост-чисел з дробовою частиною.

А мій вектор, із значень настроювальних параметрів: це 14-ть величин — цілі числа та рядкові значення.

Проблема, звичайно, легко обходиться, призначенням рядкових величин — якихось певних чисел.

Т.ч., в результаті, основний шматок R-скрипту виглядає так:

Виклик 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

Тут, за допомогою lower и upper атрибутів підпрограми ga задається, по суті, область пошукового простору, всередині якого буде виконуватись пошук такого вектора (або векторів) для яких буде отримано максимальне значення фітнес-функції.

ga-підпограма виконує пошук максимізуючи фітнес-функцію.

Ну, т.ч., виходить що, в даному випадку, треба щоб фітнес-функція, розуміючи вектор як набір значень для певних параметрів субд, отримувала метрику від субд.

Тобто: скільки при даному налаштуванні субд і даному навантаженні на субд: субд обробляє транзакцій в секунду.

Тобто, розгортаючи, потрібно щоб усередині фітнес-функції виконувалася така багатоходівка:

  1. Обробка вхідного вектора чисел — перетворення його значення для параметрів субд.
  2. Спроба створення заданої кількості редо-груп, заданого розміру. Причому спроба може бути невдалою.
    Журнальні групи, що вже існували в субд, в якому то кол-ві і якогось розміру, для чистоти експерименту - д.б. видалено.
  3. При успіху попереднього пункту: завдання базі значень конфігураційних параметрів (знову ж таки: може бути збій)
  4. При успіху попереднього пункту: зупинка субд, запуск субд для того, щоб знову задані значення параметрів - набули чинності. (Знову ж таки: може бути збій)
  5. За успіху попереднього пункту: виконати навантажувальний тест. отримати метрику від субд.
  6. Повернути субд до вихідного стану, тобто. видалити додаткові журнальні групи, повернути роботу вихідну конфігурацію субд.

Код фітнес-функції

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

Т.о. вся робота: виконується у фітнес-функції.

ga-підпрограма виконує обробку векторів, ну або, правильніше говорити — хромосом.
В якій нам найбільше важлива: селекція хромосом з такими генами, при яких фітнес-функція видає великі значення.

Це, по суті, і є процес пошуку оптимального набору вектором хромосом, в N-мірному просторі пошуку.

Дуже виразне, докладне пояснення, з прикладами R-коду роботи генетичного алгоритму.

Окремо відзначу два технічні моменти.

Допоміжні виклики з ф-ції evaluateнаприклад зупинка-запуск, завдання значення параметра субд, виконуються на основі cran-r ф-ції system2

За допомогою якої: викликається вже якийсь баш-скрипт, або команда.

Наприклад:

set_db_parameter

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

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

Другий момент - рядок, evaluate ф-ції, із збереженням конкретного значення метрики та їй відповідного настроювального вектора, в лог-файл:

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

Це важливо, тому що з цього масиву даних можна буде отримати додаткову інформацію про те, який з компонентів настроювального вектора більше, або менше впливає на значення метрики.

Тобто: можна буде провести attribute-importamce аналіз.

Отже, що може вийде.

У вигляді графіка, якщо впорядкувати тести щодо зростання метрики, картина така:

Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму

Деякі дані, що відповідають крайнім значенням метрики:
Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму
Тут, на скріншоті з результатами, уточню: значення настроювального вектора — дано в термінах коду фітнес-функції, не в термінах намбер-списку параметрів/діапазонів значень параметрів, що формулювали вище за текстом.

Ну. Багато це, або мало, ~ 8тис tps: питання окреме.
У рамках лабораторної роботи — не має значення ця цифра, важлива динаміка, як це значення змінюється.

Динаміка тут хороша.
Вочевидь, що, щонайменше один чинник, значуще впливає значення метрики, ga-алгоритм, перебираючи вектора-хромосоми: накрив.
Судячи з досить бадьорої динаміки значень кривої, є ще як мінімум один фактор, який, хоч і значно менший, але впливає.

Ось тут потрібен attribute-importance аналіз, щоб зрозуміти: які атрибути (ну, у разі — компоненти настроювального вектора) і як сильно впливають значення метрики.
А від цієї інформації: зрозуміти — які чинники торкалися змін значних атрибутів.

виконати attribute-importance можна різними способами.

Мені, з цією метою, подобається алгоритм randomForest однойменного R-пакету (документація)
randomForestЯк я розумію його роботу взагалі і його підхід до оцінки важливості атрибутів зокрема, будує певну модель залежності змінної-відгуку, від атрибутів.

У нашому випадку змінна відгуку - це метрика, що отримується від субд, в навантажувальних тестах: tps;
А атрибути це компоненти настроювального вектора.

Так от randomForest оцінює важливість кожного атрибуту моделі двома числами: %IncMSE як наявність/відсутність даного атрибуту, в моделі, змінює MSE-якість цієї моделі (Mean Squared Error);

І IncNodePurity - це число, яке відображає наскільки якісно, ​​за значеннями даного атрибуту, можна розділити датасет зі спостереженнями, так щоб в одній частині виявилися дані, з якимось одним значенням метрики, що пояснюється, а в іншій з іншим значенням метрики.
Ну тобто: наскільки це класифікуючий атрибут (найбільш виразне, російськомовне пояснення по рандомфоресту бачив тут).

Робочо-селянський R-код для обробки датасету з підсумками навантажувальних тестів:

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

Можна прямо руками підбирати гіперпараметри алгоритму і, орієнтуючись на якість моделі, вибрати модель, яка точніше виконує передбачення на валідаційному датасеті.
Можна написати якусь функцію для цієї роботи (до речі — знову ж таки, на якомусь оптимізаційному алгоритмі).

Можна скористатися R-пакетом caret, не має значення.

У результаті, в даному випадку, виходить такий результат для оцінки ступеня важливості атрибутів:

Метод наукового тику, або як підібрати конфігурацію субд за допомогою бенчмарків та оптимізаційного алгоритму

Ну. Т.ч., можна приступати до глобальних осмислень:

  1. Виходить так що найбільш значущим, за даних умов тестування, виявився параметр commit_wait
    Технічно він задає режим виконання io-операції запису редо-даних, з лог-буфера субд, в current-журнальну групу: синхронний, або асинхронний.
    значення nowait при якому виходить практично вертикальний, кратний приріст значення tps-метрики: це включення асинк-моди в редо-групи.
    Окреме питання — треба чи не треба так робити в продовій бд. Тут я обмежуюсь лише констатацією: це значний чинник.
  2. Не дивно, що розмір лог-буффера субд: виявляється значним чинником.
    Чим менший розмір лог-буфера, тим менше його здатність буферизувати, тим частіше трапляються його переповнення і/або не можливість виділити в ньому вільну область під порцію нових редо-даних.
    А значить: затримки пов'язані з алокування простору в лог-буфері і/або скиданням редо-даних з нього в редо-групи.
    Ці затримки, звичайно ж, повинні впливати і впливають на пропускну здатність субд по транзакціях.
  3. Параметр db_block_checksum: ну, теж, загалом зрозуміло — обробка транзакцій призводить до утворення дарті-блоків у буферному кеші субд.
    Які, при включеній перевірці чексум датаблоків, базі доводиться обробляти - обчислювати ці чексуми від тіла датаблока, звіряти їх з тим, що написано в хідері датаблока: збігається/не збігається.
    Така робота, знову ж таки, не може не затягувати обробку даних, та й відповідно, параметр і механізм який цей параметр задає - виявляються значущими.
    Тому ж вендор пропонує, в документації на цей параметр, різні його (параметри) значення і зазначає, що так, імпакт буде, але, ось, різні значення, аж до "вимкнено" і різний імпакт, можете вибрати.

Та й глобальний висновок.

Підхід, загалом, виявляється цілком робочим.

Він цілком собі дозволяє, на ранніх етапах навантажувального тестування якоїсь сервісної системи, для вибору її (системи) оптимальної конфігурації під навантаження не дуже сильно вникати особливо налаштування системи під навантаження.

Але не виключає зовсім - хоча б на рівні розуміння: "регулювальних ручок" і допустимих діапазонів обертання цих ручок знати систему треба.

Далі підхід щодо швидко може знайти оптимальну конфігурацію системи.
І можна, за підсумками тестування, отримати інформацію про природу взаємозв'язку метрики якості роботи системи та значень настроювальних параметрів системи.

Що, звісно, ​​має сприяти виникненню ось цього глибокого розуміння системи, її роботи, по крайнього заходу — під даним навантаженням.

Практично це: обмін витрат на вникнення в систему, що налаштовується, на витрати з підготовки ось такого тестування роботи системи.

Окремо зазначу: у цьому підході критично важливий ступінь адекватності тестування системи до тих умов її роботи, які у неї будуть у продовій експлуатації.

Дякую за вашу увагу, час.

Джерело: habr.com

Додати коментар або відгук