PDA

Просмотр полной версии : [Вопрос] Нагрузка на базу данных



KrutoyKrosch
30.10.2016, 00:01
Привет всем. Столкнулся с такой проблемой что стала лагать MySQL база когда я включаю сохранения всего что есть на сервере (сервер на локалке, не хочется что то хост покупать). Да я вполне соглашусь с тем кто скажет что у меня ЖД прожил свое и ему пора на помойку.

Вот сами сохранения

SetTimer("SaveVehicle", 40000, 0);
SetTimer("SavePlayer", 30000, 0);
SetTimer("SaveApartment", 20000, 0);
SetTimer("SaveHome", 25000, 0);



public SavePlayer()
{
for(new i, a = GetPlayerPoolSize() + 1; i < a; i++)
SavePlayer_1(i);

SetTimer("SavePlayer", 30000, 0);
}

stock SavePlayer_1(playerid)
{
format(gstring, sizeof(gstring),"UPDATE `players` SET `Name` = '%s', `Sex` = '%d', `Skin` = '%d', `Money` = '%d' WHERE `ID`='%d'",
pInfo[playerid][pName], pInfo[playerid][pSex], pInfo[playerid][pSkin], pInfo[playerid][pMoney], pInfo[playerid][pID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
}

public SaveHome()
{
for(new i; i < MAX_HOME; i++)
{
format(gstring, sizeof(gstring),"UPDATE `home` SET `NamePlayer` = '%s', `IDInterior` = '%d', `Bought` = '%d', `Status` = '%d', `Money` = '%d', `Bank` = '%d', `NumHome` = '%d', `IDStreets` = '%d', `Сity` = '%d', `X` = '%f', `Y` = '%f', `Z` = '%f' WHERE `ID` = '%d'",
iHome[i][hNamePlayer], iHome[i][hIDInterior], iHome[i][hBought], iHome[i][hStatus], iHome[i][hMoney], iHome[i][hBank], iHome[i][hNumHome], iHome[i][hIDStreets], iHome[i][hCity], iHome[i][hX], iHome[i][hY], iHome[i][hZ], iHome[i][hID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
}
SetTimer("SaveHome", 25000, 0);
}

public SaveApartment()
{
for(new i; i < MAX_APARTMENT_PLAYER; i++) // Зачем тут два цикла? Ну в первом обращение к 0 ячейке во втором к 1
{
format(gstring, sizeof(gstring),"UPDATE `apartment` SET `NamePlayer` = '%s', `Bought` = '%d', `Status` = '%d', `Money` = '%d', `Bank` = '%d', `IDInterior` = '%d' WHERE `ID`='%d'",
iApartment[0][i][aNamePlayer], iApartment[0][i][aBought], iApartment[0][i][aStatus], iApartment[0][i][aMoney], iApartment[0][i][aBank], iApartment[0][i][aIDInterior], iApartment[0][i][aID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
}
for(new i; i < MAX_APARTMENT_PLAYER; i++)
{
format(gstring, sizeof(gstring),"UPDATE `apartment` SET `NamePlayer` = '%s', `Bought` = '%d', `Status` = '%d', `Money` = '%d', `Bank` = '%d', `IDInterior` = '%d' WHERE `ID`='%d'",
iApartment[1][i][aNamePlayer], iApartment[1][i][aBought], iApartment[1][i][aStatus], iApartment[1][i][aMoney], iApartment[1][i][aBank], iApartment[1][i][aIDInterior], iApartment[1][i][aID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
}
SetTimer("SaveApartment", 20000, 0);
}

public SaveVehicle()
{
for(new i; i < MAX_VEHICLES; i++)
{
if(!iVehicle[i][vMoney]) continue;
SaveVehicle_1(i);
}
SetTimer("SaveVehicle", 40000, 0);
}

stock SaveVehicle_1(vehicleid)
{
format(gstring, sizeof(gstring),"UPDATE `vehicle` SET `NamePlayer` = '%s', `Money` = '%d', `Status` = '%d', `HP` = '%f', `VehicleID` = '%d', `Color1` = '%d', `Color2` = '%d', `Paintjob` = '%d', `Tyres` = '%d', `Spoiler` = '%d', `FrontBumper` = '%d', `RearBumper` = '%d', `Nitro` = '%d', `Hydraulics` = '%d', `Engine` = '%d' WHERE `ID`='%d'",
iVehicle[vehicleid][vNamePlayer], iVehicle[vehicleid][vMoney], iVehicle[vehicleid][vStatus], iVehicle[vehicleid][vHP], iVehicle[vehicleid][vVehicleID], iVehicle[vehicleid][vColor1], iVehicle[vehicleid][vColor2], iVehicle[vehicleid][vPaintjob], iVehicle[vehicleid][vTyres], iVehicle[vehicleid][vSpoiler], iVehicle[vehicleid][vFrontBumper], iVehicle[vehicleid][vRearBumper], iVehicle[vehicleid][vNitro], iVehicle[vehicleid][vHydraulics], iVehicle[vehicleid][vEngine], iVehicle[vehicleid][vID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
}


В чем собственно лаги базы: когда заходишь на сервер у тебя в течении 10 секунд появляется только окно авторизации, после того как авторизировался самп просто зависает на секунд 15 (это идет загрузка содержимого аккаунта), при этом всём ЖД компа загружен на 83% (все эти проценты жрет процесс mysqlbd.exe). Если же я отключаю сохранения, то все становится нормально, ЖД нагружен на 1%, окно авторизации появляется за наносекунды и т.д.

В чем может быть проблема? Может можно как то оптимизировать процесс сохранения что бы даже мой ущербный жесткий диск успел все записывать?

- - - Добавлено - - -

Кто то спросит зачем мне таймеры

Ну представим мой сервак ддудосят и он аварийно завершает работу, при этом не вызывается OnGameModeExit, и что? Сохранения все пропадают

ziggi
30.10.2016, 00:16
Кто так таймеры использует? Разве так не проще?

SetTimer("SaveVehicle", 40000, 1);

public SaveVehicle()
{
for(new i; i < MAX_VEHICLES; i++)
{
if(!iVehicle[i][vMoney]) continue;
SaveVehicle_1(i);
}
}

А по проблеме не знаю, сколько записей в БД?

KrutoyKrosch
30.10.2016, 00:19
Так еще больше подвисать будет, я использую по совету DeimoS

200 записей квартир
170 домов
1 машина
1 игрок

vovandolg
30.10.2016, 00:36
Так канеш не кто не делает в наше время))
Ну а что мешает написать систему в большое кол-во(на каждую квартиру, игрока и т.п.) таймеров с плавным сохранением?

- - - Добавлено - - -

Если так допустим сделать...


#define MAX_TIME_INTERVAL (200) //ms

public sm_SavePlayer()
{
for(new i, a = GetPlayerPoolSize() + 1; i < a; i++)
SetTimerEx("SavePlayer_1", i * MAX_TIME_INTERVAL, 0, "i", i);
}

KrutoyKrosch
30.10.2016, 00:38
ща попробуем

vovandolg
30.10.2016, 00:44
Главное интервал под себя настроить, а дальше всё по маслу пойдёт.
Время сохранения увеличится, но лаги должны пройти.

KrutoyKrosch
30.10.2016, 00:52
база как задыхалась, так и задыхается

https://pp.vk.me/c604629/v604629947/132d0/Q6FnuMdKXUk.jpg

задержку ставил и на 300 и на 400 дальше уже думаю не имеет смысла

vovandolg
30.10.2016, 00:53
база как задыхалась, так и задыхается

https://pp.vk.me/c604629/v604629947/132d0/Q6FnuMdKXUk.jpg

А код показать можно?

KrutoyKrosch
30.10.2016, 01:06
public SaveHome()
{
for(new i; i < MAX_HOME; i++)
SetTimerEx("SaveHome_1", MAX_INTERVAL_SAVE_TIME, 0, "i", i);

SetTimer("SaveHome", 25000, 0);
}

public SaveHome_1(homeid)
{
format(gstring, sizeof(gstring),"UPDATE `home` SET `NamePlayer` = '%s', `IDInterior` = '%d', `Bought` = '%d', `Status` = '%d', `Money` = '%d', `Bank` = '%d', `NumHome` = '%d', `IDStreets` = '%d', `Сity` = '%d', `X` = '%f', `Y` = '%f', `Z` = '%f' WHERE `ID` = '%d'",
iHome[homeid][hNamePlayer], iHome[homeid][hIDInterior], iHome[homeid][hBought], iHome[homeid][hStatus], iHome[homeid][hMoney], iHome[homeid][hBank], iHome[homeid][hNumHome], iHome[homeid][hIDStreets], iHome[homeid][hCity], iHome[homeid][hX], iHome[homeid][hY], iHome[homeid][hZ], iHome[homeid][hID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
}


ой да чет я уже спать хочу, i * не дописал, ща секунду

- - - Добавлено - - -

Нагрузка диска упала с 97% до 46%, лаги пропали (ну хз как при онлайне будет). Может кто то предложит вариант по лучше ?

vovandolg
30.10.2016, 01:12
Главное интервал под себя настроить

#define MAX_TIME_INTERVAL (200) //ms
Пробуй увеличивать)

KrutoyKrosch
30.10.2016, 01:29
Да и так уже 500 млс поставил

Saibot
30.10.2016, 01:31
Ну представим мой сервак ддудосят и он аварийно завершает работу, при этом не вызывается OnGameModeExit, и что? Сохранения все пропадают
Есть одно решение, даже таймеров не нужно будет.
Получается, что ты грубо говоря каждые 20 секунд отправляешь длинные запросы в БД на сохранение данных которые даже небыли изменены.

Не думаю, что такие значение, как:

`Sex` = '%d', `Skin` = '%d', `Money`NamePlayer` = '%s', `IDInterior` = '%d', `Bought` = '%d', `Status` = '%d', `Money` = '%d', `Bank` = '%d', `NumHome` = '%d', `IDStreets` = '%d', `Сity` = '%d', `X` = '%f', `Y` = '%f', `Z` = '%f' WHERE `ID` = '%d'
`Status` = '%d', `HP` = '%f', `VehicleID` = '%d', `Color1` = '%d', `Color2` = '%d', `Paintjob` = '%d', `Tyres` = '%d', `Spoiler` = '%d', `FrontBumper` = '%d', `RearBumper` = '%d', `Nitro` = '%d', `Hydraulics` = '%d', `Engine` = '%d'
меняются каждые 20 секунд.

Можно сделать после каждого изменения одного из этих перечислений сохранение, но именно того, что было изменено.

Например игрок получил деньги и в том же теле делать запрос в БД

format(gstring, sizeof(gstring),"UPDATE `players` SET `Money` = '%d' WHERE `ID`='%d'",
pInfo[playerid][pMoney], pInfo[playerid][pID]);
mysql_function_query(mysqlconnect, gstring, false, "", "");
И наш запрос получился коротким и только тогда когда у игрока было изменено перечисление pMoney

Конечно нужно будет не много поработать, но зато не будет лишних запросов в БД.
Но, решать тебе.

KrutoyKrosch
30.10.2016, 01:34
Ну если никто ниже не напишет решения, придется сделать как ты написал

vovandolg
30.10.2016, 01:38
А что если два или более игроков зайдут и начнут тасовать между собой эти деньги?:sarcastic:
Как тут без таймеров то) если хочется что то сделать мягко и без флуда таймеры точно пригодятся.

KrutoyKrosch
30.10.2016, 01:42
По сути кол-во запросов в цикле будет = их тасованию пока им не надоесть

vovandolg
30.10.2016, 01:50
По сути кол-во запросов в цикле будет = их тасованию пока им не надоесть

Если ты про мой вариант, то цикл быстро создаст много таймеров, которые не очень вредны в таком кол-ве, далее цикл окончится и таймеры как букеты начнут раскрываться один за одним по заданному интервалу, вариант от Saibot действительно лучше будет, так как серв с моим вариантом может упасть и не успеет что то засейвить, а тут у него на каждом шагу будет сейв, просто надо уметь с толком это всё делать чтобы не дудосили потом твою бд.

- - - Добавлено - - -

Есть такие вещи в игре которые постоянно тасуются туда сюда по кол-ву вот их и придется таким образом сохранять как я расписал если это конечно же к большому кол-ву объектов/субъектов и т.п.

Saibot
30.10.2016, 02:02
А что если два или более игроков зайдут и начнут тасовать между собой эти деньги?
Можно поставить таймер на флуд в таких местах.

А, каждые 20 + секунд оправлять больше 2000 (MAX_VEHICLES) запросов в БД не будет флуда? это только отправит public SaveVehicle не считая еще, SaveHome, SavePlayer и SaveApartment.

vovandolg
30.10.2016, 02:07
Можно поставить таймер на флуд в таких местах.

А, каждые 20 + секунд оправлять больше 2000 (MAX_VEHICLES) запросов в БД не будет флуда? это только отправит public SaveVehicle не считая еще, SaveHome, SavePlayer и SaveApartment.



- - - Добавлено - - -

Есть такие вещи в игре которые постоянно тасуются туда сюда по кол-ву вот их и придется таким образом сохранять как я расписал если это конечно же к большому кол-ву объектов/субъектов и т.п.
Типо того

ziggi
30.10.2016, 09:27
Отправлять запрос в БД после каждого изменения значения очень затратно и опасно. Можно попробовать объединить оба подхода и сохранять раз в N секунд только те данные, которые были изменены.

Saibot
30.10.2016, 17:19
Отправлять запрос в БД после каждого изменения значения очень затратно и опасно.
Почему же? отправляться будут краткие запросы и то когда, это нужно будет.

Вот пример:
Зашел игрок на сервер и встал в АФК на 1 час, за этот 1 час кроме expa и lvl больше ничего не изменялось.
Если сделать сохранение после изменение, то за это время только отправится два запроса на exp и lvl, но если будет таймер, как у автора темы, который каждые 20 секунд отправляет запросы в БД, то в итоге за один час только у этого игрока, что стоит в АФК отправится - 10.800 запросов в БД на обновление всех данных которые даже небыли за тронуты.


сохранять раз в N секунд только те данные, которые были изменены.
Если он создаст таймер для обновление, то как он узнает какие данные были изменены, что бы именно их обновить? делать кучу проверок?

KrutoyKrosch
30.10.2016, 18:04
Кучу проверок + цикл

так себе идея

- - - Добавлено - - -

а можно каким то способом узнать ддусят ли сервак или нет? просто если только начали - запускать сохранение, а так просто только в дисконнект запихать и се

Nexius_Tailer
30.10.2016, 18:12
а можно каким то способом узнать ддусят ли сервак или нет? просто если только начали - запускать сохранение, а так просто только в дисконнект запихать и се
Можно примерно узнать общую загруженность (типо фпс) сервера через GetServerTickRate (http://wiki.sa-mp.com/wiki/GetServerTickRate), чем ниже значение - тем хуже.
Насчёт дудоса: сохранение наверняка ещё больше повысит нагрузку в тот момент

KrutoyKrosch
30.10.2016, 18:15
тикрейт разве отвечает за загруженность? я слышал что это типа максимальная скорость обмена данных на сервере, ну ща посморим

Redsan
30.10.2016, 18:18
тикрейт разве отвечает за загруженность? я слышал что это типа максимальная скорость обмена данных на сервере, ну ща посморим

А я слышал что если вместо "return 1" писать "return true" производительность мода будет выше.
На вики все написано. (http://wiki.sa-mp.com/wiki/GetServerTickRate)

KrutoyKrosch
30.10.2016, 18:24
решил в OnPlayerUpdate засунуть, посмореть какой тик будет


через минуту после того как зашел на сервер

SA-MP 0.3.7
Exception At Address: 0x0040F64C
Base: 0x03970000

Registers:
EAX: 0x00000000 EBX: 0x22118688 ECX: 0x041C4D28 EDX: 0x000005A0
ESI: 0x00C6A2CC EDI: 0x22112680 EBP: 0x2210C678 ESP: 0x0177FBBC
EFLAGS: 0x00210246

Stack:
+0000: 0x007104C3 0x22112680 0x00000000 0x00000000
+0010: 0x00B6F9CC 0x00C6A7F0 0x00000001 0x00000000
+0020: 0x041C00C0 0x00000001 0x22112680 0x1005764C
+0030: 0x00000016 0x3C0010D7 0x3F4517D1 0xBF235C0A
+0040: 0x44BC69BC 0x44AA5E59 0x436C53FE 0x44BC4BEB
+0050: 0x44AA60EE 0x436CAE9E 0x00000000 0x2210C678
+0060: 0x041C4D28 0x22112680 0xBDA54000 0xBEB54000
+0070: 0x0000000B 0x00FFFFFE 0x00000800 0x0000072B
+0080: 0x0000003B 0x00000000 0x0000003B 0x041C00C0
+0090: 0x00000000 0x000006AB 0x041C75C0 0x000004AC
+00A0: 0x041C75C0 0x0177FCEC 0x76F55DE0 0x133DEC95
+00B0: 0xFFFFFFFE 0x0177FCB0 0x76F25005 0x00006000
+00C0: 0x00006008 0x00000000 0x0177FCA4 0x00B6F9CC
+00D0: 0x44BC69BC 0x44AA5E59 0x436C53FE 0x00000000
+00E0: 0x00000000 0x041C75C0 0x00000003 0x00000000
+00F0: 0x22118688 0x0177FCC0 0x76F24EA2 0x00000000
+0100: 0x3F6E8800 0x0177FCFC 0x00824219 0x041C0000
+0110: 0x00000000 0x0082421E 0xBBDC09A1 0xBF7B9B10
+0120: 0x3E3CCA66 0x00000061 0x3F72EF6A 0x3D547716
+0130: 0x3E9F4672 0x00825EA4 0xBEA17005 0x3E354B7E
+0140: 0x3F6EAD10 0x0082423B 0x44BC4BEB 0x44AA60EE
+0150: 0x436CAE9E 0x00000000 0x00000000 0x00000000
+0160: 0x0177FD7C 0x00848BD8 0x00000000 0x00711E46
+0170: 0x00C6A2CC 0x00B6F9CC 0x00000061 0xC83B3952
+0180: 0x0177FD8C 0x0053C1B0 0x00B6F9CC 0x0000001A
+0190: 0xFFFFFFFF 0x00000000 0x3C9684BC 0x3FE7A5D0
+01A0: 0xBFC00000 0x03A0DAAE 0x0000001A 0xC83B1A99
+01B0: 0x00000000 0x12004A20 0x0177FD64 0x0177F6FC
+01C0: 0x0177FF70 0x03A25D64 0x03A4A678 0x00000002
+01D0: 0x73AAF5E0 0x0053E986 0x00000001 0x008241AF
+01E0: 0x004D9486 0x0053ECC2 0x00000001 0x00619B71
+01F0: 0x0000001A 0x00000001 0x00000001 0x0000000A
+0200: 0x00748DA0 0x0000001A 0x00000001 0x7689CD90
+0210: 0x00000000 0x0177FF80 0x003A3000 0x011C0000
+0220: 0x4F483B0F 0x041C0EC8 0x00000008 0x00000100
+0230: 0x00000008 0x00000102 0x44700000 0x44070000
+0240: 0x00000000 0x00000000 0x00000780 0x00000438
+0250: 0x00000000 0x00000001 0x000106FC 0x00000200
+0260: 0x00000000 0x021C03C0 0x04774FCE 0x000003C0
+0270: 0x0000021C 0x0000002C 0x0177FE24 0x00825EA4

SCM Op: 0x685, lDbg: 0 LastRendObj: 19477

Game Version: US 1.0

State Information: Ped Context: 0

просто при низком тике запускать сохранение и кикать всех игроков

DeimoS
30.10.2016, 18:36
Если он создаст таймер для обновление, то как он узнает какие данные были изменены, что бы именно их обновить? делать кучу проверок?

Как вариант, создать второй массив, аналогичный массиву для хранения информации о аккаунте, и записывать в него данные при сохранении. Типа

new pInfo[MAX_PLAYERS][e_PLAYER_INFO];
new pInfo_buff[MAX_PLAYERS][e_PLAYER_INFO];

И при обновлении

if(pInfo_buff[playerid][имя_константы] != pInfo[playerid][имя_константы])
{
//тут запрос
pInfo_buff[playerid][имя_константы] = pInfo[playerid][имя_константы];
}

Но такой вариант, на самом деле, хорош только, например, для сохранения позиции или каких-то подобных данных, которые сами по себе обновляются часто и при изменении их сохранять не стоит

KrutoyKrosch
30.10.2016, 18:45
А если два раза изменится основной массив ? Получается он будет проверятся по первому, а изменение то второе. Думаю понял о чем я

все дошло как ты хочешь сделать) В принципе для автомобилей, домов, квартир это подойдет, там то не так часто что то обновляется. А для игроков все таки можно проверять тик и если он низкий включать сохранения через таймер. Ну и так по сути обычно на хостах mysql база и сам мод находится на разных серверах, поэтому если что то одно ддосят не так уж и страшно.

DeimoS
30.10.2016, 19:05
Не думаю, что, даже при онлайне в 1000 игроков и при сохранении основных данных прямо при их изменении, MySQL не вытянет всю нагрузку.
На многих серверах-гигантах сохранение происходит прямо при изменении (это можно определить, например, зайдя в игру + зайдя в личный кабине на сайте, а после начать передавать деньги и смотреть на данные в личном кабинете) и проблем особых нет. Мультипоточные запросы решают

Disinterpreter
31.10.2016, 23:23
Вообще я бы не советовал держать на домашнем ПК публичный игровой сервер.

qwezert
01.11.2016, 12:32
На самом деле да, нужно просто использовать сохранение совмещая оба способа(сохранение при выходе игрока и при изменении). Тогда можно минимизировать потерю данных и уменьшить нагрузку.