Ответ
Да, на одном из проектов мы столкнулись с необходимостью горизонтального масштабирования, когда нагрузка выросла до 10k RPS. Мы использовали встроенный модуль cluster для распределения нагрузки между ядрами процессора:
const cluster = require('cluster');
const os = require('os');
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
console.log(`Primary ${process.pid} is running. Forking ${numCPUs} workers...`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Forking a new one...`);
cluster.fork();
});
} else {
require('./app'); // Запуск Express/Fastify приложения
console.log(`Worker ${process.pid} started`);
}
Дополнительно мы использовали:
- PM2 в production для управления процессами и zero-downtime перезагрузок
- Nginx как балансировщик нагрузки перед несколькими инстансами приложения
- Redis для сессий и кэширования, чтобы сделать приложение stateless
- Docker и Kubernetes для оркестрации контейнеров в облачной среде
Вертикальное масштабирование (увеличение RAM/CPU) мы применяли временно, но оно быстро достигало лимитов и было дороже горизонтального подхода.
Ответ 18+ 🔞
А, ну это классика, блядь! Мы тоже через это проходили, когда наш сервис внезапно стал популярным, и нагрузка подскочила до 10 тысяч запросов в секунду. Ёпта, серваки просто загорались, как новогодняя ёлка! Пришлось срочно думать, как это всё горизонтально масштабировать, потому что вертикально уже упёрлись в потолок — денег на железо нужно было овердохуища.
Вот смотри, первым делом мы заюзали встроенный в Node.js модуль cluster. Это же гениально, ёб твою мать! Один мастер-процесс плодит воркеров по числу ядер процессора, и они начинают дружно пахать. Код выглядел примерно так:
const cluster = require('cluster');
const os = require('os');
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
console.log(`Primary ${process.pid} is running. Forking ${numCPUs} workers...`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Forking a new one...`);
cluster.fork();
});
} else {
require('./app'); // Запуск Express/Fastify приложения
console.log(`Worker ${process.pid} started`);
}
Работает просто — мастер, сука, как папа Карло, штампует воркеров, а если какой-то вдруг накрылся медным тазом, он тут же нового запускает. Удобно, но для продакшена этого маловато будет, доверия к такому решению — ебать ноль.
Поэтому дальше мы взяли PM2. Вот это инструмент, ядрёна вошь! Он не только процессы плодит, но и следит за ними, делает zero-downtime деплои — то есть можно обновлять приложение без остановки. Красота, блядь! А ещё у него куча фич: мониторинг, логи, метрики. Просто подарок, а не утилита.
Но и это ещё не всё. Спереди мы поставили Nginx в роли балансировщика нагрузки. Он, хитрая жопа, распределяет запросы между несколькими физическими серверами или инстансами приложения. Так мы получили ещё один уровень масштабирования и отказоустойчивости. Если один сервер ляжет, Nginx просто перестанет слать на него трафик.
Дальше пошла магия stateless-архитектуры. Чтобы воркеры были взаимозаменяемыми, мы вынесли состояние наружу — в Redis. Сессии, кэш, всякие временные данные — всё туда. Теперь если воркер помер, пользователь даже не заметит, его сессию подхватит другой. Гениально и просто, как два пальца об асфальт.
Ну и вершиной этого цирка стали Docker и Kubernetes. Мы запаковали приложение в контейнеры, а K8s стал нашим главным распиздяем, который эти контейнеры плодит, убивает и распределяет по кластеру. Масштабирование теперь — это просто команда в консоли. Нагрузка подскочила? K8s автоматически запускает новые поды. Упала? Гасит лишние. Волшебство, ёбана!
А вертикальное масштабирование? Ну, мы его пробовали, конечно. Добавляли оперативки, апгрейдили процессоры. Но это, чувак, тупиковый путь. Быстро упираешься в лимиты железа, а платишь за это, будто за золотые унитазы. Горизонталка и дешевле, и надёжнее в итоге. Вот такая, блядь, история.