Хабраэффект 2011: как пост на Хабре положил наш сервер и что мы с этим сделали
Это было в четверг около 11 утра. Наш инструмент для работы с задачами — небольшой SaaS для разработчиков — попал в посты дня на Хабрахабр. Через 11 минут после публикации Zabbix начал слать алерты. Через 18 минут сайт не открывался.
Хабраэффект — это не городская легенда русскоязычного интернета. Это конкретное явление с конкретными техническими причинами. Аудитория Хабра в 2011 году — технари, открывающие ссылку сразу. Никакого медленного нарастания, как с соцсетями. Одновременный резкий пик — и всё.
Что упало и в каком порядке
Сервер: VDS на 2 ядра, 2 ГБ RAM, Ubuntu 10.04, Apache + mod_php, MySQL 5.1, без кэша.
Хронология:
- T+0: пост опубликован, первые 50 посетителей
- T+8 мин: 600 одновременных посетителей, Apache упёрся в MaxClients 150
- T+11 мин: очередь Apache переполнена, новые запросы получают 503
- T+14 мин: MySQL достиг max_connections (100 по умолчанию), PHP-ошибки
- T+17 мин: ошибки пишутся в /var/log/apache2/error.log, диск 100%
- T+18 мин: MySQL крашнулся из-за невозможности записать бинлог (диск полон)
- T+19 мин: сервер не отвечает на SSH
Всё это — за 19 минут. Наш сайт был недоступен 2 часа 40 минут в пике хабраэффекта.
Вскрытие: три слоя проблем
Слой 1: Apache держал соединение пока PHP не отработал. При медленных запросах (а запросы к БД без кэша были медленными) Apache-воркер блокировался на всё время выполнения. 150 воркеров × среднее время ответа 800мс = пропускная способность ~190 запросов/сек. На практике — меньше, потому что при нагрузке MySQL начинал тормозить.
Слой 2: каждый запрос — полный рендер из БД. Главная страница делала 34 SQL-запроса. Никакого кэша. При 150 одновременных посетителях: 5100 SQL-запросов в секунду к одному MySQL без query cache.
Слой 3: логирование без ротации и лимита. PHP-ошибки (которые сыпались из-за перегруженного MySQL) писались без ограничений. За 4 минуты error.log вырос с 2 МБ до 18 ГБ. Диск кончился.
Что мы сделали после восстановления
Nginx как reverse proxy и статика
Заменили Apache на схему nginx → PHP-FPM. Nginx отдаёт статику сам, к PHP-FPM идут только динамические запросы:
server {
listen 80;
# Статика — сразу, без PHP
location ~* \.(css|js|png|jpg|woff|ico)$ {
root /var/www/app/public;
expires 30d;
access_log off;
}
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php5-fpm.sock;
include fastcgi_params;
fastcgi_read_timeout 30;
}
}
При хабраэффекте статика (CSS, JS, картинки) составляет ~70% запросов. Nginx держит тысячи таких соединений без каких-либо воркеров PHP.
Memcached на каждом дорогом запросе
Правило: если SQL-запрос выполняется дольше 20 мс — он идёт через кэш.
class CacheQuery {
private $mc;
public function get(string $key, int $ttl, callable $loader): mixed {
$cached = $this->mc->get($key);
if ($cached !== false) return $cached;
$result = $loader();
$this->mc->set($key, $result, 0, $ttl);
return $result;
}
}
// Главная страница: было 34 запроса, стало 3 (только авторизация и счётчики)
$projects = $cache->get("user:{$userId}:projects", 120, fn() =>
Project::whereUserId($userId)->with('stats')->get()
);
После внедрения: главная страница — 3 SQL-запроса вместо 34. При хабраэффекте кэш держит нагрузку, MySQL получает запросы только на кэш-промахах.
Ограничение логов и ротация
# /etc/logrotate.d/apache2
/var/log/nginx/error.log {
daily
missingok
rotate 7
compress
size 100M # Ротация при достижении 100 МБ, не только по дате
notifempty
sharedscripts
}
И в PHP:
// Лимит на размер error_log
ini_set('log_errors_max_len', 1024);
ini_set('error_log', '/var/log/php/error.log');
// Ротация через cron каждые 6 часов при нагрузке
Тест на хабраэффект (синтетический)
После всех изменений мы прогнали Apache Bench с профилем, имитирующим хабраэффект — 1000 одновременных соединений, 10 000 запросов:
ab -n 10000 -c 1000 -k https://ourapp.com/
# До оптимизации: 503 на 84% запросов, сервер лёг на 2000 запросов
# После: 0 ошибок, среднее время ответа 340мс, p99 — 1100мс
Что хабраэффект изменил в нашем подходе
После 2011 года у нас появилось правило: каждый новый проект перед запуском проходит нагрузочный тест с профилем ×50 от ожидаемого пика. Не ×2, не ×5. Хабр и Reddit не предупреждают.
Второе правило: мониторинг диска — первый алерт, не последний. Полный диск валит больше сервисов, чем кончившаяся RAM.
Хабраэффект 2011 обошёлся нам потерей ~3 часов трафика в пике и репутационным ущербом среди аудитории, которая пришла по ссылке и увидела 502. Это был дорогой, но необходимый урок о разнице между «работает в обычный день» и «выдержит нагрузку».