Здравствуйте.
Вирішив поділитися своєю знахідкою — плодом роздумів, спроб і помилок.
За великим рахунком: це ніяка не знахідка, звичайно ж, все це має бути давно відомо, тим, хто займається прикладною стат-обробкою даних та оптимізацією будь-яких систем, не обов'язково саме СУБД.
І: нехай знають, пишуть цікаві статті за своїми ресерчами,
З іншого боку: навскідку не вбачаю широкої згадки, поширення такого підходу, в інтернеті, серед ІТ-фахівців, ДБА.
Отже, насправді.
Припустимо, що ось у нас завдання: налаштувати якусь сервісну систему, на обслуговування якоїсь роботи.
Про цю роботу відомо: яка вона, в чому вимірюється якість цієї роботи і який критерій показник вимірів цієї якості.
Також припустимо що, більш-менш відомо-зрозуміло: як саме виконується робота (або з) цією сервісною системою.
"Більше-менш" - це означає що є можливість підготувати (або десь взяти) якийсь тулз, утиліту, сервіс яким можна синтезувати і подати на систему тестове навантаження досить адекватне до того, що буде в проді, в досить адекватних до роботи в проде умовах .
Та й припустимо, що відомий набір регулювальних параметрів цієї сервісної системи, якими можна налаштовувати цю систему, у сенсі продуктивності її роботи.
І, в чому проблема — немає достатньо повного розуміння цієї сервісної системи, що дозволяє експертно виставити налаштування цієї системи, під майбутнє навантаження, на даній платформі і отримати потрібно продуктивність роботи системи.
Ну. Так, майже завжди буває.
Що тут можна зробити?
Ну перше, що спадає на думку: зазирнути в доку за цією системою. Зрозуміти, які там допустимі діапазони на значення регулювальних параметрів. І, наприклад, методом координатного спуску, підбирати значення для параметрів системи у тестах.
Тобто. ставити системі якусь конфігурацію, як конкретного набору значень її настроювальних пар-ров.
Подавати на неї тестове навантаження, ось цією самою тулзою-утилітою, лоад-генератором.
І дивитися величину - відгук, ну чи метрику якості роботи системи.
Другою думкою може бути висновок такий, що — це дуже довго.
Ну тобто: якщо настроювальних параметрів - багато, якщо діапазони їх значень, що пробігаються, великі, якщо кожен окремий навантажувальний тест - виконується багато часу, то: так, це все може зайняти не прийнятно багато часу.
Ну і тут, що можна зрозуміти і згадати.
Можна дізнатися, в наборі значень настроювальних параметрів сервісної системи вектор, як послідовність якихось значень.
Кожному такому вектору, за інших рівних (у тому, що цим вектором не торкається) відповідає цілком певне значення метрики — показника якості роботи системи, під тестовим навантаженням.
Тобто
Ось позначимо вектор конфігурації системи як , Де ; Де - Кількість параметрів конфігурації системи, скільки їх, цих параметрів.
А значення метрики, що відповідає даному позначимо як
, то, у нас виходить функція:
Ну і тоді: все негайно зводиться до, в моєму випадку: майже забутих зі студентської лави, алгоритмів пошуку екстремуму функції.
Добре, але тут виникає організаційно-прикладне питання: а який саме алгоритм використовувати.
- У сенсі — щоб найменше руками кодити.
- І щоб працювало, тобто. знаходило екстремум (якщо він є), ну, принаймні, швидше за координатний спуск.
Перший момент натякає що треба подивитися в бік якихось середовищ, у яких такі алгоритми вже реалізовані, і є, в якомусь вигляді, готовому до використання в коді.
Ну, мені відомі python
и cran-r
Другий момент означає всі почитати про власне алгоритми, які вони є, які вони вимоги, особливості у роботі.
І що вони дають, можуть бути корисні побічні ефекти-результати, або безпосередньо від самого алгоритму.
Або їх можна одержати на підсумках роботи алгоритму.
Тут багато що залежить від вхідних умов.
Наприклад якщо, з якихось причин, потрібно швидше отримати результат, ну, потрібно дивитися у бік алгоритмів градієнтного спуску, вибирати якийсь із них.
Або, якщо не так важливий час, можна, наприклад, користуватися методами стохастичної оптимізації, наприклад генетичним алгоритмом.
Пропоную розглянути роботу такого підходу, щодо підбору конфігурації системи, з використанням генетичного алгоритму, на наступній, так би мовити: лабораторній роботі.
Вихідні:
- Нехай є як сервісна система:
oracle xe 18c
- Нехай вона обслуговує транзакційну активність і ціль: отримати можливо велику пропускну здатність субд, по транзакціях/сек.
- Транзакції - бувають дуже різні, за своїм характером роботи з даними та контекстом роботи.
Умовимося так що це транзакції які не обробляють велику кількість табличних даних.
У тому сенсі, що не генерують ундо-даних більше ніж редо і не обробляють великі відсотки рядків, великих таблиць.
Це транзакції, які змінюють один рядок у більш-менш великій таблиці, з невеликою кількістю індексів над цією таблицею.
У такому розкладі: продуктивність субд з обробки транзакцій, з застереженням, визначатиметься якістю обробки базою даних.
Застереження - якщо говорити саме про налаштування субд.
Тому що, в загальному випадку, можуть бути, наприклад, транзакційні блокування, між скл-сесіями, через дизайн користувальницької роботи з табличними даними та/або табличної моделі.
Які, звичайно ж, будуть гнітюче позначатися на tps-метриці і це буде екзогенний, щодо субд, фактор: ну ось так задизайнили табличну модель і роботу з даними в ній, що виникають блокування.
Тому для чистоти експерименту виключимо цей фактор нижче уточню як саме.
- Припустимо, для певності, що 100% поданих у субд sql-команд: це dml-команди.
Нехай характеристики роботи з субд: одні й ті ж, у тестах.
А саме: у скл-сесій, табличні дані, то як з ними працюють скл-сесії. - Субд працює в
FORCE LOGGING
,ARCHIVELOG
моди. Флешбак-датабейс режим вимкнено, на рівні субд. - Редологи: розташовані в окремій файловій системі, на окремому "диску";
Вся решта фізичної компоненти бд: в іншій, окремій фс, на окремому "диску":
Докладніше, для визначення фіз. компоненти лабораторної бд
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
Спочатку під ці умови навантаження суб транзакціями хотів використовувати
Вона має таку чудову особливість, процитую автора:
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
Повертаючись до постановки лабораторної роботи.
Будемо, за інших рівних, варіювати значення таких параметрів лабораторної субд:
- Розмір журнальних груп бд. діапазон значень: [32, 1024] Мбайт;
- Кількість журнальних груп бд. діапазон значень: [2,32];
log_archive_max_processes
діапазон значень: [1,8];commit_logging
допускається два значення:batch|immediate
;commit_wait
допускається два значення:wait|nowait
;log_buffer
Діапазаон значень: [2,128] Мбайт.log_checkpoint_timeout
діапазаон значень: [60,1200] секундdb_writer_processes
діапазаон значень: [1,4]undo_retention
Діапазаон значень: [30; 300] секундtransactions_per_rollback_segment
діапазаон значень: [1,8]disk_asynch_io
допускається два значення:true|false
;filesystemio_options
допускаються такі значення:none|setall|directIO|asynch
;db_block_checking
допускаються такі значення:OFF|LOW|MEDIUM|FULL
;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-підпограма виконує пошук максимізуючи фітнес-функцію.
Ну, т.ч., виходить що, в даному випадку, треба щоб фітнес-функція, розуміючи вектор як набір значень для певних параметрів субд, отримувала метрику від субд.
Тобто: скільки при даному налаштуванні субд і даному навантаженні на субд: субд обробляє транзакцій в секунду.
Тобто, розгортаючи, потрібно щоб усередині фітнес-функції виконувалася така багатоходівка:
- Обробка вхідного вектора чисел — перетворення його значення для параметрів субд.
- Спроба створення заданої кількості редо-груп, заданого розміру. Причому спроба може бути невдалою.
Журнальні групи, що вже існували в субд, в якому то кол-ві і якогось розміру, для чистоти експерименту - д.б. видалено. - При успіху попереднього пункту: завдання базі значень конфігураційних параметрів (знову ж таки: може бути збій)
- При успіху попереднього пункту: зупинка субд, запуск субд для того, щоб знову задані значення параметрів - набули чинності. (Знову ж таки: може бути збій)
- За успіху попереднього пункту: виконати навантажувальний тест. отримати метрику від субд.
- Повернути субд до вихідного стану, тобто. видалити додаткові журнальні групи, повернути роботу вихідну конфігурацію субд.
Код фітнес-функції
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-мірному просторі пошуку.
Дуже виразне, докладне
Окремо відзначу два технічні моменти.
Допоміжні виклики з ф-ції 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
, не має значення.
У результаті, в даному випадку, виходить такий результат для оцінки ступеня важливості атрибутів:
Ну. Т.ч., можна приступати до глобальних осмислень:
- Виходить так що найбільш значущим, за даних умов тестування, виявився параметр
commit_wait
Технічно він задає режим виконання io-операції запису редо-даних, з лог-буфера субд, в current-журнальну групу: синхронний, або асинхронний.
значенняnowait
при якому виходить практично вертикальний, кратний приріст значення tps-метрики: це включення асинк-моди в редо-групи.
Окреме питання — треба чи не треба так робити в продовій бд. Тут я обмежуюсь лише констатацією: це значний чинник. - Не дивно, що розмір лог-буффера субд: виявляється значним чинником.
Чим менший розмір лог-буфера, тим менше його здатність буферизувати, тим частіше трапляються його переповнення і/або не можливість виділити в ньому вільну область під порцію нових редо-даних.
А значить: затримки пов'язані з алокування простору в лог-буфері і/або скиданням редо-даних з нього в редо-групи.
Ці затримки, звичайно ж, повинні впливати і впливають на пропускну здатність субд по транзакціях. - Параметр
db_block_checksum
: ну, теж, загалом зрозуміло — обробка транзакцій призводить до утворення дарті-блоків у буферному кеші субд.
Які, при включеній перевірці чексум датаблоків, базі доводиться обробляти - обчислювати ці чексуми від тіла датаблока, звіряти їх з тим, що написано в хідері датаблока: збігається/не збігається.
Така робота, знову ж таки, не може не затягувати обробку даних, та й відповідно, параметр і механізм який цей параметр задає - виявляються значущими.
Тому ж вендор пропонує, в документації на цей параметр, різні його (параметри) значення і зазначає, що так, імпакт буде, але, ось, різні значення, аж до "вимкнено" і різний імпакт, можете вибрати.
Та й глобальний висновок.
Підхід, загалом, виявляється цілком робочим.
Він цілком собі дозволяє, на ранніх етапах навантажувального тестування якоїсь сервісної системи, для вибору її (системи) оптимальної конфігурації під навантаження не дуже сильно вникати особливо налаштування системи під навантаження.
Але не виключає зовсім - хоча б на рівні розуміння: "регулювальних ручок" і допустимих діапазонів обертання цих ручок знати систему треба.
Далі підхід щодо швидко може знайти оптимальну конфігурацію системи.
І можна, за підсумками тестування, отримати інформацію про природу взаємозв'язку метрики якості роботи системи та значень настроювальних параметрів системи.
Що, звісно, має сприяти виникненню ось цього глибокого розуміння системи, її роботи, по крайнього заходу — під даним навантаженням.
Практично це: обмін витрат на вникнення в систему, що налаштовується, на витрати з підготовки ось такого тестування роботи системи.
Окремо зазначу: у цьому підході критично важливий ступінь адекватності тестування системи до тих умов її роботи, які у неї будуть у продовій експлуатації.
Дякую за вашу увагу, час.
Джерело: habr.com