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

Какие знаешь типы масштабирования баз данных?

1.8 Middle🔥 141 комментариев
#REST API и микросервисы#Базы данных и SQL

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

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

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

Типы масштабирования баз данных

По мере роста объёма данных и количества пользователей нужно масштабировать БД. Существует несколько стратегий масштабирования, каждая с плюсами и минусами.

1. Vertical Scaling (вертикальное масштабирование)

Увеличение мощности одного сервера: больше CPU, памяти, диска:

Шаг 1: Сервер 4 ядра, 8 GB RAM
     ↓
Шаг 2: Сервер 8 ядер, 32 GB RAM
     ↓
Шаг 3: Сервер 16 ядер, 64 GB RAM

Преимущества:

  • Просто реализовать
  • Нет изменения кода
  • ACID транзакции сохраняются

Проблемы:

  • Дороговизна (экспоненциальный рост стоимости)
  • Есть предел (физический лимит сервера)
  • Downtime при обновлении
  • Single point of failure

Когда использовать: маленькие и средние приложения.

2. Horizontal Scaling (горизонтальное масштабирование)

Добавление новых серверов БД и распределение данных:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Shard 1    │     │  Shard 2    │     │  Shard 3    │
│ User 1-333  │     │ User 334-666│     │ User 667-999│
└─────────────┘     └─────────────┘     └─────────────┘
     ↑                    ↑                    ↑
     └────────┬───────────┬────────┬──────────┘
              │           │        │
         ┌────────────────────────────┐
         │   Load Balancer / Router   │
         └────────────────────────────┘
              ↑
         Приложение

Типы:

3. Sharding (разделение по ключу)

Распределение данных по разным серверам на основе ключа:

// Функция шардирования
public class ShardRouter {
    private final int NUM_SHARDS = 3;
    
    public int getShard(Long userId) {
        return (int) (userId % NUM_SHARDS);  // 0, 1 или 2
    }
    
    public Database getShardDatabase(Long userId) {
        int shard = getShard(userId);
        switch (shard) {
            case 0: return db1;  // Shard 1
            case 1: return db2;  // Shard 2
            case 2: return db3;  // Shard 3
            default: throw new IllegalArgumentException();
        }
    }
}

// SQL запрос
User user = shardRouter.getShardDatabase(123).query(
    "SELECT * FROM users WHERE id = ?",
    123
);

Преимущества:

  • Линейное масштабирование
  • Каждый shard содержит часть данных
  • Высокая производительность

Проблемы:

  • Сложность
  • Cross-shard запросы медленные (JOIN между шардами)
  • Rebalancing сложный при добавлении нового шарда
  • Hotspot проблема (какой-то шард получает больше нагрузки)

4. Replication (репликация)

Копирование данных на несколько серверов для надёжности и чтения:

┌─────────────────────────────────────┐
│      Primary (Master) DB            │
│  Принимает write запросы            │
│  user_id=1, name="Alice"            │
│  Отправляет изменения на replicas   │
└────────────┬────────────────────────┘
             │ replication stream
        ┌────┴─────────┬──────────────┐
        ↓              ↓              ↓
  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
  │Replica 1 (RO)│ │Replica 2 (RO)│ │Replica 3 (RO)│
  │user_id=1... │ │user_id=1... │ │user_id=1... │
  │(Read-only)  │ │(Read-only)  │ │(Read-only)  │
  └─────────────┘ └─────────────┘ └─────────────┘

В Java с Read Replicas:

@Configuration
public class DatabaseConfig {
    
    @Bean
    public DataSource primaryDataSource() {  // Master
        // host: db-master.example.com
        return DataSourceBuilder.create()
            .url("jdbc:postgresql://db-master:5432/mydb")
            .build();
    }
    
    @Bean
    public DataSource replicaDataSource() {  // Read replica
        // host: db-replica-1.example.com
        return DataSourceBuilder.create()
            .url("jdbc:postgresql://db-replica-1:5432/mydb")
            .build();
    }
}

@Service
public class UserService {
    
    @Autowired
    private DataSource primaryDataSource;  // Для write
    
    @Autowired
    private DataSource replicaDataSource;  // Для read
    
    public void createUser(User user) {
        // Write на master
        JdbcTemplate write = new JdbcTemplate(primaryDataSource);
        write.update("INSERT INTO users ...", ...);
    }
    
    public User getUserById(Long id) {
        // Read с replica
        JdbcTemplate read = new JdbcTemplate(replicaDataSource);
        return read.query("SELECT * FROM users WHERE id = ?", ...);
    }
}

Преимущества:

  • Высокая доступность (если master упадёт)
  • Масштабирование чтения
  • Можно читать синхронно

Проблемы:

  • Replication lag: replica отстаёт на несколько ms/sec
  • Consistency issues: читаем старые данные после write
  • Storage дублируется (нужно больше диска)

5. Multi-Master Replication

Несколько master'ов, все могут писать:

┌──────────────┐         ┌──────────────┐
│  Master 1    │ ←---→  │  Master 2    │
│ (write + read)│ sync  │ (write + read)│
└──────────────┘         └──────────────┘
     ↑                          ↑
  write                      write
     │                          │
