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

Какие знаешь проблемы БД в режиме кластера?

2.8 Senior🔥 141 комментариев
#Docker, Kubernetes и DevOps#Базы данных и SQL

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

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

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

Проблемы баз данных в режиме кластера

Когда база данных работает в режиме кластера (несколько узлов с репликацией), возникает целый класс новых проблем, связанных с консистентностью данных, синхронизацией и обработкой сбоев. Это критические вызовы для масштабируемых систем.

Проблема CAP Theorem

Теорема CAP (Consistency, Availability, Partition tolerance) гласит, что нельзя одновременно гарантировать все три свойства:

Consistency (Консистентность)
  ↓
  Все узлы видят одинаковые данные в один момент времени
  
Availability (Доступность)
  ↓
  Система всегда отвечает на запросы
  
Partition Tolerance (Устойчивость к разделению)
  ↓
  Система работает даже при разрыве связи между узлами

В кластере приходится выбирать:

  • CP системы (PostgreSQL с масштабированием) — консистентность + устойчивость, жертвуем доступностью
  • AP системы (распределённые NoSQL) — доступность + устойчивость, жертвуем консистентностью

Race Conditions и конфликты одновременного доступа

Проблема: Lost Update

// Два клиента одновременно обновляют счёт пользователя
// Поток 1: SELECT balance = 100; balance += 10; UPDATE balance = 110
// Поток 2: SELECT balance = 100; balance += 20; UPDATE balance = 120
// Результат: 120 вместо 130 (потеря 10 единиц)

String sql = "UPDATE users SET balance = balance + ? WHERE id = ?";
// ❌ Уязвиво без синхронизации

Решение: SELECT FOR UPDATE

BEGIN;
SELECT balance FROM users WHERE id = 1 FOR UPDATE;
-- Получили эксклюзивную блокировку
UPDATE users SET balance = balance + 10 WHERE id = 1;
COMMIT;

Проблема: Dirty Reads и Phantom Reads

Уровни изоляции транзакций

УровеньDirty ReadsNon-RepeatablePhantom ReadsЦена производительности
READ UNCOMMITTEDНизкая
READ COMMITTEDСредняя
REPEATABLE READВысокая
SERIALIZABLEОчень высокая
// Phantom read — новая строка появляется внутри транзакции
Transaction 1: SELECT * FROM users WHERE age > 18; // 100 пользователей
Transaction 2: INSERT INTO users VALUES (19); -- Новый пользователь
Transaction 1: SELECT * FROM users WHERE age > 18; // 101 пользователь!

// Решение: SERIALIZABLE уровень, но это очень медленно
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

Проблема: Replication Lag

Вторичные узлы отстают от основного при высокой нагрузке:

// Основной узел (Primary)
insertUserRecord(user); // Записали в Primary

// Вторичный узел (Replica) отстаёт на 100мс
// Если клиент сразу читает из Replica
User readUser = replicaConnection.getUser(id); // null!

// Классическая проблема: write-then-read
insertOrder(order);
Order read = readFromReplica(order.id); // Может быть null!

Решение: Sticky Connections

// После write операции читаем из того же узла
Connection primaryConn = getConnection(PRIMARY);
primaryConn.insertOrder(order);

// Читаем из того же Primary на время репликации
Order order = primaryConn.getOrder(order.id);

Проблема: Cascading Failures

Отказ одного узла вызывает цепную реакцию:

Node 1 (Primary) отказывает
    ↓
Узлы пытаются переизбрать новый Primary
    ↓
Все пытаются получить блокировку одновременно
    ↓
Описание оставшихся узлов (thundering herd)
    ↓
Вся система деградирует

Решение: Circuit Breaker Pattern

public class DatabaseCircuitBreaker {
    private static final int FAILURE_THRESHOLD = 5;
    private int failureCount = 0;
    private long lastFailureTime = 0;
    
    public <T> T execute(Callable<T> operation) throws Exception {
        // Если circuit open (открыта), не пытаемся подключиться
        if (isOpen()) {
            throw new CircuitBreakerOpenException();
        }
        
        try {
            T result = operation.call();
            onSuccess();
            return result;
        } catch (Exception e) {
            onFailure();
            throw e;
        }
    }
    
    private boolean isOpen() {
        // Half-open после timeout
        return failureCount >= FAILURE_THRESHOLD && 
               System.currentTimeMillis() - lastFailureTime < 30_000;
    }
}

Проблема: Split Brain

Когда сеть разделяется и возникают два Primary узла:

Сеть разрывается
    ↓
Узел A считает, что B мёртв, избирает себя Primary
Узел B считает, что A мёртв, избирает себя Primary
    ↓
Возникают два Primary (Split Brain)
    ↓
Данные расходятся, консистентность нарушена

Решение: Quorum-based elections

Есть 5 узлов. Primary может быть избран только если 
элекция поддержана >= 3 узлов (мажоритет).

При разрыве на 3-2:
  - 3 узла могут избрать новый Primary (имеют мажоритет)
  - 2 узла не могут (потеряли мажоритет)

Проблема: Network Partitions

Между узлами теряется связь:

// Таймауты должны быть достаточно большими, но не бесконечными
int readTimeout = 5_000;    // 5 сек — обнаружить проблему
int writeTimeout = 10_000;  // 10 сек — для очень медленных операций

// Хардкодные таймауты — причина многих проблем в кластере
// ❌ Без таймаутов: зависание навечно
// ✅ С таймаутами: контролируемая деградация

Проблема: Transaction Consistency in Distributed Systems

Two-Phase Commit (2PC) — медленно, но консистентно

Phase 1 (PREPARE): Спросить все узлы, готовы ли выполнить?
Phase 2 (COMMIT):  Все узлы выполняют коммит

-- Проблема: если один узел упадёт на Phase 2, неконсистентность

Eventual Consistency — быстро, но консистентность постепенная

// Запись в Primary успешна сразу
insertRecord(); // Success

// Репликация в Replica произойдёт позже
// На время репликации данные не синхронизированы

Лучшие практики для кластерных БД

  1. Используй SERIALIZABLE только где действительно нужно, остальное READ COMMITTED
  2. SELECT FOR UPDATE для критичных операций (платежи, резервирование)
  3. Sticky connections после write операций
  4. Circuit breakers для обработки сбоев узлов
  5. Quorum-based выборы для избежания split brain
  6. Адекватные таймауты для сетевых операций
  7. Мониторь replication lag — это первый признак проблем
  8. Используй READ ONLY REPLICAS для чтения, Primary только для write
  9. Тестируй chaos scenarios — отключения узлов, network partition
  10. Не полагайся на консистентность — обработай race conditions в приложении
Какие знаешь проблемы БД в режиме кластера? | PrepBro