Middle: питання на співбесіді з теми «Сесії»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
2 питання
Драйвер задається в config/session.php. На одному сервері різниці майже немає, на кількох - вона вирішальна.
file - файли на диску сервера. За балансувальником користувач після кожного запиту може потрапити на інший сервер, де його сесії немає, тож його «розлогінює» через раз.
cookie - усе зберігається в самій куці, зашифрованій. Спільного сховища не треба, але обсяг обмежений ~4 КБ, і дані їздять у кожному запиті.
database - таблиця sessions, спільна для всіх серверів. Працює надійно, ціна - запит на кожен запит.
redis - спільне сховище в памʼяті. Стандартний вибір для кількох серверів: швидко й без навантаження на базу.
Що ще ламається на кількох серверах, крім драйвера:
APP_KEYмає бути однаковий на всіх серверах, інакше куки, зашифровані одним, не розшифрує інший.- Той самий Redis мають бачити всі вузли - окремі інстанси не допоможуть.
Дві деталі, що псують життя окремо від масштабування. Сесія блокується на час запиту, тож кілька паралельних AJAX-запитів шикуються в чергу - для читання це знімається ->block() або сесією без запису. І в API сесій зазвичай узагалі не має бути: токен не потребує стану на сервері, а SESSION_DRIVER для stateless-запитів - зайва робота.
Laravel зчитує сесію на початку запиту, а записує цілком у кінці. Якщо два запити одного користувача виконуються паралельно, кожен працює зі своєю копією, і той, що завершиться останнім, перезапише зміни іншого.
Приклад:
Запит A (додати товар у кошик) читає кошик [1] записує [1, 2]
Запит B (додати інший товар) читає кошик [1] записує [1, 3] <- товар 2 втрачено
Так буває з кількома AJAX-запитами одночасно, подвійним кліком, кількома вкладками. На відміну від стандартних файлових сесій PHP, драйвери Laravel (database, redis, file) не блокують сесію за замовчуванням.
Блокування сесії для конкретних маршрутів:
Route::post('/cart/items', AddToCartController::class)
->block($lockSeconds = 10, $waitSeconds = 10);
- запит бере атомарне блокування для сесії (через кеш) і тримає його до
$lockSeconds; - наступний запит цієї ж сесії до маршруту з
blockчекає до$waitSeconds, а якщо не дочекався - отримуєLockTimeoutException; - запити виконуються послідовно, кожен бачить зміни попереднього.
Вимоги: драйвер кешу з атомарними блокуваннями (redis, memcached, database, dynamodb) і драйвер сесій не cookie.
Ціна: паралельність для одного користувача зникає - повільний запит затримує інші. Тому блокують лише маршрути, які змінюють сесію, а не весь застосунок.
Часто краще не тримати такі дані в сесії взагалі:
- кошик, чернетки, налаштування - у базі з транзакціями чи атомарними операціями. Там конкурентність розв'язується надійніше й не залежить від сесії;
- для лічильників - атомарний інкремент у Redis чи базі.
Як виявити проблему: «зникають» щойно додані дані, флеш-повідомлення показується не там, кошик втрачає товари при швидких кліках. Відтворюється одночасною відправкою двох запитів (наприклад, Promise.all з двох fetch).