App Instance 1          App Instance 2

Проблемы:

  • Конфликты: если оба master'а пишут в одну запись одновременно
  • Complexity: нужна сложная логика разрешения конфликтов
  • Consistency: не гарантирует ACID

Редко используется в SQL БД.

6. Partitioning (разделение на партиции)

Разделение одной таблицы на части по диапазону значений:

-- Разделяем таблицу заказов по дате
CREATE TABLE orders (
    id BIGINT,
    user_id BIGINT,
    created_at TIMESTAMPTZ,
    amount DECIMAL
) PARTITION BY RANGE (created_at);

-- Партиции
CREATE TABLE orders_2024_q1 PARTITION OF orders
    FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');

CREATE TABLE orders_2024_q2 PARTITION OF orders
    FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');

-- Со своим индексом
CREATE INDEX idx_orders_2024_q1 ON orders_2024_q1(user_id);
CREATE INDEX idx_orders_2024_q2 ON orders_2024_q2(user_id);

Преимущества:

  • Быстрые запросы к старым данным (partition pruning)
  • Проще архивирование
  • Улучшена производительность индексов

Проблемы:

  • Сложнее управлять
  • Cross-partition запросы медленнее

7. Read-Write Splitting

Направление write на master, read на replica:

public class RoutingDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
            ? "replica"   // Читаем с replica
            : "primary";  // Пишем на primary
    }
}

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource routingDataSource(
            @Qualifier("primaryDataSource") DataSource primary,
            @Qualifier("replicaDataSource") DataSource replica) {
        
        RoutingDataSource routingDataSource = new RoutingDataSource();
        
        Map<Object, Object> targets = new HashMap<>();
        targets.put("primary", primary);
        targets.put("replica", replica);
        
        routingDataSource.setTargetDataSources(targets);
        routingDataSource.setDefaultTargetDataSource(primary);
        
        return routingDataSource;
    }
}

// Использование
@Service
public class UserService {
    
    @Transactional(readOnly = false)  // → primary
    public void createUser(User user) {
        userRepository.save(user);
    }
    
    @Transactional(readOnly = true)   // → replica
    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
}

8. NoSQL для масштабирования

Перемещение определённых данных в NoSQL (MongoDB, Cassandra):

┌──────────────────┐
│ PostgreSQL       │  ← Структурированные данные
│ (Users, Orders)  │
└──────────────────┘

┌──────────────────┐
│ MongoDB          │  ← JSON documents (logs, events)
│ (Event logs)     │
└──────────────────┘

┌──────────────────┐
│ Cassandra        │  ← Time series (metrics)
│ (Metrics, Logs)  │
└──────────────────┘

┌──────────────────┐
│ Redis            │  ← Cache (sessions, real-time)
│ (Cache)          │
└──────────────────┘

NoSQL преимущества:

  • Горизонтальное масштабирование из коробки
  • Высокая пропускная способность
  • Гибкая схема

Проблемы:

  • Нет ACID транзакций
  • Eventual consistency
  • Слабый query язык

9. Database Federation

Разделение данных по доменам (каждый домен свою БД):

┌─────────────────────┐
│  User Service       │
│  PostgreSQL         │
│  (users, profiles)  │
└─────────────────────┘

┌─────────────────────┐
│  Order Service      │  ← Microservices
│  PostgreSQL         │     каждый со своей БД
│  (orders, items)    │
└─────────────────────┘

┌─────────────────────┐
│  Payment Service    │
│  PostgreSQL         │
│  (transactions)     │
└─────────────────────┘

Каждый микросервис управляет своей БД.

10. Caching Layer

Добавляем кэш перед БД:

┌─────────────┐
│  Application│
└──────┬──────┘
       │
       ↓
┌─────────────────────────┐
│  Redis / Memcached      │  ← Cache L1 (in-memory)
│  (hot data, sessions)   │
└──────┬──────────────────┘
       │ (на cache miss)
       ↓
┌─────────────────────────┐
│  PostgreSQL + Read      │  ← Primary storage
│  Replicas               │
└─────────────────────────┘
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RedisTemplate<String, User> redisTemplate;
    
    public User getUserById(Long id) {
        String key = "user:" + id;
        
        // Проверяем кэш
        User cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return cached;  // Cache hit
        }
        
        // Cache miss → идём в БД
        User user = userRepository.findById(id)
            .orElseThrow();
        
        // Сохраняем в кэш на 1 час
        redisTemplate.opsForValue().set(key, user, Duration.ofHours(1));
        
        return user;
    }
}

Стратегия выбора масштабирования

Время:        Пользователи:      Стратегия:
────────────────────────────────────────────────
0-3 месяца    < 10k              Vertical scaling
3-12 месяцев  10k - 100k         Replication + cache
1-2 года      100k - 1M          Sharding
2+ года       1M+                Federation + Sharding

Best Practices

  1. Начни с vertical scaling (дешевле и проще)
  2. Добавь caching (Redis для горячих данных)
  3. Replicate reads (master-slave для read-heavy workloads)
  4. Sharding (только когда data не влезает на один сервер)
  5. Federation/Microservices (когда разные домены независимы)
  6. NoSQL (для времени/логов когда не нужна strict consistency)