132 lines
5.7 KiB
Python
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 |