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

Что такое CAP теорема?

2.0 Middle🔥 191 комментариев
#Базы данных и SQL

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

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

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

CAP теорема: основы и практическое применение

CAP теорема (также известная как теорема Брюэра) — это фундаментальный принцип в распределённых системах, который определяет ограничения при проектировании баз данных и микросервисов.

Что такое CAP?

CAP — это акроним трёх гарантий:

1. Consistency (Согласованность)

Все узлы системы видят одни и те же данные в один момент времени.

// Пример нарушения консистентности:
// Клиент 1: пишет user.balance = 1000
// Клиент 2: читает user.balance = 500 (не обновленное значение)

// ✅ Согласованная система:
public class ConsistentUserService {
    private final DataSource dataSource;
    
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void transfer(UUID fromUser, UUID toUser, BigDecimal amount) {
        // Если записываем, ВСЕ читают актуальные данные
        User from = userRepository.findByIdForUpdate(fromUser);
        User to = userRepository.findByIdForUpdate(toUser);
        
        from.decreaseBalance(amount);
        to.increaseBalance(amount);
        
        userRepository.save(from);
        userRepository.save(to);
    }
}

2. Availability (Доступность)

Система всегда доступна для чтения и записи, на каждый запрос есть успешный ответ.

// Пример высокой доступности:
public class AvailableUserService {
    private final UserRepository primaryReplica;
    private final UserRepository secondaryReplica1;
    private final UserRepository secondaryReplica2;
    
    public User getUser(UUID id) {
        try {
            return primaryReplica.findById(id);
        } catch (Exception e) {
            // Primary не отвечает — берём из secondary
            try {
                return secondaryReplica1.findById(id);
            } catch (Exception e2) {
                return secondaryReplica2.findById(id);
            }
        }
    }
    
    public void saveUser(User user) {
        // Записываем на все реплики асинхронно
        CompletableFuture.runAsync(() -> primaryReplica.save(user));
        CompletableFuture.runAsync(() -> secondaryReplica1.save(user));
        CompletableFuture.runAsync(() -> secondaryReplica2.save(user));
    }
}

3. Partition Tolerance (Устойчивость к разделению сети)

Система продолжает работать даже если часть узлов потеряет связь с другой частью (сетевой раздел).

// Пример partition tolerance:
public class PartitionTolerantService {
    private final Node primaryDataCenter;
    private final Node backupDataCenter;
    
    public void handleNetworkPartition() {
        // Если сеть разделена: primary в европе, backup в азии
        // Backup может продолжить обслуживать запросы
        // Но данные могут рассинхронизироваться
        
        if (!primaryDataCenter.isReachable()) {
            logger.warn("Network partition detected");
            // Switchover to backup
            activateBackupDataCenter();
        }
    }
}

Теорема Брюэра

Можешь выбрать максимум ДВА из трёх свойств:

     C (Consistency) — все данные в sync
         /  \
        /    \
       /      \
   CP /        \ CA
     /          \
    /____________\
       P (Partition Tolerance)
       A (Availability)

Три стратегии

CP системы (Consistency + Partition Tolerance)

Жертвуем доступностью — при разделении сети система может стать недоступной, но данные всегда консистентны.

Примеры: PostgreSQL (синхронная репликация), ZooKeeper, HBase, etcd

// CP подход: PostgreSQL с синхронной репликацией
public class CPUserService {
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void saveUser(User user) {
        // Данные пишутся сначала в primary
        userRepository.save(user);
        // Потом синхронно на все replicas (ждём ответа)
        // Если replica не отвечает — транзакция откатывается
        
        // ПЛЮСЫ: 100% консистентность
        // МИНУСЫ: если replica упала, запись не пройдёт
    }
}

AP системы (Availability + Partition Tolerance)

Жертвуем консистентностью — при разделении сети разные части могут иметь разные данные, но система всегда отвечает.

Примеры: DynamoDB, Cassandra, MongoDB (по умолчанию), Redis

// AP подход: Cassandra
public class APUserService {
    @Autowired
    private CassandraOperations cassandra;
    
    public void saveUser(User user) {
        // Пишем с eventual consistency
        cassandra.insert(user);
        // Система сразу говорит "ОК"
        // Но данные распространяются на все узлы с задержкой
        
        // ПЛЮСЫ: высокая доступность, масштабируемость
        // МИНУСЫ: может быть временная несогласованность
    }
}

CA системы (Consistency + Availability)

Жертвуем partition tolerance — может работать только если сеть идеальна (что невозможно в реальности).

Примеры: Практически все монолитные БД (Oracle, MySQL), которые работают на одной машине

// CA подход: монолитная БД на одном сервере
public class CAUserService {
    @Transactional
    public void saveUser(User user) {
        userRepository.save(user); // Все данные в sync, всегда доступно
        // НО: если сервер упал — всё недоступно
    }
}

Практическое применение в архитектуре

Сценарий 1: E-commerce платформа

// Данные о товарах: AP (availability важнее)
// Могут быть слегка устаревшими, но система всегда доступна
public class ProductService {
    @Autowired
    private MongoDB mongoDb; // AP база
    
    public Product getProduct(String id) {
        return mongoDb.findById(id); // Может вернуть слегка старое значение
    }
}

// Платежи: CP (консистентность критична)
// Деньги никогда не теряются, даже если медленнее
public class PaymentService {
    @Autowired
    private PostgreSQL postgres; // CP база
    
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void processPayment(Payment payment) {
        // Гарантированная консистентность
    }
}

Сценарий 2: Микросервисная архитектура

// Если два сервиса теряют связь (partition):

// ❌ Ждём синхронизации (теряем доступность):
public void orderPayment(Order order) {
    // Синхронный вызов payment service
    PaymentResponse response = paymentService.charge(order.getAmount());
    // Если payment недоступен — весь заказ падает
}

// ✅ Используем очередь (жертвуем временной консистентностью):
public void orderPayment(Order order) {
    // Асинхронно через очередь (RabbitMQ, Kafka)
    eventBus.publish(new OrderCreatedEvent(order));
    // Система доступна, даже если payment service недоступен
    // Платёж обработается позже (eventual consistency)
}

Вывод

Современный подход: нет универсального выбора. В одной системе могут быть разные части:

  • CP для критичных данных (платежи, транзакции)
  • AP для некритичных (кэш, analytics, рекомендации)
  • CA только если это монолит на одном сервере

Ключевой навык: понимать trade-off и выбирать правильную архитектуру для каждой части системы.

Что такое CAP теорема? | PrepBro