Одного ранку я прокинувся від сповіщень Slack. Це ніколи не є хорошим знаком.
За ніч мій екземпляр Redis повністю заповнився. Ми використовуємо Redis для двох речей:
- Session storage
- Caching a few bits of data, nothing substantial
Я використовував TablePlus, щоб побачити, що я можу в Redis. Трохи важко зрозуміти, що відбувається, оскільки Laravel використовує випадкові хеші як частину ключів кешу, а корисне навантаження закодовано/зашифровано.
Однак я бачив, що було 2 бази даних Redis (db0
і db1
). Перевіривши файл config/databases.php
, я виявив, що для Redis справді визначено 2 відповідні бази даних.
# File config/databases.php
return [
// Things ommitted here...
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];
З’єднання за замовчуванням
використовує db0
, тоді як з’єднання cache
використовує db1
.Виявляється, сеанси зберігаються в db0
, тоді як те, що ми кешуємо в коді, використовує db1
. База даних за замовчуванням, db0
, мала НАБАГАТО більше ключів, ніж база даних кешу.
Програма створювала забагато сеансів.
Що створює сеанс?
Кожен веб-запит (для маршрутів, визначених у routes/web.php
) створює сеанс (або використовує існуючий). Веб-програми повертають файл cookie під час створення сеансу. Веб-браузери зберігають ці файли cookie та надсилають їх назад, коли надсилають додаткові веб-запити. Це дозволяє нашим веб-додаткам знати, який сеанс дійсний для певного користувача.
Якщо веб-переглядач не повертатиме файл cookie на кожен запит, користувач не зможе залишатися в системі.
Сеанси на основі API не працюють таким чином. Кожна сесія створюється, а потім знищується в кожному веб-запиті – файли cookie не застосовуються. Натомість клієнт повинен надсилати інформацію про автентифікацію на кожен веб-запит (зазвичай певний маркер).
Що підірвало Redis?
Отже, що спричинило вибух сеансів у нашому екземплярі Redis?
Динамічно створені ресурси, які інші вбудовують на свої веб-сайти. У нас було два таких випадки:
- Our application generated a
.js
file that others embedded on their web sites - Our application also generated
.svg
images for the same purpose
Ці маршрути було визначено в нашому файлі routes/web.php
:
Route::get('/embed.js');
Route::get('/{project}/share.js');
Ви бачите проблему? Клієнти розміщували це на своїх веб-сайтах.Щоразу, коли хтось відвідував їхній веб-сайт, до нашої програми надсилався HTTP-запит для вбудовування або SVG, і це створювало сеанс.
Це означає, що веб-трафік наших клієнтів також створював сеанси в нашій веб-програмі!
Як зменшити кількість сеансів
Виправлення полягає в тому, щоб переконатися, що ми не створюємо сеанси для певних маршрутів. Сказати досить просто, але як це зробити?
Виявилося, що створення файлів cookie та сеансів здійснюється в проміжному програмному забезпеченні Laravel. Це добре, оскільки ми контролюємо, яке проміжне програмне забезпечення застосовується до кожного маршруту.
Щоб гарантувати, що деякі маршрути не створюють сеансів/повернення файлів cookie, я хочу створити окремий файл маршрутів із іншим стеком проміжного ПЗ.
Для цього нам потрібно зробити кілька речей:
- Create a new
routes/static.php
file (the name is arbitrary) - Add a middleware stack to
app/Http/Kernel.php
- Update
app/Providers/RouteServiceProvider.php
to load our new route file, and apply our new middleware stack
Новий файл маршрутів простий – ми створюємо новий файл і переміщуємо до нього наші визначення маршрутів:
# File routes/static.php`
# Move these from routes/web.php
Route::get('/embed.js');
Route::get('/{project}/share.js');
Тоді ми можемо оновити файл Kernel.php
, щоб створити новий стек проміжного ПЗ.Ми можемо скопіювати стек проміжного програмного забезпечення web
і видалити проміжне програмне забезпечення, яке обробляє файли cookie та сеанси:
# File app/Http/Kernel.php
# Items omitted here
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],26+ 27+ 'static' => [28+ \Illuminate\Routing\Middleware\SubstituteBindings::class,29+ ],
];
# Items omitted here
Ми створили нову групу проміжного програмного забезпечення під назвою static
. Це схоже на проміжне програмне забезпечення API, але ми не маємо регулювання.
Нарешті, нам потрібно зареєструвати новий файл маршрутів і застосувати нашу нову групу проміжного програмного забезпечення static
.Ми зробимо це, оновивши RouteServiceProvider.php
:
# File app/Providers/RouteServiceProvider.php
# Items omitted here
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));22+ 23+ Route::middleware('static')24+ ->namespace($this->namespace)25+ ->group(base_path('routes/static.php'));26+ });
}
# Items omitted here
RouteServiveProvider
реєструє кожен файл маршруту та визначає їх проміжне програмне забезпечення. Ось як усе в routes/web.php
отримує групу проміжного програмного забезпечення web
, призначену йому.