Что такое снимок (snapshot) в БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Снимок (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}")
Лучшие практики
-
Регулярные снимки
- Каждый час / день для критичных БД
- Храни несколько версий
-
Проверяй целостность снимков
import hashlib def verify_backup(backup_file): with open(backup_file, 'rb') as f: hash_val = hashlib.sha256(f.read()).hexdigest() return hash_val -
Тестируй восстановление
- Минимум раз в месяц восстанавливай из снимка
- Убедись, что данные целы
-
Распределённое хранение
- Снимки на разные диски / сервера
- Защита от потери данных
-
Шифруй снимки
from cryptography.fernet import Fernet key = Fernet.generate_key() cipher = Fernet(key) encrypted_backup = cipher.encrypt(backup_data)
Снимки — критична часть стратегии управления данными. Правильная организация снимков обеспечивает надежность, доступность и восстанавливаемость БД.