The post has been translated automatically. Original language: Russian
Architectural constraints: static and synchronous services
Sometimes the server is “free”, but the RPS is not growing. This is an architectural ceiling.
Mistake #1. Static via PHP
If PHP processes:
- SVG,
- images,
- resize cache,
- checking the existence of files,
then each request executes system calls:
1. access()
2. stat()
3. lstat()
4. open()
Under load:
- growing syscall time,
- increases iowait,
- The CPU is loaded not with calculations, but with file operations.
The correct model:
- Nginx → direct to static,
- CDN for public resources,
- PHP is only for dynamics.
Mistake number 2. Synchronous external calls
Each external API call within an HTTP request:
- increases the variance,
- adds latency,
- creates dependence on someone else's infrastructure.
In one of the cases, the dynamic limit was 30 RPS. The reason is a synchronous call to the GeoIP service.:
- connect()
- poll()
- timeout of 5 seconds
- blocking the worker
With 30 simultaneous requests, all the workers were waiting for an external service to respond.
Decision:
1. Local database,
2. in-memory cache,
3. async queue,
4. Geo pre-calculation.
After the synchronicity was eliminated, the limit increased several times.
I/O and disk: hidden degradation
The CPU may be free, but the system is slow. The reason is I/O.
What it looks like in strace
When analyzing one process, you can see:
- 30-40% of the time — access()
- 25–30% — stat()
- tens of thousands of read() calls
This means that the code is actively working with the file system.
Typical reasons
- Download the same file with each request.
- Cache storage on HDD.
- Using NFS under high load.
- Checking multiple image sizes for each product.
Why is this critical?
Each syscall:
- causes a context switch,
- requires accessing the inode,
- can be blocked when the I/O queue is high.
If await in iostat is > 10-20 ms under load, this is already a bottleneck.
What to do
- transfer hot data to memory,
- use SSD/NVMe,
- exclude NFS from the hot circuit,
- remove repeated file checks,
- optimize CMS logic.
The result of the cycle
It's not the “site” that breaks down under load. A specific layer breaks:
- monitoring,
- CPU,
- process pool,
- network,
- external services,
- the disk.
Load testing is not about RPS. It's about finding a point of degradation and eliminating architectural constraints.
Архитектурные ограничения: статика и синхронные сервисы
Иногда сервер “свободен”, но RPS не растёт. Это архитектурный потолок.
Ошибка №1. Статика через PHP
Если PHP обрабатывает:
- SVG,
- изображения,
- resize cache,
- проверку существования файлов,
то каждый запрос выполняет системные вызовы:
1. access()
2. stat()
3. lstat()
4. open()
Под нагрузкой:
- растёт syscall time,
- увеличивается iowait,
- CPU загружен не вычислениями, а файловыми операциями.
Правильная модель:
- Nginx → напрямую к статики,
- CDN для публичных ресурсов,
- PHP только для динамики.
Ошибка №2. Синхронные внешние вызовы
Каждый вызов внешнего API в рамках HTTP-запроса:
- увеличивает variance,
- добавляет latency,
- создаёт зависимость от чужой инфраструктуры.
В одном из кейсов предел динамики был 30 RPS. Причина — синхронный вызов GeoIP-сервиса:
- connect()
- poll()
- timeout 5 секунд
- блокировка воркера
При 30 одновременных запросах все воркеры ждали ответа внешнего сервиса.
Решение:
1. локальная база,
2. in-memory кеш,
3. async очередь,
4. предрасчёт гео.
После устранения синхронности предел вырос кратно.
I/O и диск: скрытая деградация
CPU может быть свободен, но система медленная. Причина — I/O.
Как это выглядит в strace
При анализе одного процесса видно:
- 30–40% времени — access()
- 25–30% — stat()
- десятки тысяч вызовов read()
Это означает, что код активно работает с файловой системой.
Типовые причины
- Загрузка одного и того же файла при каждом запросе.
- Хранение кэша на HDD.
- Использование NFS под высокой нагрузкой.
- Проверка нескольких размеров изображений на каждый товар.
Почему это критично
Каждый syscall:
- вызывает переключение контекста,
- требует обращения к inode,
- может блокироваться при высокой очереди I/O.
Если await в iostat > 10–20 ms под нагрузкой — это уже узкое место.
Что делать
- переносить горячие данные в память,
- использовать SSD/NVMe,
- исключить NFS из горячего контура,
- убрать повторные проверки файлов,
- оптимизировать логику CMS.
Итог цикла
Под нагрузкой ломается не “сайт”. Ломается конкретный слой:
- мониторинг,
- CPU,
- пул процессов,
- сеть,
- внешние сервисы,
- диск.
Нагрузочное тестирование — это не про RPS. Это про поиск точки деградации и устранение архитектурных ограничений.