← Назад к вопросам

Как масштабируется монолит

1.3 Junior🔥 141 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Масштабирование монолита: стратегия и практика

Это один из самых сложных и интересных вопросов в архитектуре. В своей практике я сталкивался с масштабированием монолита, когда продукт быстро рос, а архитектура не была к этому готова.

Вертикальное масштабирование (Scale Up)

Первый шаг — это обычно самый простой. Просто добавляешь больше ресурсов серверу:

  • Больше CPU
  • Больше памяти (JVM heap)
  • Более быстрый диск (SSD вместо HDD)
# JVM параметры для большого heap
java -Xms4G -Xmx8G -XX:+UseG1GC application.jar

Это работает до определённого потолка (~200 GB RAM, десятки CPU). После этого стоимость растёт экспоненциально, а надёжность падает (Single Point of Failure).

Горизонтальное масштабирование (Scale Out)

Load Balancer — критически важен. Я использовал Nginx, HAProxy для распределения трафика:

upstream java_backend {
    server app1.example.com:8080;
    server app2.example.com:8080;
    server app3.example.com:8080;
    keepalive 32;
}

server {
    listen 80;
    location / {
        proxy_pass http://java_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Sticky Sessions — при распределении запросов нужно учитывать сессии пользователя. Стратегии:

  1. IP-Based Routing — маршрутизация по IP клиента (простая, но хрупкая)
  2. Cookie-Based — сохраняешь информацию о сессии в куке
  3. Session Store — вынесение сессий в Redis/Memcached

Я предпочитал третий подход:

@Configuration
@EnableSpringSession
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class SessionConfig { }

Это позволяет любому инстансу приложения обслужить пользователя, так как сессия в Redis.

Проблемы монолита при масштабировании

Database Bottleneck — часто монолит упирается в БД раньше, чем в приложение.

// Типичное узкое место
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userRepository.findById(id).orElseThrow();
}

Решения:

  • Caching (Redis) — кешировать часто читаемые данные
  • Read Replicas — вынести читающие запросы на отдельные реплики БД
  • Database Sharding — разбить данные по нескольким БД
@Service
public class UserService {
    @Autowired
    private CacheManager cacheManager;
    
    @Cacheable(value = "users", key = "#id")
    public User getUser(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void updateUser(Long id, User user) {
        userRepository.save(user);
    }
}

Stateful Components — монолит часто содержит компоненты, которые хранят состояние в памяти (кеши, очереди, таймеры). При масштабировании это вызывает проблемы.

Решение — вынести состояние:

// Вместо этого
private Map<Long, User> userCache = new ConcurrentHashMap<>();

// Используй Redis
@Autowired
private RedisTemplate<String, User> redisTemplate;

public void cacheUser(Long id, User user) {
    redisTemplate.opsForValue().set("user:" + id, user, Duration.ofHours(1));
}

Long Operations — если одна операция занимает много времени, она блокирует тред. При масштабировании это критично.

Решение — асинхронность:

@Service
public class OrderService {
    @Async
    public CompletableFuture<Order> processOrder(Order order) {
        // Долгая операция
        Thread.sleep(5000);
        return CompletableFuture.completedFuture(order);
    }
}

Connection Pooling

Database Connection Pool — критичен при множестве инстансов. Я использовал HikariCP:

spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

Каждый инстанс может иметь до 20 соединений. При 5 инстансах — 100 соединений к БД. Нужно планировать.

Distributed Tracing

При множестве инстансов отладка становится ночным кошмаром. Я внедрял Spring Cloud Sleuth + Zipkin для отслеживания запросов:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

Теперь каждый запрос имеет trace ID, и ты видишь всю цепочку вызовов в Zipkin UI.

Kubernetes для масштабирования

В современных проектах я использовал Kubernetes для горизонтального масштабирования:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: java-app
  template:
    metadata:
      labels:
        app: java-app
    spec:
      containers:
      - name: java-app
        image: my-java-app:latest
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

Кубернетес автоматически создаёт поды, распределяет их по нодам, перезагружает упавшие.

Metrics & Monitoring

При масштабировании нужен visibility. Я использовал Micrometer для метрик:

@Service
public class UserService {
    private final MeterRegistry meterRegistry;
    
    @Autowired
    public UserService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    public User getUser(Long id) {
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            return userRepository.findById(id).orElseThrow();
        } finally {
            sample.stop(Timer.builder("user.get")
                .publishPercentiles(0.5, 0.95, 0.99)
                .register(meterRegistry));
        }
    }
}

Метрики отправляются в Prometheus, визуализируются в Grafana.

Когда уходить от монолита

Если монолит масштабируется плохо, это сигнал к архитектурным изменениям:

  • Разные компоненты имеют разные требования к масштабированию
  • Развертывание становится рискованным (один баг — всё падает)
  • Команда растёт, но работает неэффективно

Время переходить на микросервисы или модульный монолит.

Итог

Масштабирование монолита требует комплексного подхода: вертикальное масштабирование, горизонтальное распределение, оптимизация БД, кеширование, мониторинг. Это возможно до определённого масштаба, но растущие сложность и стоимость в итоге толкают к архитектурному переосмыслению.