# Server load fix, phase 1 — runbook (2026-10-17)

The site slows to a crawl once 100+ people use it at the same time. Phase 1 (branch `perf/load-phase1-2026-10-17`) removes work that every request did. Some of the fix is code and some is server configuration. This file covers the server part, in order.

## 0. Measure before deploying (read-only)

Run these on the server and keep the output, so there is something to compare against afterwards.

```bash
cd /path/to/kapitano
grep -E '^(APP_ENV|APP_DEBUG|CACHE_DRIVER|SESSION_DRIVER|SESSION_LIFETIME|QUEUE_CONNECTION|REDIS_)' .env
ls vendor/barryvdh 2>/dev/null                       # debugbar installed?
ls storage/framework/sessions | wc -l                # session files (every app call made one)
du -sh storage/framework/sessions storage/framework/cache storage/debugbar 2>/dev/null
php -m | grep -i redis                               # phpredis extension (optional; predis is in composer)
redis-cli ping
grep -E '^pm(\.|\s)' /etc/php/*/fpm/pool.d/www.conf  # pm.max_children etc.
free -m; nproc
```

In MySQL:

```sql
SHOW VARIABLES WHERE Variable_name IN ('max_connections','innodb_buffer_pool_size','long_query_time','slow_query_log');
SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Max_used_connections','Slow_queries');
-- turn the slow query log on for a busy day (resets when MySQL restarts):
SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 0.5;
```

## 1. Deploy the code (quiet hour)

1. Merge into `main` as usual and pull it on the server.
2. `composer install --no-dev --optimize-autoloader`. This drops debugbar.
3. `php artisan migrate --force`. This adds the hot lookup indexes (`2026_10_17_000001`). `products` has a FULLTEXT index, so run it at a quiet hour. Each index is skipped if its column is already indexed. The by-hand alternative is `sqlupdates/kapitano_hot_indexes_2026-10-17.sql`.

## 2. Switch the cache and sessions to Redis

Edit `.env`:

```
APP_DEBUG=false
CACHE_DRIVER=redis
SESSION_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# REDIS_PASSWORD=...
# sessions: REDIS_DB (default 0); cache: REDIS_CACHE_DB (default 1). Keep them different,
# so `cache:clear` (FLUSHDB of the cache db) never logs anybody out.
```

Leave `SESSION_LIFETIME` as production has it. Production already runs a 2-hour session.

Then run:

```bash
php artisan optimize:clear
php artisan optimize          # config + route cache (safe since 2026-10-03)
sudo systemctl reload php*-fpm
```

What to expect:

- Everyone signed in on the **website** is logged out once, because their old file sessions are no longer read. App users are not affected: they use tokens.
- The cache starts empty once, so the first page loads after the switch are slower.
- Afterwards you can delete the old session files: `find storage/framework/sessions -type f -delete`.

## 3. After deploying

- Count session files again. They should stop growing, because the app API no longer creates any.
- Compare the slow-query log and `Max_used_connections` on a busy day against step 0.
- Run a quick load check from another machine, for example `ab -n 2000 -c 100 https://kapitano.shop/` and the same against an app endpoint with the `System-Key` header. Compare against a run made before the deploy.

## Rollback

- **Redis switch:** set `CACHE_DRIVER=file` and `SESSION_DRIVER=file`, then `php artisan optimize:clear && php artisan optimize`.
- **Indexes:** `php artisan migrate:rollback --step=1` (only the new migration; it drops only the indexes it added). They are harmless to keep.
