Лекция
Асинхронные запросы позволяют браузеру одновременно обращаться к нескольким API-эндпоинтам, не блокируя интерфейс пользователя. Но на стороне PHP такая параллельность может неожиданно исчезнуть.
Причина — механизм блокировки сессии. Если несколько запросов от одного пользователя используют одну PHP-сессию, каждый из них может ждать освобождения session lock, из-за чего запросы, отправленные одновременно через AJAX или fetch, фактически выполняются последовательно.
В результате простой session_start() способен стать скрытым узким местом производительности: чем дольше выполняется один запрос, тем дольше остальные запросы этого же пользователя находятся в ожидании.
В этой статье разберем, почему возникает блокировка PHP-сессии, как она влияет на асинхронные запросы и какие есть способы решить проблему — от раннего вызова session_write_close() до полного отказа от session-based authentication в API.
В PHP есть очень характерная проблема с сессиями и параллельными AJAX/fetch-запросами: сессия фактически может превратить асинхронные запросы в последовательные.
Представим, браузер одновременно отправляет:
GET /api/user GET /api/products GET /api/messages GET /api/notifications
Ты ожидаешь:
┌─ /user ───────┐ ├─ /products ───┤ START ─┼─ /messages ───┼─> одновременно └─ /notifications
Но если каждый PHP-скрипт делает:
session_start();
то PHP обычно блокирует сессионные данные для данного пользователя. Следующий запрос с тем же PHPSESSID ждет, пока предыдущий запрос закончит работу с сессией.
Получается примерно:
Request 1: session_start() └────── работа 1 ──────┘ session закрыта Request 2: WAIT ──────────┘ └── работа 2 ─── Request 3: WAIT ─────────────────────────┘ └─ работа 3
То есть AJAX технически асинхронный на стороне браузера, но PHP-запросы могут сериализоваться из-за lock сессии. Это особенно заметно, если один запрос долго выполняется.
Допустим, каждый endpoint работает 1 секунду.
Без блокировки:
4 запроса × 1 секунда ≈ 1 секунда общего времени
При блокировке одной PHP-сессии:
request 1 → 1 сек request 2 → 1 сек request 3 → 1 сек request 4 → 1 сек ≈ 4 секунды
И это может происходить только для одного пользователя/одной сессии, поэтому на серверных нагрузочных тестах проблема иногда вообще незаметна.
session_write_close()Если сессия нужна только в начале запроса, например:
session_start(); $userId = $_SESSION['user_id'];
а дальше выполняется тяжелая работа:
// запросы к БД // API // обработка // вычисления // sleep() // и т.д.
то лучше:
session_start(); $userId = $_SESSION['user_id']; session_write_close(); // дальше долгая работа
session_write_close() сохраняет данные сессии и освобождает блокировку, после чего другие параллельные запросы этого пользователя могут продолжить работу.
После:
session_write_close();
ты не должен рассчитывать, что изменения в $_SESSION дальше будут сохранены.
Например:
session_start(); $_SESSION['user_id'] = 123; session_write_close(); $_SESSION['something'] = 'abc'; // это уже не будет нормально сохранено
Поэтому правильная схема:
session_start(); $userId = $_SESSION['user_id']; $someValue = $_SESSION['someValue']; session_write_close(); // вся остальная работа
Если ты имеешь в виду именно Laravel, то проблема тоже актуальна.
Например, у тебя React делает одновременно:
/api/services /api/products /api/messages /api/profile /api/notifications
и Laravel использует обычную session-based authentication.
Если middleware открывает сессию, а endpoint затем выполняет долгую операцию, другие запросы этого же пользователя могут ждать освобождения session lock.
Поэтому иногда возникает очень странная ситуация:
«У меня 5 AJAX-запросов отправляются одновременно, но почему сервер обрабатывает их один за другим?»
И первая вещь, которую стоит проверить — PHP session locking.
Это не проблема самих async/await, React, Axios или браузера. Это серверная синхронизация доступа к сессии.
Если первое решение — session_write_close(), то второе принципиальное решение — вообще не использовать PHP-сессии для API-запросов, а перейти на stateless-аутентификацию, например Bearer-токены.
Вместо:
Browse │ │ PHPSESSID cookie ▼ PHP Session │ └── session lock
использовать:
Browser │ │ Authorization: Bearer▼ API │ └── проверка токена
Тогда каждый запрос не открывает общую PHP-сессию пользователя и не блокирует другие параллельные запросы.
Например:
GET /api/messages Authorization: Bearer abc123
одновременно:
GET /api/services Authorization: Bearer abc123
и:
GET /api/profile Authorization: Bearer abc123
Все три запроса могут независимо выполняться на сервере.
Для API обычно используют:
У тебя, судя по предыдущим вопросам, уже был вариант с Bearer token в localStorage и Axios interceptor. В таком случае это как раз подход, который позволяет не зависеть от PHP session lock.
Не стоит думать:
«Bearer token автоматически делает запросы быстрыми».
Он только убирает проблему блокировки PHP-сессии. Если все 5 запросов одновременно обращаются к одной медленной БД или выполняют тяжелую операцию, они все равно могут тормозить.
Итого:
| Подход | Session lock | Параллельные API-запросы |
|---|---|---|
PHP Session + session_start() |
Да | +- могут блокироваться |
Session + session_write_close() |
Только короткое время | + |
| Bearer Token / stateless API | Нет | + |
| Laravel Sanctum API | Зависит от способа аутентификации | +при token-based API |
Если API у тебя преимущественно REST + React, я бы предпочел второй подход: stateless API с Bearer token.
PHP-сессии удобны, но их механизм блокировки важно учитывать при разработке приложений с большим количеством параллельных запросов.
Если несколько AJAX-запросов используют одну сессию, долгий запрос может удерживать session lock и заставлять остальные запросы ждать. Внешне это выглядит так, будто асинхронные запросы выполняются последовательно, хотя браузер отправил их одновременно.
Если сессия нужна только для чтения данных в начале запроса, простое решение — получить необходимые значения и как можно раньше вызвать session_write_close(). Это освобождает блокировку и позволяет другим запросам продолжить выполнение.
Для API, особенно в SPA и мобильных приложениях, еще лучше рассмотреть stateless-аутентификацию с Bearer-токенами. В таком подходе состояние пользователя не хранится в общей PHP-сессии, поэтому запросы не конкурируют за один session lock.
Главный принцип простой: не удерживайте PHP-сессию дольше, чем это действительно необходимо. Асинхронность на стороне клиента имеет смысл только тогда, когда сервер также способен обрабатывать запросы параллельно.
Комментарии