Как мы обслуживаем 10 миллионов пользователей всего на двух серверах — антимикросервисный подход

Исходная ситуация

Джоб-борд «РаботаВсем» — агрегатор вакансий для регионов России. 10 миллионов уникальных посетителей в месяц, 50 миллионов просмотров страниц, 400 000 активных вакансий. Стандартный набор функций: поиск, фильтрация, отклики, личные кабинеты работодателей и соискателей.

Когда CTO пришёл к нам в itfresh.ru, у него было 14 серверов: 3 веб-сервера, 2 базы, 2 Redis, 3 воркера, 2 балансировщика, ElasticSearch, мониторинг. Ежемесячные расходы на инфраструктуру — 480 000 рублей. Утилизация CPU на веб-серверах — 5-10%. RAM использовалась на 30%.

Мы предложили радикальное решение: сжать всё до двух серверов. Не из экономии ради экономии, а потому что меньше серверов — меньше точек отказа, проще мониторинг, быстрее деплой.

Вертикальное масштабирование: почему два мощных сервера лучше десяти слабых

Индустрия годами продвигает горизонтальное масштабирование. Но для большинства проектов это преждевременная оптимизация. Вот математика «РаботаВсем»:

  • 50 миллионов просмотров/месяц = ~20 запросов/сек в среднем, ~200 запросов/сек в пике.
  • Один современный сервер обрабатывает 5000-10000 запросов/сек при правильной настройке.
  • У нас 25-кратный запас мощности на одном сервере.

Конфигурация каждого из двух серверов:

КомпонентХарактеристики
CPUAMD EPYC 7543P — 32 ядра, 64 потока
RAM256 GB ECC DDR4-3200
Системный диск2x Samsung PM9A3 960 GB NVMe (RAID 1)
Диск данных2x Samsung PM9A3 3.84 TB NVMe (RAID 1)
Сеть2x 10 Gbps

Стоимость двух таких серверов в аренду — 120 000 руб/мес. Экономия — 360 000 руб/мес (75%).

PostgreSQL на мощном железе: тюнинг для максимальной производительности

Ключевой компонент — PostgreSQL 16, настроенный для максимального использования железа. На слабом сервере с 8 GB RAM такие настройки невозможны:

# postgresql.conf — тюнинг для 256 GB RAM и NVMe

# Память
shared_buffers = 64GB
effective_cache_size = 192GB
work_mem = 256MB
maintenance_work_mem = 4GB
wal_buffers = 128MB

# WAL и чекпоинты
wal_level = replica
max_wal_size = 16GB
min_wal_size = 4GB
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min

# Параллельные запросы
max_parallel_workers_per_gather = 8
max_parallel_workers = 16
max_parallel_maintenance_workers = 4
parallel_tuple_cost = 0.01
parallel_setup_cost = 100

# Планировщик
random_page_cost = 1.1  # NVMe: почти как sequential
effective_io_concurrency = 200
seq_page_cost = 1.0

# Соединения
max_connections = 200

# Автовакуум — агрессивный для таблиц вакансий
autovacuum_max_workers = 6
autovacuum_naptime = 15s
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.01

Результат: запрос поиска вакансий по 15 фильтрам (город, зарплата, опыт, график, удалёнка, категория) — 8 мс при полной таблице в 400 000 записей. На старой конфигурации тот же запрос выполнялся 120 мс.

Nginx + Redis: кэширование на уровне запросов

80% запросов к джоб-борду — это просмотры страниц вакансий и результатов поиска. Эти страницы меняются раз в 5-15 минут (обновление статусов вакансий). Значит, 80% запросов можно отдавать из кэша.

