Отключение HTTP сессий для ускорения API

Если вы запускаете приложение Laravel чисто как headless API, то будет выгодно отключить HTTP сессии.

Мы это используем в службе мониторинга Oh Dear, где все удаленные сервера, проверяющие аптайм, это headless API на Laravel.

Дефолтное поведение сессий в Laravel

По дефолту в Laravel включены HTTP-сессии. Это делается через обработчик file, который их сохраняет в каталог storage/framework/sessions.

Довольно часто там можно найти тысячи файлов сессий.

$ pwd
storage/framework/sessions

$ ls -alh | wc -l
1155

Каждый новый посетитель (или бот), открывающий ваше приложение, генерирует новый уникальный идентификатор сессии PHP.

Фактически для наших конечных точек API это складывается.

$ ls -alh | wc -l
33984

Более 30 000 файлов! А ведь мы даже не использовали сессии. И все потому, что они включены по умолчанию!

Как сессии влияют на производительность?

Вы можете хранить сессии в файлах (по дефолту), в Redis, в Memcached,… Но файловый обработчик по умолчанию требует очистки в ручную, иначе файлы старых сессий никогда не истекут.

Это настраивается в файле config/sessions.php.

/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery (Лотерея очистки сессий)
|--------------------------------------------------------------------------
|
| Некоторые драйверы сессий должны вручную очищать свои хранилища от старых сессий. 
| Ниже приведены шансы, что это случится по текущему запросу. 
| По умолчанию, шансы составляют 2 из 100.
|
*/

'lottery' => [2, 100],

Это означает, что только 2% всех запросов будут запускать код, проверяющий каждый файл в каталоге и удаляющий старые сессии. Это сборщик мусора ваших сессионных файлов.

2% — звучит не так уж и много, правда?

Для большинства приложений это разумное значение. Однако наш API получает несколько миллионов запросов в день, и в этот момент все начинает складываться.

2% всех этих запросов будут в цикле обрабатывать каталог, проверять возраст каждого файла и удаляя его при необходимости. Это означает, что мы имеем (значительное) случайное замедление 2% всех наших вызовов API.

Если вы сделайте strace такого запроса, то вы увидите, насколько интенсивными могут быть операции с данными.

(Strace показывает системные или низкоуровневые вызовы, которые выполняет процесс php.)

$ strace -p [PID] -e trace=file
[...]
[pid 12084] lstat("storage/framework/sessions/L0Ecb4JK1yeOVyEZgTmIscT8uuVOfXJE3OI174Rp", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] stat("storage/framework/sessions/L0Ecb4JK1yeOVyEZgTmIscT8uuVOfXJE3OI174Rp", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] access("storage/framework/sessions/L0Ecb4JK1yeOVyEZgTmIscT8uuVOfXJE3OI174Rp", F_OK) = 0

[pid 12084] lstat("storage/framework/sessions/GGjrcIYeceatB0EK9dvpqT4thFQM39PfDQFSbW6R", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] stat("storage/framework/sessions/GGjrcIYeceatB0EK9dvpqT4thFQM39PfDQFSbW6R", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] access("storage/framework/sessions/GGjrcIYeceatB0EK9dvpqT4thFQM39PfDQFSbW6R", F_OK) = 0

[pid 12084] lstat("storage/framework/sessions/TGi8DXmKJfT7p6YFaJ14lnHGpN2ey6ydqBpqodZv", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] stat("storage/framework/sessions/TGi8DXmKJfT7p6YFaJ14lnHGpN2ey6ydqBpqodZv", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] access("storage/framework/sessions/TGi8DXmKJfT7p6YFaJ14lnHGpN2ey6ydqBpqodZv", F_OK) = 0

[pid 12084] lstat("storage/framework/sessions/pvZdw0hYzvn6BdxdTslKeBTuQIeVZj8V2cV7OswA", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] stat("storage/framework/sessions/pvZdw0hYzvn6BdxdTslKeBTuQIeVZj8V2cV7OswA", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] access("storage/framework/sessions/pvZdw0hYzvn6BdxdTslKeBTuQIeVZj8V2cV7OswA", F_OK) = 0

[pid 12084] lstat("storage/framework/sessions/eu19GV2bGUfe1yGviTkuObRYC35mUNOLjQmsDQ3z", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] stat("storage/framework/sessions/eu19GV2bGUfe1yGviTkuObRYC35mUNOLjQmsDQ3z", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
[pid 12084] access("storage/framework/sessions/eu19GV2bGUfe1yGviTkuObRYC35mUNOLjQmsDQ3z", F_OK) = 0

Для каждого файла сессий из каталога storage/framework/session необходимо выполнить 3 системных вызова.

Умножьте это на количество имеющихся у вас файлов, и поймете, что PHP потратит приличное количество процессорного времени на их обработку.

График работы CPU при очистке сессий в Laravel

Приведенная выше диаграмма CPU взята с одного из наших удаленных серверов проверки аптаймов. Мы развернули фикс, отключающий HTTP-сессии для API примерно в 10:45.

С этого момента нагрузка на нашу систему стала намного более стабильной и контролируемой. Мы избавились от всех случайных пиков работы процессора, происходивших из-за очистки файлов сессий.

Отключение HTTP-сессий в Laravel

Если вам вообще не нужны HTTP-сессии, то отключить их довольно просто. Откройте ваш app/Http/Kernel.php и удалите следующие мидлвары из группы protected $middlewareGroups:

\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,

После этого ваше приложение больше не будет запускать или загружать сессии.

Почему бы просто не переключиться на {memcached, redis,…}?

Да, memcached или redis имеют собственный механизм очистки, который также предотвратил бы случайные скачки CPU. В конце концов, больше не PHP будет заниматься очисткой.

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

Если вам все же нужен обработчик файловых сессий…

…возможно, стоит поэкспериментировать, можете ли вы полностью отключить лотерею/сборщик мусора и запустить собственный процесс, который займется этим.

Если вы отключите лотерею и не будете очищать сессии вручную, то они будут накапливаться и в конечном итоге займут всё ваше дисковое пространство. Но отказ от стандартного подхода очистки сессий может быть полезен. Ваши HTTP-запросы больше не вызовут случайные скачки CPU из-за очистки сессий, так как уборкой займутся кастомные воркеры.

Увеличит ли это быстродействие?

Зависит от многих причин.

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

Автор: Mattias Geniar
Перевод: Алексей Широков

Подписывайтесь на новости в Вконтакте, Твиттер и Телеграмм.

Наш Телеграм-канал — следите за новостями о Laravel.