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

Что такое снимок (snapshot) в БД?

1.0 Junior🔥 241 комментариев
#Безопасность#Тестирование

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

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

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

Снимок (Snapshot) в БД

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

Типы снимков

1. Физический снимок (Physical Snapshot)

  • Копия всех физических файлов БД
  • Содержит всё: таблицы, индексы, логи
  • Быстрое восстановление (просто скопировать файлы)
import shutil
import os
from datetime import datetime

def create_physical_snapshot(db_path, backup_dir):
    # Создаём папку с меткой времени
    timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    snapshot_path = os.path.join(backup_dir, f'snapshot_{timestamp}')
    
    # Копируем файлы БД
    shutil.copytree(db_path, snapshot_path)
    print(f"Физический снимок создан: {snapshot_path}")
    return snapshot_path

# Использование
physical_snap = create_physical_snapshot('/var/lib/postgresql/data', '/backups')

2. Логический снимок (Logical Snapshot)

  • SQL дамп: CREATE TABLE, INSERT, SELECT
  • Портативен (можно загрузить в другую СУБД)
  • Медленнее восстанавливается
import subprocess
from datetime import datetime

def create_logical_snapshot(db_name, user, backup_dir):
    timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    snapshot_file = f"{backup_dir}/snapshot_{db_name}_{timestamp}.sql"
    
    # PostgreSQL
    cmd = f"pg_dump -U {user} {db_name} > {snapshot_file}"
    subprocess.run(cmd, shell=True)
    
    print(f"Логический снимок создан: {snapshot_file}")
    return snapshot_file

# Использование
logical_snap = create_logical_snapshot('mydb', 'postgres', '/backups')

Снимки в контексте транзакций

Изоляция при одновременных операциях:

import sqlite3

conn = sqlite3.connect(':memory:')
cursor = conn.cursor()

cursor.execute('CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)')
cursor.execute('INSERT INTO accounts VALUES (1, 1000)')
cursor.execute('INSERT INTO accounts VALUES (2, 500)')
conn.commit()

# Транзакция 1: снимок начального состояния
conn.isolation_level = 'SERIALIZABLE'  # Полная изоляция
cursor.execute('SELECT SUM(balance) FROM accounts')  # Снимок: 1500
print(f"Снимок в Tx1: {cursor.fetchone()[0]}")

# Одновременно Транзакция 2 могла бы изменить данные
# Но благодаря снимку Tx1 видит консистентное состояние

conn.commit()

MVCC (Multi-Version Concurrency Control)

Механизм, где каждый снимок — это отдельная версия данных:

# PostgreSQL: каждый запрос видит снимок на момент начала
# Даже если другие транзакции меняют данные

from sqlalchemy import create_engine, text

engine = create_engine('postgresql://...')

with engine.connect() as conn:
    # Начало транзакции = начало снимка
    trans = conn.begin()
    
    # Эта транзакция видит консистентное состояние
    result = conn.execute(text('SELECT * FROM users WHERE age > 25'))
    users = result.fetchall()
    
    # Даже если другие Tx добавляют/удаляют пользователей,
    # мой снимок не изменится
    
    trans.commit()

Снимки для резервного копирования

Холодное копирование (Cold Backup)

import shutil
import subprocess

def cold_backup(db_path, backup_path):
    # 1. Останавливаем БД
    subprocess.run(['systemctl', 'stop', 'postgresql'])
    
    # 2. Создаём снимок
    shutil.copytree(db_path, backup_path)
    
    # 3. Запускаем БД
    subprocess.run(['systemctl', 'start', 'postgresql'])
    
    print(f"Cold backup готов: {backup_path}")

cold_backup('/var/lib/postgresql/data', '/backups/cold_backup_2024')

Горячее копирование (Hot Backup)

import subprocess
from datetime import datetime

def hot_backup(db_name, backup_dir):
    timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    backup_file = f"{backup_dir}/backup_{timestamp}.sql.gz"
    
    # БД остаётся работающей, создаём снимок
    cmd = f"pg_dump {db_name} | gzip > {backup_file}"
    subprocess.run(cmd, shell=True)
    
    print(f"Hot backup готов: {backup_file}")
    return backup_file