# nginx.conf — кэширование через Redis
upstream backend {
    server 127.0.0.1:8000;
    keepalive 64;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=page_cache:256m
                 max_size=10g inactive=60m use_temp_path=off;

server {
    listen 443 ssl http2;
    server_name rabotavsem.ru;

    # Статика — отдаём напрямую
    location /static/ {
        root /var/www;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Страницы вакансий — кэш 10 минут
    location /vacancy/ {
        proxy_cache page_cache;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_key "$scheme$request_method$host$request_uri";
        proxy_cache_use_stale error timeout updating http_500 http_502;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_pass http://backend;
    }

    # API и личный кабинет — без кэша
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Redis для session-кэша и горячих данных:

# redis.conf
maxmemory 16gb
maxmemory-policy allkeys-lfu

# Сессии пользователей (TTL 30 минут)
# Топ вакансий по городам (TTL 5 минут)
# Результаты поиска (TTL 3 минуты)

С кэшированием до бэкенда доходит только 15-20% запросов. Остальные отдаёт Nginx из кэша за 1-2 мс.

HAProxy и отказоустойчивость без сложных кластеров

Два сервера — это active-passive конфигурация с HAProxy и keepalived:

# haproxy.cfg — балансировка между двумя серверами
global
    maxconn 50000
    log /dev/log local0
    stats socket /run/haproxy/admin.sock mode 660

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s
    option httpchk GET /health
    option forwardfor

frontend https
    bind *:443 ssl crt /etc/ssl/rabotavsem.pem alpn h2,http/1.1
    default_backend web

backend web
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ rabotavsem.ru
    server srv1 10.0.0.1:8000 check inter 3s fall 3 rise 2 weight 100
    server srv2 10.0.0.2:8000 check inter 3s fall 3 rise 2 weight 100 backup

Keepalived для автоматического переключения VIP:

# keepalived.conf — на сервере 1 (MASTER)
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass s3cr3t
    }
    virtual_ipaddress {
        185.x.x.x/32
    }
    track_script {
        chk_haproxy
    }
}

vrrp_script chk_haproxy {
    script "/usr/bin/pgrep haproxy"
    interval 2
    weight 2
}

При падении первого сервера VIP переезжает на второй за 3 секунды. PostgreSQL реплицируется синхронно через streaming replication. Пользователь не замечает переключения.

CDN и оптимизация фронтенда

Минимум серверов не означает игнорирование CDN. Статика — это 70% трафика, и отдавать её из одного ДЦ — нерационально.

  • CDN для статики: CSS, JS, изображения вакансий и логотипы компаний раздаются через CDN. Серверы «РаботаВсем» генерируют только HTML.
  • Сжатие: Brotli для HTML/CSS/JS (экономия 20-30% по сравнению с gzip), WebP для изображений.
  • HTTP/2 Server Push: критичные CSS и JS отправляются вместе со страницей, убирая один round-trip.
# nginx — Brotli и оптимизация
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript application/json;

# Gzip как fallback
gzip on;
gzip_comp_level 5;
gzip_types text/html text/css application/javascript application/json;
gzip_min_length 256;

# Оптимизация TCP
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;

Мониторинг: проще, но надёжнее

С двумя серверами мониторинг радикально упрощается. Мы развернули Prometheus + Grafana на том же сервере (ресурсов хватает с запасом) и настроили 5 критичных алертов:

# prometheus/alerts.yml
groups:
  - name: critical
    rules:
      - alert: HighCPU
        expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "CPU выше 80% более 5 минут"

      - alert: DiskSpaceLow
        expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
        for: 1m
        labels:
          severity: critical

      - alert: PostgresReplicationLag
        expr: pg_replication_lag_seconds > 5
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Репликация PostgreSQL отстаёт более 5 секунд"

      - alert: HighResponseTime
        expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
        for: 3m
        labels:
          severity: warning

      - alert: BackendDown
        expr: up{job="backend"} == 0
        for: 30s
        labels:
          severity: critical

Алерты уходят в Telegram-бот дежурного. Дашборд Grafana — один экран с 8 графиками: RPS, время ответа, CPU, RAM, диск, PostgreSQL connections, cache hit rate, репликация.

Результаты и выводы

Через месяц после миграции на двухсерверную архитектуру:

МетрикаДо (14 серверов)После (2 сервера)
Среднее время ответа180 мс28 мс
P99 время ответа1200 мс95 мс
Утилизация CPU5-10%15-25%
Ежемесячные расходы480 000 ₽120 000 ₽
Точек отказа142
Время деплоя25 минут (14 серверов)3 минуты

Ключевые принципы антимикросервисного подхода, которые мы вынесли из этого проекта:

  • Считайте нагрузку в цифрах, а не в ощущениях. 200 RPS — это не «высокая нагрузка», это один сервер с запасом.
  • Вертикальное масштабирование дешевле. Сервер с 256 GB RAM стоит меньше, чем 8 серверов по 32 GB, и управлять им проще.
  • Кэширование решает 80% проблем. Redis + Nginx proxy cache убирают основную нагрузку с бэкенда.
  • Простота = надёжность. Чем меньше компонентов, тем меньше может сломаться. Меньше серверов — меньше сетевых проблем, меньше патчей, меньше мониторинга.

Микросервисы нужны, когда у вас 50 разработчиков и 200 RPS на каждый сервис. Для большинства проектов монолит на мощном железе — правильный выбор. Мы в itfresh.ru помогаем подобрать оптимальную архитектуру под реальную нагрузку, а не модные тренды.

Часто задаваемые вопросы

Нет, если правильно настроена репликация и автоматическое переключение. Два сервера с keepalived и PostgreSQL streaming replication дают SLA 99.95%. На практике это надёжнее, чем 14 серверов, где каждый может стать причиной каскадного сбоя.
Когда один сервер не справляется с нагрузкой даже при максимальной оптимизации. Для типичного веб-приложения это 5000-10000 RPS. Если ваша нагрузка ниже — вертикальное масштабирование будет дешевле и надёжнее.
Серверные NVMe с защитой от потери данных при отключении питания (power-loss protection): Samsung PM9A3, Intel D7-P5510, Micron 7450 PRO. Бытовые NVMe (Samsung 990 Pro, WD Black) не подходят — они могут потерять данные из кэша записи при внезапном отключении.
ECC RAM корректирует одиночные битовые ошибки, которые в обычной памяти приводят к порче данных. При 256 GB RAM вероятность битовой ошибки за год — около 30%. Без ECC ваша база данных может молча повредиться. Для серверов с большим объёмом памяти ECC обязателен.
Да, если ваш интернет-магазин обрабатывает до 3000-5000 заказов в день. Каталог товаров, корзина, поиск — всё отлично работает на двух серверах с PostgreSQL и Redis. Единственное ограничение — если у вас тяжёлая обработка изображений или видео, может потребоваться отдельный сервер для медиа.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#высокая нагрузка#вертикальное масштабирование#nginx#redis#postgresql#haproxy#антимикросервисы#оптимизация производительности
Комментарии 0

Оставить комментарий

загрузка...