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

Как реализовано свойство partition tolerance в MongoDB?

2.7 Senior🔥 71 комментариев
#Базы данных (NoSQL)

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

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

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

Partition Tolerance в MongoDB и CAP Theorem

Partition Tolerance — это способность системы продолжать работать при разделении сети (network partition), когда узлы не могут общаться друг с другом. Разберём как это реализовано в MongoDB.

1. CAP Theorem и выбор MongoDB

CAP Theorem гласит, что распределённая система может гарантировать только 2 из 3 свойств:

Consistency   (Согласованность) — все узлы видят одни и те же данные
Availability  (Доступность)     — система работает даже если какие-то узлы упали
Partition     (Толерантность)   — система работает при разделении сети

MongoDB выбирает CP (Consistency + Partition) в режиме по умолчанию:

# MongoDB по умолчанию использует сильную согласованность
# и толерантность к разделениям сети
# но может потерять доступность временно

from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError

try:
    client = MongoClient(
        "mongodb://replica-set/db?replicaSet=rs0",
        serverSelectionTimeoutMS=5000
    )
    result = client.test_db.test_collection.find_one()
except ServerSelectionTimeoutError:
    # При разделении сети MongoDB может стать недоступным
    print("Network partition: cannot reach any replica")

2. Replica Sets — основа Partition Tolerance

Repica Set — это группа MongoDB узлов, которые реплицируют данные друг к другу:

# Стандартная конфигурация Replica Set
replica_set_config = {
    "_id": "rs0",
    "members": [
        {"_id": 0, "host": "primary:27017"},
        {"_id": 1, "host": "secondary1:27017"},
        {"_id": 2, "host": "secondary2:27017"},
    ],
    "settings": {
        "electionTimeoutMillis": 10000,  # Время для выборов
    }
}

# Нормальное состояние:
# PRIMARY          SECONDARY        SECONDARY
#   ↓ writes         ↑ reads          ↑ reads
#   ↓ replicates     ←←←←←←←          ←←←←←←←
#
# При разделении PRIMARY ↔ SECONDARIES:
#   PRIMARY              SECONDARY  SECONDARY
#   (можно писать)       (можно писать?)

3. Election и Quorum

Если PRIMARY упадёт или произойдёт разделение сети, MongoDB проводит Election (выборы) нового PRIMARY:

# Quorum — минимальное количество узлов для принятия решения
# Для 3-узловой replica set: quorum = 2

