mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-04-22 16:17:23 +08:00
361 lines
11 KiB
Rust
361 lines
11 KiB
Rust
use crate::db::Db;
|
|
use crate::db::entity::*;
|
|
use sea_orm::*;
|
|
use tokio::time::{Duration, sleep};
|
|
use tracing::{error, info, warn};
|
|
|
|
/// 数据清理策略配置
|
|
#[derive(Debug, Clone)]
|
|
pub struct CleanupConfig {
|
|
/// 健康记录保留天数
|
|
pub health_record_retention_days: i64,
|
|
/// 每个节点保留的健康记录最大数量
|
|
pub max_health_records_per_node: u64,
|
|
/// 清理任务运行间隔(秒)
|
|
pub cleanup_interval_seconds: u64,
|
|
/// 是否启用自动清理
|
|
pub auto_cleanup_enabled: bool,
|
|
}
|
|
|
|
impl Default for CleanupConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
health_record_retention_days: 30,
|
|
max_health_records_per_node: 70000,
|
|
cleanup_interval_seconds: 1200, // 20分钟
|
|
auto_cleanup_enabled: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 数据清理管理器
|
|
pub struct CleanupManager {
|
|
db: Db,
|
|
config: CleanupConfig,
|
|
running: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
|
}
|
|
|
|
impl CleanupManager {
|
|
/// 创建新的清理管理器
|
|
pub fn new(db: Db, config: CleanupConfig) -> Self {
|
|
Self {
|
|
db,
|
|
config,
|
|
running: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
|
}
|
|
}
|
|
|
|
/// 使用默认配置创建清理管理器
|
|
pub fn with_default_config(db: Db) -> Self {
|
|
Self::new(db, CleanupConfig::default())
|
|
}
|
|
|
|
/// 启动自动清理任务
|
|
pub async fn start_auto_cleanup(&self) -> anyhow::Result<()> {
|
|
if self.config.auto_cleanup_enabled {
|
|
let running = self.running.clone();
|
|
let db = self.db.clone();
|
|
let config = self.config.clone();
|
|
|
|
running.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
|
|
tokio::spawn(async move {
|
|
info!("Auto cleanup task started");
|
|
|
|
while running.load(std::sync::atomic::Ordering::SeqCst) {
|
|
if let Err(e) = Self::perform_cleanup(&db, &config).await {
|
|
error!("Auto cleanup failed: {}", e);
|
|
}
|
|
|
|
sleep(Duration::from_secs(config.cleanup_interval_seconds)).await;
|
|
}
|
|
|
|
info!("Auto cleanup task stopped");
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 停止自动清理任务
|
|
pub fn stop_auto_cleanup(&self) {
|
|
self.running
|
|
.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
}
|
|
|
|
/// 执行一次完整的清理操作
|
|
pub async fn perform_cleanup(db: &Db, config: &CleanupConfig) -> anyhow::Result<CleanupResult> {
|
|
let mut result = CleanupResult::default();
|
|
|
|
// 清理旧的健康记录
|
|
let health_cleanup_result =
|
|
Self::cleanup_old_health_records(db, config.health_record_retention_days).await?;
|
|
result.old_health_records_cleaned = health_cleanup_result.records_removed;
|
|
|
|
// 清理过量的健康记录
|
|
let excess_cleanup_result =
|
|
Self::cleanup_excess_health_records(db, config.max_health_records_per_node).await?;
|
|
result.excess_health_records_cleaned = excess_cleanup_result.records_removed;
|
|
|
|
// 数据库维护
|
|
let maintenance_result = Self::perform_database_maintenance(db).await?;
|
|
result.vacuum_performed = maintenance_result.vacuum_performed;
|
|
result.analyze_performed = maintenance_result.analyze_performed;
|
|
|
|
info!("Cleanup completed: {:?}", result);
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// 清理旧的健康记录
|
|
async fn cleanup_old_health_records(
|
|
db: &Db,
|
|
days: i64,
|
|
) -> anyhow::Result<CleanupHealthRecordsResult> {
|
|
let cutoff = chrono::Local::now().fixed_offset() - chrono::Duration::days(days);
|
|
|
|
let result = health_records::Entity::delete_many()
|
|
.filter(health_records::Column::CheckedAt.lt(cutoff))
|
|
.exec(db.orm_db())
|
|
.await?;
|
|
|
|
let records_removed = result.rows_affected;
|
|
|
|
if records_removed > 0 {
|
|
info!(
|
|
"Cleaned {} old health records (older than {} days)",
|
|
records_removed, days
|
|
);
|
|
}
|
|
|
|
Ok(CleanupHealthRecordsResult { records_removed })
|
|
}
|
|
|
|
/// 清理过量的健康记录
|
|
async fn cleanup_excess_health_records(
|
|
db: &Db,
|
|
max_records: u64,
|
|
) -> anyhow::Result<CleanupExcessRecordsResult> {
|
|
// 获取所有节点
|
|
let nodes = shared_nodes::Entity::find().all(db.orm_db()).await?;
|
|
|
|
let mut total_removed = 0;
|
|
|
|
for node in nodes {
|
|
// 计算需要删除的记录数量
|
|
let total_count = health_records::Entity::find()
|
|
.filter(health_records::Column::NodeId.eq(node.id))
|
|
.count(db.orm_db())
|
|
.await?;
|
|
|
|
if total_count > max_records {
|
|
let to_remove = total_count - max_records;
|
|
|
|
// 获取需要保留的最小ID
|
|
let keep_id = health_records::Entity::find()
|
|
.filter(health_records::Column::NodeId.eq(node.id))
|
|
.order_by_desc(health_records::Column::CheckedAt)
|
|
.offset(max_records)
|
|
.limit(1)
|
|
.into_model::<health_records::Model>()
|
|
.one(db.orm_db())
|
|
.await?;
|
|
|
|
info!(
|
|
"Node {}: total count: {}, to remove: {}, last keep record: {:?}",
|
|
node.id, total_count, to_remove, keep_id
|
|
);
|
|
|
|
if let Some(keep_record) = keep_id {
|
|
// 删除比保留记录更早的记录
|
|
let result = health_records::Entity::delete_many()
|
|
.filter(health_records::Column::NodeId.eq(node.id))
|
|
.filter(health_records::Column::Id.lt(keep_record.id))
|
|
.exec(db.orm_db())
|
|
.await?;
|
|
|
|
total_removed += result.rows_affected;
|
|
}
|
|
}
|
|
}
|
|
|
|
if total_removed > 0 {
|
|
info!(
|
|
"Cleaned {} excess health records (max {} per node)",
|
|
total_removed, max_records
|
|
);
|
|
}
|
|
|
|
Ok(CleanupExcessRecordsResult {
|
|
records_removed: total_removed,
|
|
})
|
|
}
|
|
|
|
/// 执行数据库维护操作
|
|
async fn perform_database_maintenance(db: &Db) -> anyhow::Result<DatabaseMaintenanceResult> {
|
|
let mut vacuum_performed = false;
|
|
let mut analyze_performed = false;
|
|
|
|
// 执行 ANALYZE
|
|
match db
|
|
.orm_db()
|
|
.execute(Statement::from_string(
|
|
DatabaseBackend::Sqlite,
|
|
"ANALYZE".to_string(),
|
|
))
|
|
.await
|
|
{
|
|
Ok(_) => {
|
|
analyze_performed = true;
|
|
info!("Database ANALYZE completed");
|
|
}
|
|
Err(e) => {
|
|
warn!("Database ANALYZE failed: {}", e);
|
|
}
|
|
}
|
|
|
|
// 执行 VACUUM(仅在需要时)
|
|
if vacuum_performed || analyze_performed {
|
|
match db
|
|
.orm_db()
|
|
.execute(Statement::from_string(
|
|
DatabaseBackend::Sqlite,
|
|
"VACUUM".to_string(),
|
|
))
|
|
.await
|
|
{
|
|
Ok(_) => {
|
|
vacuum_performed = true;
|
|
info!("Database VACUUM completed");
|
|
}
|
|
Err(e) => {
|
|
warn!("Database VACUUM failed: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(DatabaseMaintenanceResult {
|
|
vacuum_performed,
|
|
analyze_performed,
|
|
})
|
|
}
|
|
|
|
/// 获取数据库统计信息
|
|
pub async fn get_database_stats(db: &Db) -> anyhow::Result<DatabaseStats> {
|
|
let total_nodes = shared_nodes::Entity::find().count(db.orm_db()).await?;
|
|
|
|
let total_health_records = health_records::Entity::find().count(db.orm_db()).await?;
|
|
|
|
let active_nodes = shared_nodes::Entity::find()
|
|
.filter(shared_nodes::Column::IsActive.eq(true))
|
|
.count(db.orm_db())
|
|
.await?;
|
|
|
|
Ok(DatabaseStats {
|
|
total_nodes,
|
|
active_nodes,
|
|
total_health_records,
|
|
})
|
|
}
|
|
|
|
/// 获取清理配置
|
|
pub fn get_config(&self) -> &CleanupConfig {
|
|
&self.config
|
|
}
|
|
|
|
/// 更新清理配置
|
|
pub fn update_config(&mut self, config: CleanupConfig) {
|
|
self.config = config;
|
|
}
|
|
}
|
|
|
|
/// 清理结果
|
|
#[derive(Default, Debug, Clone, serde::Serialize)]
|
|
pub struct CleanupResult {
|
|
pub old_health_records_cleaned: u64,
|
|
pub old_instances_cleaned: u64,
|
|
pub excess_health_records_cleaned: u64,
|
|
pub vacuum_performed: bool,
|
|
pub analyze_performed: bool,
|
|
}
|
|
|
|
/// 健康记录清理结果
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct CleanupHealthRecordsResult {
|
|
pub records_removed: u64,
|
|
}
|
|
|
|
/// 停止实例清理结果
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct CleanupStoppedInstancesResult {
|
|
pub instances_removed: u64,
|
|
}
|
|
|
|
/// 过量记录清理结果
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct CleanupExcessRecordsResult {
|
|
pub records_removed: u64,
|
|
}
|
|
|
|
/// 数据库维护结果
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct DatabaseMaintenanceResult {
|
|
pub vacuum_performed: bool,
|
|
pub analyze_performed: bool,
|
|
}
|
|
|
|
/// 数据库统计信息
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct DatabaseStats {
|
|
pub total_nodes: u64,
|
|
pub active_nodes: u64,
|
|
pub total_health_records: u64,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::Db;
|
|
|
|
#[tokio::test]
|
|
async fn test_cleanup_manager() {
|
|
let db = Db::memory_db().await;
|
|
let cleanup_manager = CleanupManager::with_default_config(db.clone());
|
|
|
|
// 测试获取配置
|
|
let config = cleanup_manager.get_config();
|
|
assert_eq!(config.health_record_retention_days, 30);
|
|
|
|
// 测试清理操作
|
|
let result = CleanupManager::perform_cleanup(&db, config).await.unwrap();
|
|
println!("Cleanup result: {:?}", result);
|
|
|
|
// 测试获取统计信息
|
|
let stats = CleanupManager::get_database_stats(&db).await.unwrap();
|
|
println!("Database stats: {:?}", stats);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_cleanup_config() {
|
|
let config = CleanupConfig {
|
|
health_record_retention_days: 7,
|
|
max_health_records_per_node: 500,
|
|
cleanup_interval_seconds: 1800,
|
|
auto_cleanup_enabled: false,
|
|
};
|
|
|
|
let db = Db::memory_db().await;
|
|
let mut cleanup_manager = CleanupManager::new(db, config.clone());
|
|
|
|
assert_eq!(cleanup_manager.get_config().health_record_retention_days, 7);
|
|
|
|
// 测试更新配置
|
|
let new_config = CleanupConfig::default();
|
|
cleanup_manager.update_config(new_config);
|
|
assert_eq!(
|
|
cleanup_manager.get_config().health_record_retention_days,
|
|
30
|
|
);
|
|
}
|
|
}
|