Chaos Engineering для PHP-приложений: ломаем сайт, чтобы спасти бизнес

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему ваш PHP-код обречен (и как это исправить)

Представьте: вы запустили обновление на сайте интернет-магазина. Всё работает идеально — в тестовой среде. Через час после деплоя падает база данных. Сайт лежит. Бизнес теряет миллион в минуту. Знакомая история?

Традиционное тестирование (unit, integration, e2e) проверяет сценарии, которые вы ожидаете. Но продакшн — это дикий запад: сетевые задержки, нехватка памяти, отказы сервисов, медленные запросы от реальных пользователей. Именно здесь в игру вступает Chaos Engineering — дисциплина, которая помогает выявлять слабые места системы до того, как они уничтожат ваш бизнес.

В этой статье на pythonlib.ru мы разберем, как применять хаос-инжиниринг для PHP-приложений: от простых скриптов до интеграции с популярными фреймворками. Вы узнаете, как намеренно ломать код, чтобы сделать его сильнее.

Что такое Chaos Engineering (и при чем тут PHP)?

Chaos Engineering (или хаос-инжиниринг) — это практика экспериментального тестирования системы путем внесения контролируемых "хаотических" сбоев. Цель — проверить, как приложение реагирует на непредвиденные обстоятельства, и найти скрытые баги, которые не ловятся обычными тестами.

Для PHP-разработчиков это особенно актуально. PHP часто работает в связке с MySQL/PostgreSQL, Redis, Nginx и кучей микросервисов. Каждое звено этой цепи может упасть. Chaos Engineering помогает ответить на вопросы:

  • Что будет, если Redis перестанет отвечать 5 секунд?
  • Как поведет себя приложение при внезапном отключении базы данных?
  • Выдержит ли код поток из 1000 одновременных запросов?
  • Что случится, если файл конфигурации повредится?

Инструменты для Chaos Engineering в PHP

К счастью, вам не нужно писать всё с нуля. Вот основные инструменты, которые помогут внедрить хаос в ваше PHP-приложение:

1. Chaos Monkey для PHP (самописный)

Netflix популяризировал Chaos Monkey — сервис, который случайным образом убивает процессы. В PHP вы можете реализовать его логику с помощью простого скрипта, который будет отключать сервисы в тестовой среде.

2. Библиотеки для fault injection

Специализированные библиотеки, такие как lstrojny/fault-injection или phpstan/phpstan (для статического анализа), помогают симулировать ошибки на уровне кода.

3. Нагрузочное тестирование + хаос

Комбинация инструментов вроде Apache Bench, JMeter или k6 с хаос-экспериментами дает максимальный эффект.

4. Docker и Kubernetes

Если вы используете контейнеризацию, можно легко "убивать" контейнеры с PHP-FPM или Nginx, наблюдая за поведением системы.

Практический пример: ломаем базу данных в PHP-приложении

Давайте напишем код, который симулирует отказ базы данных и проверяет, как наше приложение обрабатывает эту ситуацию. Это классический эксперимент Chaos Engineering.

Шаг 1. Код, который нужно протестировать

Предположим, у нас есть простой класс для работы с пользователями:

<?php

class UserRepository {
    private PDO $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function findById(int $id): ?array {
        try {
            $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
            $stmt->execute([$id]);
            $user = $stmt->fetch(PDO::FETCH_ASSOC);
            return $user ?: null;
        } catch (PDOException $e) {
            // Логируем ошибку
            error_log('Database error: ' . $e->getMessage());
            return null;
        }
    }
}

Код выглядит надежно: если БД упала, мы ловим исключение и возвращаем null. Но давайте проверим это в реальном хаос-эксперименте.

Шаг 2. Пишем Chaos Experiment

Создадим скрипт, который симулирует отказ базы данных, убивая соединение после каждого запроса:

<?php

require 'vendor/autoload.php';

// Класс-обертка для симуляции хаоса
class ChaosPDO extends PDO {
    private bool $shouldFail = false;
    
    public function __construct($dsn, $username, $passwd, $options = []) {
        parent::__construct($dsn, $username, $passwd, $options);
        $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    
    public function failNextQuery(): void {
        $this->shouldFail = true;
    }
    
    public function prepare(string $query, array $options = []): PDOStatement|false {
        if ($this->shouldFail) {
            $this->shouldFail = false;
            throw new PDOException('Simulated database crash!');
        }
        return parent::prepare($query, $options);
    }
}

// Настройка эксперимента
$chaosPdo = new ChaosPDO('mysql:host=127.0.0.1;dbname=test', 'root', '');
$repository = new UserRepository($chaosPdo);

// Запускаем 100 запросов, каждый 10-й — с хаосом
$success = 0;
$failures = 0;

for ($i = 0; $i < 100; $i++) {
    if ($i % 10 === 0) {
        $chaosPdo->failNextQuery(); // Каждый 10-й запрос ломаем
    }
    
    $user = $repository->findById(rand(1, 100));
    
    if ($user === null) {
        $failures++;
    } else {
        $success++;
    }
}

echo "Success: $success\n";
echo "Failures: $failures\n";

Запустив этот эксперимент, вы увидите, что при каждом 10-м запросе возвращается null. Но что если ваш код не обрабатывает null? Например, если вы забыли проверить возвращаемое значение? В реальном приложении это вызовет ошибку уровня TypeError или Warning.

Шаг 3. Анализ результатов

Наш эксперимент показал, что:

  • Приложение не падает полностью — это хорошо.
  • Но пользователь получает "пустой" ответ, что может привести к непредсказуемому поведению на фронтенде (например, белый экран или бесконечная загрузка).
  • Логирование ошибок работает, но нет механизма повторной попытки (retry) или кэширования.

Вывод: нужно добавить стратегию retry с экспоненциальной задержкой и, возможно, кэширование в Redis.

Продвинутые сцена

Блоги

Рекомендаций по книгам