Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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-запитів - зайва робота.

Докладніше в документації: HTTP-сесія

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).

Докладніше в документації: Блокування сесії