hot_backup('mydb', '/backups')

Снимки при репликации

Синхронизация между хостами:

from sqlalchemy import create_engine, text

# Master БД
master = create_engine('postgresql://master:5432/mydb')

# Slave БД
slave = create_engine('postgresql://slave:5432/mydb')

# 1. Создаём снимок на Master
with master.connect() as conn:
    # Получаем позицию WAL (Write-Ahead Log)
    result = conn.execute(text('SELECT pg_current_wal_lsn()'))
    wal_position = result.fetchone()[0]
    print(f"Снимок на Master (WAL): {wal_position}")
    
    # 2. Копируем логи репликации на Slave
    # 3. Slave воспроизводит снимок

Снимки для тестирования

import sqlite3
import tempfile
import shutil
from pathlib import Path

class DatabaseSnapshot:
    def __init__(self, db_file):
        self.db_file = db_file
        self.snapshot_file = None
    
    def create_snapshot(self):
        # Создаём снимок перед тестом
        with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as f:
            self.snapshot_file = f.name
        
        shutil.copy(self.db_file, self.snapshot_file)
        print(f"Снимок перед тестом: {self.snapshot_file}")
    
    def restore_snapshot(self):
        # Восстанавливаем снимок после теста
        shutil.copy(self.snapshot_file, self.db_file)
        print(f"БД восстановлена из снимка")
    
    def cleanup(self):
        # Удаляем снимок
        Path(self.snapshot_file).unlink()

# Использование в тестах
def test_user_operations():
    snap = DatabaseSnapshot('test.db')
    snap.create_snapshot()
    
    try:
        # Тестируем операции
        conn = sqlite3.connect('test.db')
        cursor = conn.cursor()
        cursor.execute('DELETE FROM users')  # Опасная операция
        # Ошибка в тесте
    finally:
        snap.restore_snapshot()  # БД вернута в исходное состояние
        snap.cleanup()

Версионирование данных (Temporal Snapshots)

from datetime import datetime
from sqlalchemy import create_engine, text

engine = create_engine('sqlite:///:memory:')

with engine.begin() as conn:
    # Таблица с версионированием
    conn.execute(text('''
        CREATE TABLE user_history (
            id INTEGER PRIMARY KEY,
            user_id INTEGER,
            name TEXT,
            email TEXT,
            created_at TIMESTAMP,
            snapshot_id INTEGER
        )
    '''))
    
    # Текущее состояние
    conn.execute(text('''
        INSERT INTO user_history (user_id, name, email, created_at, snapshot_id)
        VALUES (1, 'John', 'john@example.com', datetime('now'), 1)
    '''))
    
    # После изменения
    conn.execute(text('''
        INSERT INTO user_history (user_id, name, email, created_at, snapshot_id)
        VALUES (1, 'John Smith', 'john.smith@example.com', datetime('now'), 2)
    '''))

# Просмотр истории
with engine.connect() as conn:
    result = conn.execute(text(
        'SELECT name, email, created_at FROM user_history WHERE user_id = 1'
    ))
    for row in result:
        print(f"Снимок: {row}")

Лучшие практики

  1. Регулярные снимки

    • Каждый час / день для критичных БД
    • Храни несколько версий
  2. Проверяй целостность снимков

    import hashlib
    
    def verify_backup(backup_file):
        with open(backup_file, 'rb') as f:
            hash_val = hashlib.sha256(f.read()).hexdigest()
        return hash_val
    
  3. Тестируй восстановление

    • Минимум раз в месяц восстанавливай из снимка
    • Убедись, что данные целы
  4. Распределённое хранение

    • Снимки на разные диски / сервера
    • Защита от потери данных
  5. Шифруй снимки

    from cryptography.fernet import Fernet
    
    key = Fernet.generate_key()
    cipher = Fernet(key)
    encrypted_backup = cipher.encrypt(backup_data)
    

Снимки — критична часть стратегии управления данными. Правильная организация снимков обеспечивает надежность, доступность и восстанавливаемость БД.

Что такое снимок (snapshot) в БД? | PrepBro