← Назад к вопросам

Что делать когда достигается предельный размер Cache

2.0 Middle🔥 71 комментариев
#Кэширование и NoSQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка переполнения Cache

Когда cache достигает своего предельного размера (capacity), необходимо применить стратегию вытеснения (eviction policy) для удаления старых или менее используемых записей. Выбор правильной стратегии критичен для производительности приложения.

Основные стратегии вытеснения

LRU (Least Recently Used) — удаляются данные, которые дольше всего не использовались. Это одна из самых популярных и эффективных стратегий для большинства приложений.

LFU (Least Frequently Used) — удаляются элементы с наименьшим количеством обращений. Полезна, когда частота использования важнее времени последнего доступа.

FIFO (First In First Out) — удаляются элементы в порядке их добавления в кэш. Простая, но часто неэффективная стратегия.

TTL (Time To Live) — элементы удаляются по истечении установленного времени жизни, независимо от использования.

Реализация LRU Cache в Java

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> {
    private final int capacity;
    private final LinkedHashMap<K, V> cache;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        // LinkedHashMap с accessOrder=true отслеживает порядок доступа
        this.cache = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > capacity;
            }
        };
    }
    
    public synchronized V get(K key) {
        return cache.get(key);  // обновляет порядок доступа
    }
    
    public synchronized void put(K key, V value) {
        cache.put(key, value);
        // Если размер превышен, LinkedHashMap автоматически
        // удалит least recently used элемент
    }
    
    public synchronized int size() {
        return cache.size();
    }
}

// Использование
LRUCache<String, String> cache = new LRUCache<>(100);
cache.put("user:1", "John Doe");
String value = cache.get("user:1");

Использование Guava Cache с вытеснением

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.TimeUnit;

public class UserCache {
    private final Cache<String, User> cache;
    
    public UserCache() {
        cache = CacheBuilder.newBuilder()
            .maximumSize(10000)              // максимум 10000 элементов
            .expireAfterWrite(10, TimeUnit.MINUTES)  // TTL 10 минут
            .recordStats()                    // собирать статистику
            .build();
    }
    
    public User getUser(String userId) {
        return cache.getIfPresent(userId);
    }
    
    public void putUser(String userId, User user) {
        cache.put(userId, user);
    }
    
    public void printStats() {
        System.out.println(cache.stats());
    }
}

Использование Spring Cache с вытеснением

import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("users", "products");
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(15, TimeUnit.MINUTES)
            .recordStats());
        return cacheManager;
    }
}

@Service
public class UserService {
    @Cacheable("users")
    public User findById(String id) {
        // если cache переполнен, Caffeine автоматически удалит
        // LRU элемент
        return userRepository.findById(id);
    }
    
    @CacheEvict("users", key = "#id")
    public void updateUser(String id, User user) {
        userRepository.save(user);
    }
}

Мониторинг и управление Cache

public class CacheMonitor {
    private final Cache<String, User> cache;
    
    public void monitorCache() {
        // Проверка размера
        if (cache.size() > capacity * 0.8) {
            logger.warn("Cache usage is at 80% capacity");
        }
        
        // Ручная очистка при необходимости
        cache.invalidateAll();  // очистить всё
        cache.invalidate(key);  // удалить конкретный ключ
    }
}

Выбор стратегии в зависимости от сценария

СтратегияКогда использоватьПример
LRUОбщий случай, веб-приложенияКэш пользовательских данных
LFUНеравномерное распределение обращенийКэш популярных товаров
TTLДанные с истечением актуальностиСессии, токены доступа
FIFOКогда порядок важенОчереди сообщений

Лучшие практики

  • Выбирайте правильный размер cache в зависимости от доступной памяти
  • Используйте готовые библиотеки (Guava, Caffeine) вместо самодельных реализаций
  • Мониторьте hit rate и miss rate cache для анализа эффективности
  • Применяйте TTL для данных, которые могут устаревать
  • Используйте асинхронную загрузку данных, чтобы избежать блокировок

Правильное управление переполнением cache — это ключ к сбалансированной производительности приложения.