Files

132 lines
5.7 KiB
Python

"""
===============================================================================
FILE: services/snapshots/retention.py
ROLE: Политика ротации и очистки промежуточных почасовых срезов в SQLite.
===============================================================================
"""
import logging
from datetime import datetime, timedelta
from typing import List, Tuple
from core.connection import get_connection
logger = logging.getLogger("SNAPSHOT_RETENTION")
def cleanup_old_intermediate_snapshots(days_to_keep_all: int = 2) -> int:
"""
Очищает промежуточные дневные срезы старше days_to_keep_all дней.
Правило сохранения для архивных дней:
- Сохраняется срез Y (23:59:59).
- Сохраняется полуденный срез (ближайший к 13:00).
- Все остальные промежуточные снапшоты удаляются из scud_logs.
Возвращает количество удаленных записей.
"""
cutoff_date = datetime.now() - timedelta(days=days_to_keep_all)
cutoff_str = cutoff_date.strftime("%Y-%m-%d")
deleted_rows_count = 0
with get_connection() as conn:
cursor = conn.cursor()
# 1. Получаем список всех уникальных дат старше cutoff_date
cursor.execute("""
SELECT DISTINCT log_date
FROM scud_logs
WHERE snapshot_time IS NOT NULL
""")
all_dates = [row[0] for row in cursor.fetchall()]
for raw_date in all_dates:
try:
# Преобразуем формат ДД.ММ.ГГГГ в объект даты
d_clean = raw_date.replace('_', '.')
dt_obj = datetime.strptime(d_clean, "%d.%m.%Y")
if dt_obj >= cutoff_date:
continue # Свежие дни не трогаем — там хранятся все срезы
# 2. Для архивной даты ищем все снапшоты
cursor.execute("""
SELECT DISTINCT snapshot_id, snapshot_time
FROM scud_logs
WHERE log_date = ?
""", (raw_date,))
snapshots = cursor.fetchall()
if len(snapshots) <= 2:
continue # Если и так 1-2 среза, ничего чистить не нужно
keep_ids = set()
# Ищем финальный срез Y / 23:59:59 / 22:00:00
for s_id, s_time in snapshots:
s_id_str = str(s_id)
s_time_str = str(s_time)
if s_id_str.startswith('Y') or '23:59:59' in s_time_str or '22:00:00' in s_time_str:
keep_ids.add(s_id)
break
# Ищем полуденный срез (ближайший к 13:00)
noon_target = datetime.strptime(f"{d_clean} 13:00:00", "%d.%m.%Y %H:%M:%S")
best_noon_id = None
min_noon_diff = float('inf')
for s_id, s_time in snapshots:
if s_id in keep_ids:
continue
try:
raw_t = str(s_time).strip()
if '.' in raw_t.split()[0]:
dt_s = datetime.strptime(raw_t, "%d.%m.%Y %H:%M:%S")
else:
dt_s = datetime.strptime(raw_t, "%Y-%m-%d %H:%M:%S")
diff = abs((dt_s - noon_target).total_seconds())
if diff < min_noon_diff:
min_noon_diff = diff
best_noon_id = s_id
except Exception:
continue
if best_noon_id:
keep_ids.add(best_noon_id)
# 3. Все снапшоты, не попавшие в keep_ids, удаляем
to_delete_ids = [s_id for s_id, _ in snapshots if s_id not in keep_ids]
for del_id in to_delete_ids:
cursor.execute("DELETE FROM scud_logs WHERE snapshot_id = ?", (del_id,))
deleted_rows_count += cursor.rowcount
logger.info(f"Удален промежуточный архивный срез {del_id} за {raw_date}")
except Exception as e:
logger.warning(f"Ошибка при обработке ротации за дату {raw_date}: {e}")
conn.commit()
if deleted_rows_count > 0:
logger.info(f"Ротация завершена: удалено {deleted_rows_count} строк промежуточных срезов.")
return deleted_rows_count
def purge_day_intermediate_snapshots(date_str: str) -> int:
"""
Удаляет все промежуточные дневные срезы за указанную дату,
оставляя нетронутым итоговый вечерний срез Y..._FINAL.
"""
clean_date = date_str.replace('_', '.')
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
DELETE FROM scud_logs
WHERE log_date = ?
AND snapshot_id NOT LIKE 'Y%'
AND snapshot_id NOT LIKE '%_FINAL%'
""", (clean_date,))
deleted = cursor.rowcount
conn.commit()
if deleted > 0:
logger.info(f"[Retention] Удалено {deleted} строк промежуточных срезов за {clean_date}. Сохранен только финишный Y.")
return deleted