quorum_needed = (replica_set_size // 2) + 1
# Для 3 узлов: quorum = 2
# Для 5 узлов: quorum = 3
# Для 7 узлов: quorum = 4

# СЦЕНАРИЙ 1: Нормальное разделение
#
# PARTITION 1         PARTITION 2
# Primary (1)         Secondary1 (1)
# Secondary2 (1)      
# Total: 2 узла      Total: 1 узел
# Quorum: 2 ✓        Quorum: 2 ✗
#
# PARTITION 1 может писать (есть quorum)
# PARTITION 2 НЕ может писать (нет quorum)
# PARTITION 2 переходит в режим "читаемый вторичный"

print("PARTITION 1: Can write (has quorum)")
print("PARTITION 2: Cannot write (no quorum)")
print("Result: Consistency maintained!")

4. Write Concern и Replication

Write Concern определяет сколько узлов должны подтвердить запись:

from pymongo import MongoClient
from pymongo.errors import WriteError

client = MongoClient("mongodb://replica-set/db?replicaSet=rs0")
db = client.test_db
collection = db.test_collection

# Write Concern: ACKNOWLEDGED (w=1)
# Только PRIMARY должен подтвердить
try:
    result = collection.insert_one(
        {"name": "Alice"},
        write_concern={"w": 1}  # PRIMARY only
    )
    print(f"Inserted: {result.inserted_id}")
except WriteError as e:
    print(f"Write error: {e}")

# Write Concern: MAJORITY (w="majority")
# PRIMARY + большинство SECONDARIES должны подтвердить
result = collection.insert_one(
    {"name": "Bob"},
    write_concern={"w": "majority", "wtimeoutMS": 5000}
    # Ждём подтверждения от большинства
    # Если разделение сети, и PRIMARY в меньшинстве, запись будет отклонена
)

# Write Concern: REPLICAS (w=2) для 3-узловой replica set
result = collection.insert_one(
    {"name": "Charlie"},
    write_concern={"w": 2}  # PRIMARY + минимум 1 SECONDARY
)

Как это обеспечивает Partition Tolerance:

# СЦЕНАРИЙ: Разделение сети
# Primary в PARTITION A, Secondaries в PARTITION B
#
# PARTITION A              PARTITION B
# Primary (1)              Secondary1 (1)
#                          Secondary2 (1)
# Total: 1 узел           Total: 2 узла
# Quorum: 2               Quorum: 2 ✓
#
# Запрос с w="majority" к PRIMARY в PARTITION A:
try:
    collection.insert_one(
        {"data": "important"},
        write_concern={"w": "majority"}
    )
except WriteError:
    # ❌ Ошибка! PRIMARY не может получить подтверждение от большинства
    # Данные НЕ будут записаны
    print("Cannot achieve write concern majority - network partition detected")

# PARTITION B может выбрать нового PRIMARY из 2 узлов
# и начать принимать операции самостоятельно
print("PARTITION B: Can elect new PRIMARY and accept writes")

5. Heartbeat и Detection Network Partition

# Узлы в Replica Set периодически проверяют друг друга
# (heartbeat protocol)

heartbeat_config = {
    "heartbeatFrequencyMS": 10000,      # Каждые 10 сек
    "electionTimeoutMillis": 10000,     # Если нет пульса 10 сек
    "serverMonitoringMode": "auto",
}

# Если PRIMARY не слышит SECONDARIES более electionTimeoutMillis:
# 1. PRIMARY проверяет может ли это быть network partition
# 2. Если PRIMARY в меньшинстве (< quorum), он STEP DOWN
# 3. PRIMARY становится SECONDARY (read-only)
# 4. Большинство узлов могут выбрать нового PRIMARY

print("""
Когда PRIMARY теряет связь > electionTimeoutMillis:
1. PRIMARY проверяет что он в большинстве
2. Если НЕ в большинстве → STEP DOWN
3. PRIMARY → SECONDARY (больше не может писать)
4. Другое большинство выбирает нового PRIMARY
""")

6. Practical Partition Tolerance Implementation

from pymongo import MongoClient
from pymongo.errors import AutoReconnect, ServerSelectionTimeoutError
from typing import Any, Dict
import asyncio

class PartitionTolerantDB:
    def __init__(self, replica_set_url: str):
        self.client = MongoClient(
            replica_set_url,
            serverSelectionTimeoutMS=5000,
            connectTimeoutMS=10000,
            socketTimeoutMS=5000,
            retryWrites=True,  # Автоматический retry при временных ошибках
            w="majority"  # Сильная консистентность
        )
    
    async def write_with_retry(
        self,
        db_name: str,
        collection_name: str,
        document: Dict[str, Any],
        max_retries: int = 3
    ) -> str:
        """Запись с retry при разделении сети"""
        
        for attempt in range(max_retries):
            try:
                db = self.client[db_name]
                collection = db[collection_name]
                
                result = collection.insert_one(
                    document,
                    write_concern={"w": "majority", "wtimeoutMS": 5000}
                )
                return str(result.inserted_id)
            
            except ServerSelectionTimeoutError:
                # Не можем достичь PRIMARY
                if attempt < max_retries - 1:
                    print(f"Network partition detected, retry {attempt + 1}...")
                    await asyncio.sleep(2 ** attempt)  # Exponential backoff
                else:
                    raise
            
            except AutoReconnect:
                # Временная потеря соединения
                print("Temporary connection loss, retrying...")
                await asyncio.sleep(1)
    
    async def read_with_preference(
        self,
        db_name: str,
        collection_name: str,
        query: Dict[str, Any],
        read_preference: str = "primary"
    ) -> Any:
        """Чтение с выбором стратегии"""
        
        from pymongo.read_preferences import ReadPreference
        
        db = self.client[db_name]
        collection = db[collection_name]
        
        if read_preference == "primary":
            # Читаем только с PRIMARY (свежие данные, но недоступно при разделении)
            return collection.find_one(query)
        
        elif read_preference == "secondary":
            # Читаем со SECONDARY (могут быть старые данные, но доступно при разделении)
            collection.with_options(
                read_preference=ReadPreference.SECONDARY
            )
            return collection.find_one(query)
        
        elif read_preference == "secondary_preferred":
            # Предпочитаем SECONDARY, но можем читать с PRIMARY
            collection.with_options(
                read_preference=ReadPreference.SECONDARY_PREFERRED
            )
            return collection.find_one(query)

# Использование
async def main():
    db = PartitionTolerantDB("mongodb://rs0/mydb?replicaSet=rs0")
    
    # Запись с гарантией консистентности
    doc_id = await db.write_with_retry(
        "mydb",
        "users",
        {"name": "Alice", "email": "alice@example.com"}
    )
    
    # Чтение с разных стратегий
    fresh_data = await db.read_with_preference(
        "mydb", "users",
        {"_id": doc_id},
        read_preference="primary"
    )
    
    # При разделении сети может читать со SECONDARY
    possibly_stale = await db.read_with_preference(
        "mydb", "users",
        {"_id": doc_id},
        read_preference="secondary_preferred"
    )

7. Выбор между Consistency, Availability и Partition Tolerance

# MongoDB можно конфигурировать под разные требования:

configs = {
    "CP (Consistency + Partition)": {
        "write_concern": {"w": "majority"},
        "read_preference": "primary",
        "use_case": "Финансовые операции, критичные данные",
        "pros": "Сильная консистентность",
        "cons": "При разделении может быть недоступна"
    },
    
    "AP (Availability + Partition)": {
        "write_concern": {"w": 1},
        "read_preference": "secondary_preferred",
        "use_case": "Social media, analytics",
        "pros": "Высокая доступность даже при разделении",
        "cons": "Возможны несогласованные данные временно"
    }
}

print("MongoDB по умолчанию: CP (сильная консистентность)")
print("Можно настроить на AP если нужна доступность")

Итоговая схема Partition Tolerance в MongoDB

РЕПЛИКА САТ (3 узла):
  PRIMARY → SECONDARY → SECONDARY
     ↓         ↓           ↓
  все writes replication replication

ПРИ РАЗДЕЛЕНИИ СЕТИ:
  PARTITION A         PARTITION B
  PRIMARY (1)         SECONDARY (1)
                      SECONDARY (1)
  ↓                   ↓
  1 < quorum(2)      2 = quorum(2)
  ✗ STEP DOWN        ✓ ELECT NEW PRIMARY
  
  PARTITION A:        PARTITION B:
  ❌ cannot write      ✓ can write
  ❌ read only        ✓ read + write

РЕЗУЛЬТАТ:
- Данные консистентны
- Одна часть сети может писать
- Другая часть может только читать
- При восстановлении сети синхронизируются

Ключевые механизмы:

  1. Replica Sets — репликация данных на несколько узлов
  2. Quorum — большинство узлов для выборов
  3. Election — выбор нового PRIMARY при сбое
  4. Heartbeat — обнаружение разделений сети
  5. Write Concern — гарантии записи на несколько узлов
  6. Read Preference — выбор от какого узла читать

Это обеспечивает Partition Tolerance при сохранении Consistency!