adding several features
This commit is contained in:
@@ -0,0 +1,643 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::{Mutex, RwLock, Semaphore};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::infrastructure::services::id_mapping_service::{IdMappingService, IdMappingError};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::application::ports::outbound::IdMappingPort;
|
||||
|
||||
/// Tamaño máximo de entradas en el caché
|
||||
const MAX_CACHE_SIZE: usize = 10_000;
|
||||
|
||||
/// Tiempo de vida del caché (en segundos)
|
||||
const CACHE_TTL_SECONDS: u64 = 60 * 5; // 5 minutos
|
||||
|
||||
/// Optimizador para operaciones masivas de mapeo de IDs
|
||||
pub struct IdMappingOptimizer {
|
||||
/// Servicio base de mapeo de IDs
|
||||
base_service: Arc<IdMappingService>,
|
||||
|
||||
/// Caché de ID por ruta (path -> id)
|
||||
path_to_id_cache: RwLock<HashMap<String, (String, Instant)>>,
|
||||
|
||||
/// Caché de ruta por ID (id -> path)
|
||||
id_to_path_cache: RwLock<HashMap<String, (String, Instant)>>,
|
||||
|
||||
/// Contador de hits
|
||||
stats: RwLock<OptimizerStats>,
|
||||
|
||||
/// Semáforo para limitar operaciones de batch
|
||||
batch_limiter: Semaphore,
|
||||
|
||||
/// Cola de batch pendientes
|
||||
pending_batch: Mutex<BatchQueue>,
|
||||
}
|
||||
|
||||
/// Estadísticas del optimizador
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct OptimizerStats {
|
||||
/// Número total de consultas get_path_by_id
|
||||
pub path_by_id_queries: usize,
|
||||
/// Número de hits en caché get_path_by_id
|
||||
pub path_by_id_hits: usize,
|
||||
|
||||
/// Número total de consultas get_or_create_id
|
||||
pub get_id_queries: usize,
|
||||
/// Número de hits en caché get_or_create_id
|
||||
pub get_id_hits: usize,
|
||||
|
||||
/// Número de batch realizados
|
||||
pub batch_operations: usize,
|
||||
/// Número total de IDs procesados en batch
|
||||
pub batch_items_processed: usize,
|
||||
|
||||
/// Último momento de limpieza de caché
|
||||
pub last_cleanup: Option<Instant>,
|
||||
}
|
||||
|
||||
/// Cola para operaciones batch
|
||||
struct BatchQueue {
|
||||
/// Rutas pendientes para obtener/crear ID
|
||||
path_to_id_requests: HashSet<String>,
|
||||
/// IDs pendientes para obtener ruta
|
||||
id_to_path_requests: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Default for BatchQueue {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path_to_id_requests: HashSet::new(),
|
||||
id_to_path_requests: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resultado de una operación batch
|
||||
struct BatchResult {
|
||||
/// Mapeo de ruta a ID
|
||||
path_to_id: HashMap<String, String>,
|
||||
/// Mapeo de ID a ruta
|
||||
id_to_path: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl IdMappingOptimizer {
|
||||
/// Crea un nuevo optimizador para el servicio de mapeo de IDs
|
||||
pub fn new(base_service: Arc<IdMappingService>) -> Self {
|
||||
Self {
|
||||
base_service,
|
||||
path_to_id_cache: RwLock::new(HashMap::with_capacity(1000)),
|
||||
id_to_path_cache: RwLock::new(HashMap::with_capacity(1000)),
|
||||
stats: RwLock::new(OptimizerStats::default()),
|
||||
batch_limiter: Semaphore::new(2), // Limitar a 2 operaciones batch concurrentes
|
||||
pending_batch: Mutex::new(BatchQueue::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene estadísticas del optimizador
|
||||
pub async fn get_stats(&self) -> OptimizerStats {
|
||||
self.stats.read().await.clone()
|
||||
}
|
||||
|
||||
/// Limpia entradas expiradas del caché
|
||||
pub async fn cleanup_cache(&self) {
|
||||
let now = Instant::now();
|
||||
let ttl = Duration::from_secs(CACHE_TTL_SECONDS);
|
||||
|
||||
// Limpiar caché path_to_id
|
||||
{
|
||||
let mut cache = self.path_to_id_cache.write().await;
|
||||
let initial_size = cache.len();
|
||||
|
||||
// Retener solo entradas no expiradas
|
||||
cache.retain(|_, (_, timestamp)| {
|
||||
now.duration_since(*timestamp) < ttl
|
||||
});
|
||||
|
||||
let removed = initial_size - cache.len();
|
||||
if removed > 0 {
|
||||
debug!("Cleaned {} expired entries from path_to_id cache", removed);
|
||||
}
|
||||
}
|
||||
|
||||
// Limpiar caché id_to_path
|
||||
{
|
||||
let mut cache = self.id_to_path_cache.write().await;
|
||||
let initial_size = cache.len();
|
||||
|
||||
// Retener solo entradas no expiradas
|
||||
cache.retain(|_, (_, timestamp)| {
|
||||
now.duration_since(*timestamp) < ttl
|
||||
});
|
||||
|
||||
let removed = initial_size - cache.len();
|
||||
if removed > 0 {
|
||||
debug!("Cleaned {} expired entries from id_to_path cache", removed);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.last_cleanup = Some(now);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inicia tarea de limpieza periódica
|
||||
pub fn start_cleanup_task(optimizer: Arc<Self>) {
|
||||
tokio::spawn(async move {
|
||||
let cleanup_interval = Duration::from_secs(CACHE_TTL_SECONDS / 2);
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(cleanup_interval).await;
|
||||
optimizer.cleanup_cache().await;
|
||||
|
||||
// Loguear estadísticas periódicamente
|
||||
let stats = optimizer.get_stats().await;
|
||||
info!("ID Mapping Optimizer stats - Path queries: {}, hits: {} ({}%), ID queries: {}, hits: {} ({}%), Batch ops: {}, items: {}",
|
||||
stats.path_by_id_queries,
|
||||
stats.path_by_id_hits,
|
||||
if stats.path_by_id_queries > 0 { stats.path_by_id_hits as f64 * 100.0 / stats.path_by_id_queries as f64 } else { 0.0 },
|
||||
stats.get_id_queries,
|
||||
stats.get_id_hits,
|
||||
if stats.get_id_queries > 0 { stats.get_id_hits as f64 * 100.0 / stats.get_id_queries as f64 } else { 0.0 },
|
||||
stats.batch_operations,
|
||||
stats.batch_items_processed
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Agrega una solicitud a la cola pendiente para procesamiento batch
|
||||
async fn queue_path_to_id_request(&self, path: &StoragePath) -> Result<Option<String>, IdMappingError> {
|
||||
let path_str = path.to_string();
|
||||
|
||||
// Verificar primero en el caché
|
||||
{
|
||||
let cache = self.path_to_id_cache.read().await;
|
||||
if let Some((id, _)) = cache.get(&path_str) {
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.get_id_hits += 1;
|
||||
}
|
||||
|
||||
return Ok(Some(id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Si no está en caché, agregar a la cola de batch
|
||||
{
|
||||
let mut batch_queue = self.pending_batch.lock().await;
|
||||
batch_queue.path_to_id_requests.insert(path_str);
|
||||
}
|
||||
|
||||
// No encontrado en caché, debe procesarse en batch
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Procesa las solicitudes pendientes en batch
|
||||
async fn process_batch(&self) -> Result<BatchResult, IdMappingError> {
|
||||
// Adquirir permiso para operación batch
|
||||
let _permit = self.batch_limiter.acquire().await.unwrap();
|
||||
|
||||
// Obtener las solicitudes pendientes
|
||||
let (path_requests, id_requests) = {
|
||||
let mut batch_queue = self.pending_batch.lock().await;
|
||||
|
||||
let paths = std::mem::take(&mut batch_queue.path_to_id_requests);
|
||||
let ids = std::mem::take(&mut batch_queue.id_to_path_requests);
|
||||
|
||||
(paths, ids)
|
||||
};
|
||||
|
||||
// Crear resultados
|
||||
let mut result = BatchResult {
|
||||
path_to_id: HashMap::with_capacity(path_requests.len()),
|
||||
id_to_path: HashMap::with_capacity(id_requests.len()),
|
||||
};
|
||||
|
||||
// Procesar solicitudes path->id en batch
|
||||
for path_str in path_requests {
|
||||
let path = StoragePath::from_string(&path_str);
|
||||
match self.base_service.get_or_create_id(&path).await {
|
||||
Ok(id) => {
|
||||
result.path_to_id.insert(path_str.clone(), id.clone());
|
||||
result.id_to_path.insert(id, path_str);
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error batch-processing path {}: {}", path_str, e);
|
||||
// Continuar con las demás solicitudes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Procesar solicitudes id->path en batch
|
||||
for id in id_requests {
|
||||
match self.base_service.get_path_by_id(&id).await {
|
||||
Ok(path) => {
|
||||
let path_str = path.to_string();
|
||||
result.id_to_path.insert(id.clone(), path_str.clone());
|
||||
result.path_to_id.insert(path_str, id);
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error batch-processing ID {}: {}", id, e);
|
||||
// Continuar con las demás solicitudes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar caché con los resultados del batch
|
||||
{
|
||||
let mut path_cache = self.path_to_id_cache.write().await;
|
||||
let mut id_cache = self.id_to_path_cache.write().await;
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
for (path, id) in &result.path_to_id {
|
||||
path_cache.insert(path.clone(), (id.clone(), now));
|
||||
}
|
||||
|
||||
for (id, path) in &result.id_to_path {
|
||||
id_cache.insert(id.clone(), (path.clone(), now));
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.batch_operations += 1;
|
||||
stats.batch_items_processed += result.path_to_id.len() + result.id_to_path.len();
|
||||
}
|
||||
|
||||
// Guardar los cambios al disco en segundo plano
|
||||
let service_clone = self.base_service.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = service_clone.save_pending_changes().await {
|
||||
error!("Error saving ID mapping changes: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Fuerza el procesamiento de solicitudes pendientes si hay suficientes
|
||||
async fn trigger_batch_if_needed(&self, min_batch_size: usize) -> Result<(), IdMappingError> {
|
||||
// Verificar si hay suficientes solicitudes pendientes
|
||||
let should_process = {
|
||||
let batch_queue = self.pending_batch.lock().await;
|
||||
batch_queue.path_to_id_requests.len() + batch_queue.id_to_path_requests.len() >= min_batch_size
|
||||
};
|
||||
|
||||
// Procesar si es necesario
|
||||
if should_process {
|
||||
self.process_batch().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Precargar un conjunto de rutas para obtener sus IDs en batch
|
||||
#[allow(dead_code)]
|
||||
pub async fn preload_paths(&self, paths: Vec<StoragePath>) -> Result<(), IdMappingError> {
|
||||
// Solo proceder si hay rutas para cargar
|
||||
if paths.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Rutas que debemos cargar (las que no están en caché)
|
||||
let mut paths_to_load = Vec::new();
|
||||
|
||||
// Verificar primero el caché
|
||||
{
|
||||
let cache = self.path_to_id_cache.read().await;
|
||||
for path in paths {
|
||||
let path_str = path.to_string();
|
||||
if !cache.contains_key(&path_str) {
|
||||
paths_to_load.push(path_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si todos estaban en caché, terminar
|
||||
if paths_to_load.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Agregar rutas a la cola para procesamiento batch
|
||||
{
|
||||
let mut batch_queue = self.pending_batch.lock().await;
|
||||
for path in paths_to_load {
|
||||
batch_queue.path_to_id_requests.insert(path);
|
||||
}
|
||||
}
|
||||
|
||||
// Ejecutar procesamiento batch inmediatamente
|
||||
self.process_batch().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Precargar un conjunto de IDs para obtener sus rutas en batch
|
||||
#[allow(dead_code)]
|
||||
pub async fn preload_ids(&self, ids: Vec<String>) -> Result<(), IdMappingError> {
|
||||
// Solo proceder si hay IDs para cargar
|
||||
if ids.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// IDs que debemos cargar (los que no están en caché)
|
||||
let mut ids_to_load = Vec::new();
|
||||
|
||||
// Verificar primero el caché
|
||||
{
|
||||
let cache = self.id_to_path_cache.read().await;
|
||||
for id in ids {
|
||||
if !cache.contains_key(&id) {
|
||||
ids_to_load.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si todos estaban en caché, terminar
|
||||
if ids_to_load.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Agregar IDs a la cola para procesamiento batch
|
||||
{
|
||||
let mut batch_queue = self.pending_batch.lock().await;
|
||||
for id in ids_to_load {
|
||||
batch_queue.id_to_path_requests.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Ejecutar procesamiento batch inmediatamente
|
||||
self.process_batch().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IdMappingPort for IdMappingOptimizer {
|
||||
async fn get_or_create_id(&self, path: &StoragePath) -> Result<String, DomainError> {
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.get_id_queries += 1;
|
||||
}
|
||||
|
||||
let path_str = path.to_string();
|
||||
|
||||
// Verificar primero en el caché
|
||||
{
|
||||
let cache = self.path_to_id_cache.read().await;
|
||||
if let Some((id, _)) = cache.get(&path_str) {
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.get_id_hits += 1;
|
||||
}
|
||||
|
||||
return Ok(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Si no está en caché, intentar agregar a cola de batch primero
|
||||
let queued_result = self.queue_path_to_id_request(path).await?;
|
||||
if let Some(id) = queued_result {
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
// Trigger batch processing if enough items accumulated
|
||||
self.trigger_batch_if_needed(20).await?;
|
||||
|
||||
// Intentar obtener del servicio base
|
||||
let id = self.base_service.get_or_create_id(path).await?;
|
||||
|
||||
// Actualizar caché con el nuevo ID
|
||||
{
|
||||
let mut path_cache = self.path_to_id_cache.write().await;
|
||||
let mut id_cache = self.id_to_path_cache.write().await;
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
// Controlar tamaño del caché
|
||||
if path_cache.len() >= MAX_CACHE_SIZE {
|
||||
warn!("Path-to-ID cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
||||
path_cache.clear();
|
||||
}
|
||||
|
||||
if id_cache.len() >= MAX_CACHE_SIZE {
|
||||
warn!("ID-to-path cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
||||
id_cache.clear();
|
||||
}
|
||||
|
||||
path_cache.insert(path_str.clone(), (id.clone(), now));
|
||||
id_cache.insert(id.clone(), (path_str, now));
|
||||
}
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn get_path_by_id(&self, id: &str) -> Result<StoragePath, DomainError> {
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.path_by_id_queries += 1;
|
||||
}
|
||||
|
||||
// Verificar primero en el caché
|
||||
{
|
||||
let cache = self.id_to_path_cache.read().await;
|
||||
if let Some((path_str, _)) = cache.get(id) {
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.path_by_id_hits += 1;
|
||||
}
|
||||
|
||||
return Ok(StoragePath::from_string(path_str));
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener del servicio base
|
||||
let path = self.base_service.get_path_by_id(id).await?;
|
||||
|
||||
// Actualizar caché
|
||||
{
|
||||
let mut id_cache = self.id_to_path_cache.write().await;
|
||||
let mut path_cache = self.path_to_id_cache.write().await;
|
||||
|
||||
let now = Instant::now();
|
||||
let path_str = path.to_string();
|
||||
|
||||
// Controlar tamaño del caché
|
||||
if id_cache.len() >= MAX_CACHE_SIZE {
|
||||
warn!("ID-to-path cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
||||
id_cache.clear();
|
||||
}
|
||||
|
||||
if path_cache.len() >= MAX_CACHE_SIZE {
|
||||
warn!("Path-to-ID cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
||||
path_cache.clear();
|
||||
}
|
||||
|
||||
id_cache.insert(id.to_string(), (path_str.clone(), now));
|
||||
path_cache.insert(path_str, (id.to_string(), now));
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
async fn update_path(&self, id: &str, new_path: &StoragePath) -> Result<(), DomainError> {
|
||||
// Invalidar caché para este ID
|
||||
{
|
||||
let mut id_cache = self.id_to_path_cache.write().await;
|
||||
let mut path_cache = self.path_to_id_cache.write().await;
|
||||
|
||||
// Eliminar la entrada del ID
|
||||
if let Some((old_path, _)) = id_cache.remove(id) {
|
||||
path_cache.remove(&old_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar en el servicio base
|
||||
let result = self.base_service.update_path(id, new_path).await?;
|
||||
|
||||
// Actualizar caché con el nuevo mapeo
|
||||
{
|
||||
let mut id_cache = self.id_to_path_cache.write().await;
|
||||
let mut path_cache = self.path_to_id_cache.write().await;
|
||||
|
||||
let now = Instant::now();
|
||||
let path_str = new_path.to_string();
|
||||
|
||||
id_cache.insert(id.to_string(), (path_str.clone(), now));
|
||||
path_cache.insert(path_str, (id.to_string(), now));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn remove_id(&self, id: &str) -> Result<(), DomainError> {
|
||||
// Invalidar caché para este ID
|
||||
{
|
||||
let mut id_cache = self.id_to_path_cache.write().await;
|
||||
let mut path_cache = self.path_to_id_cache.write().await;
|
||||
|
||||
// Eliminar la entrada del ID
|
||||
if let Some((path, _)) = id_cache.remove(id) {
|
||||
path_cache.remove(&path);
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar en el servicio base
|
||||
self.base_service.remove_id(id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_changes(&self) -> Result<(), DomainError> {
|
||||
// Delegar al servicio base
|
||||
self.base_service.save_changes().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn create_test_service() -> (Arc<IdMappingService>, Arc<IdMappingOptimizer>) {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let map_path = temp_dir.path().join("id_map.json");
|
||||
|
||||
let base_service = Arc::new(IdMappingService::new(map_path).await.unwrap());
|
||||
let optimizer = Arc::new(IdMappingOptimizer::new(base_service.clone()));
|
||||
|
||||
(base_service, optimizer)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_basic_caching() {
|
||||
let (_, optimizer) = create_test_service().await;
|
||||
|
||||
let path = StoragePath::from_string("/test/file.txt");
|
||||
|
||||
// Primera llamada debería usar el servicio base
|
||||
let id = optimizer.get_or_create_id(&path).await.unwrap();
|
||||
assert!(!id.is_empty(), "ID should not be empty");
|
||||
|
||||
// Segunda llamada debería usar caché
|
||||
let id2 = optimizer.get_or_create_id(&path).await.unwrap();
|
||||
assert_eq!(id, id2, "Same path should return same ID");
|
||||
|
||||
// Verificar estadísticas de caché
|
||||
let stats = optimizer.get_stats().await;
|
||||
assert_eq!(stats.get_id_queries, 2, "Should have 2 queries");
|
||||
assert_eq!(stats.get_id_hits, 1, "Should have 1 hit");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_batch_processing() {
|
||||
let (_, optimizer) = create_test_service().await;
|
||||
|
||||
// Crear un lote de rutas
|
||||
let mut paths = Vec::new();
|
||||
for i in 0..50 {
|
||||
paths.push(StoragePath::from_string(&format!("/test/batch/file{}.txt", i)));
|
||||
}
|
||||
|
||||
// Precargar las rutas
|
||||
optimizer.preload_paths(paths.clone()).await.unwrap();
|
||||
|
||||
// Verificar que todas están en caché
|
||||
for path in &paths {
|
||||
let id = optimizer.get_or_create_id(path).await.unwrap();
|
||||
assert!(!id.is_empty(), "ID should be available for path");
|
||||
}
|
||||
|
||||
// Verificar estadísticas
|
||||
let stats = optimizer.get_stats().await;
|
||||
assert_eq!(stats.batch_operations, 1, "Should have 1 batch operation");
|
||||
assert!(stats.batch_items_processed >= 50, "Should have processed at least 50 items");
|
||||
|
||||
// Verificar que todas las consultas posteriores son hits en caché
|
||||
assert_eq!(stats.get_id_hits, 50, "All subsequente queries should be cache hits");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cache_cleanup() {
|
||||
let (_, optimizer) = create_test_service().await;
|
||||
|
||||
// Crear algunas entradas
|
||||
let path = StoragePath::from_string("/test/cleanup.txt");
|
||||
let id = optimizer.get_or_create_id(&path).await.unwrap();
|
||||
|
||||
// Verificar estadísticas iniciales
|
||||
{
|
||||
let stats = optimizer.get_stats().await;
|
||||
assert_eq!(stats.get_id_queries, 1, "Should have 1 query");
|
||||
assert_eq!(stats.get_id_hits, 0, "Should have 0 hits");
|
||||
}
|
||||
|
||||
// Ejecutar limpieza (no debería eliminar nada todavía)
|
||||
optimizer.cleanup_cache().await;
|
||||
|
||||
// Verificar que el caché sigue funcionando
|
||||
let id2 = optimizer.get_or_create_id(&path).await.unwrap();
|
||||
assert_eq!(id, id2, "Cache should still work after cleanup");
|
||||
|
||||
{
|
||||
let stats = optimizer.get_stats().await;
|
||||
assert_eq!(stats.get_id_hits, 1, "Should have 1 hit after cleanup");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user