А может быть кто-то подскажет, что я мог упустить.
Итак, имеем относительно посещаемый форум, 70 тыщ юзеров, 500-700 онлайна.
Работает на 3.1, но и для 3.2 все описанное актуально, т.к. изменений по сути в данной фукнциональности нет.
Обнаружил, что таблицы sessions, sessions_keys, login_attempts и confirm, мягко говоря, ненормального размера - 600, 370, 50 тыс. записей соответственно. А confirm так вообще 4,5 млн строк!
Почитал топик: Разлогинивает рандомно + доступ к логину за пределами форума - хоть и не по теме, но он дал понимание о sessions_gc() - функции, которая по крону чистит, как оказалось, все вышеописанные таблицы.
Начал ее смотреть...
В самом начале
Код: Выделить всё
$batch_size = 10;
Код: Выделить всё
$sql = 'SELECT session_user_id, session_page, MAX(session_time) AS recent_time
FROM ' . SESSIONS_TABLE . '
WHERE session_time < ' . ($this->time_now - $config['session_length']) . '
GROUP BY session_user_id, session_page';
$result = $db->sql_query_limit($sql, $batch_size);
Далее из таблицы session удаляются истекшие сессии юзеров с указанными id. Причем, разрабы не предусмотрели что запрос может отдавать одинаковые id и пихают это все в
Код: Выделить всё
// Delete expired sessions
$sql = 'DELETE FROM ' . SESSIONS_TABLE . '
WHERE ' . $db->sql_in_set('session_user_id', $del_user_id) . '
AND session_time < ' . ($this->time_now - $config['session_length']);
Код: Выделить всё
$del_user_id = array_unique($del_user_id);
Итак, вычистили мы эти 10 сессий, а дальше gc продолжает работу если истекших сессий больше нет (точнее меньше 10 было в этот раз) А если есть, прекращает работу не меняя время последнего выполнения по крону cron.task.core.tidy_sessions, чтобы запуститься еще раз.
Что получается:
Само задание cron.task.core.tidy_sessions запускается каждые 3600 по умолчанию, или каждый раз при глобальном кроне, если выше кол-во сессий превышает 10 (у меня, кстати, */5 для глобального крона стоит).
Даже за 5 минут, не говоря уж о 60 (если, скажем, снести все сессии вообще) форум посещают большое кол-во пользователей, которые генерят большое кол-во сессий, уж точно больше 10. И со временем, когда доходит время до очистки, этих 10 записей просто недостаточно для того, чтобы нормально чистить таблицу sessions. При этом, для пользователей, для которых такая очистка не происходит, не удаляются все новые и новые сессии и все это накапливается как снежный ком (до них просто дело не доходит).
В итоге, cron.task.core.tidy_sessions выполянется при каждом вызове глобального крона.
Но так как сессий всегда больше 10 (и их кол-во только растет со временем) сборщик мусора не дохолдит до других задач
А там и чистка sessions_keys и login_asttempts и confirm.
В общем, не совсем ясно, что хотели сделать этим LIMIT 10 - ограничить нагрузку, когда крон выполняется при генерации страницы, пользователем, а не через системный? Короче, поставил лимит в 1000 и ежеминутный запуск cron.task.core.tidy_sessions
С confirm тоже кстати забавная история вышла. Как я выше упоминал, ее размер оказался 4,5 млн строк. 4,5 млн капч??? что???
Оказалось, что если Гость пытается открыть какой-нибудь posting.php - происходит переадресация на страницу логина, но при этом генерится капча с типом 3. А такое очень любят делать роботы/спамеры, долбясь тысячами запросов в этот posting.php и генерируя "миллионы капч" в таблице confirm.
В итоге, очистив seesion и дав продолжение работы сборщику мусора он уперся в чистку таблицы confirm вывалившись с ошибкой, поскольку наткнулся на запрос:
Код: Выделить всё
function garbage_collect($type)
{
global $db, $config;
$sql = 'SELECT DISTINCT c.session_id
FROM ' . CONFIRM_TABLE . ' c
LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id)
WHERE s.session_id IS NULL' .
((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type);
$result = $db->sql_query($sql);
if ($row = $db->sql_fetchrow($result))
{
$sql_in = array();
do
{
$sql_in[] = (string) $row['session_id'];
}
while ($row = $db->sql_fetchrow($result));
if (sizeof($sql_in))
{
$sql = 'DELETE FROM ' . CONFIRM_TABLE . '
WHERE ' . $db->sql_in_set('session_id', $sql_in);
$db->sql_query($sql);
}
Пришлось предыдущий SELECT ограничить в районе LIMIT 1000-10000 и повторить несколько сотен итераций запуска сборщика...
В общем, такая вот штука.
Причина - посещаемый форум, не лучший запрос и его лимит в 10ку строк... и все растет снежным комом с каждой минутой...
Отправлено спустя 2 минуты 41 секунду:
Да, собственно, пока закончилось тем, что я поставил лимит в 1000 выборки из sessions и 10000 для выборки из confirm и оставил выполенение только через глобальный cron:run
Посмотрим, достаточно ли этихъ значений, если session_gc() будет дергаться раз в 3600 секунд, как это установлено по дефолту
После чистки
sessions - 4850 строк
sessions_keys - 99140 (730 дней стоит)
login_attempts - 20
confirm - 225650