Что такое кэширование в программировании?

Ответ

Кэширование — это техника хранения копии данных или результатов вычислений во временном, но быстродоступном хранилище (кэше) для последующего их быстрого извлечения без необходимости повторного выполнения ресурсоемкой операции (обращения к БД, сложного расчета, сетевого запроса).

Цели: повышение производительности, снижение нагрузки на источник данных и уменьшение задержек.

Базовый пример in-memory кэша на Java:

// Простейший кэш на основе HashMap
Map<String, Object> cache = new HashMap<>();

public Object getData(String key) {
    if (!cache.containsKey(key)) {
        // Дорогая операция (запрос к БД, API-вызов)
        Object data = loadDataFromSource(key);
        cache.put(key, data);
    }
    return cache.get(key);
}

Пример с использованием библиотеки Caffeine:

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

Cache<String, Object> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES) // TTL
    .maximumSize(1000) // LRU-политика
    .build();

public Object getData(String key) {
    // Лямбда выполнится только при отсутствии значения в кэше
    return cache.get(key, k -> loadDataFromSource(k));
}

Основные проблемы: инвалидация устаревших данных, обеспечение консистентности в распределенных системах, выбор политики вытеснения (LRU, LFU).

Ответ 18+ 🔞

А, кэширование, ёпта! Ну это ж как та коробка с печеньем на верхней полке, которую ты туда засунул, чтобы дети не сожрали сразу. Взял раз — и не надо каждый раз идти в магазин, блядь, за новым пакетом. Суть в том, чтобы не ебашить мозг тяжелой операцией по сто раз на дню.

Зачем это надо, спросишь? Да чтобы всё летало, как угорелое, нагрузку на основную систему не насиловать и пользователям не давать повода материться от ожидания. Всё просто, как три копейки.

Вот, смотри, самый примитивный способ на коленке собрать, прям как в старые добрые:

// Сделал из обычной мапы тайник, типа "схоронил от жены"
Map<String, Object> cache = new HashMap<>();

public Object getData(String key) {
    if (!cache.containsKey(key)) {
        // А вот тут самая жопа: лезть в базу или в интернет, что долго и муторно
        Object data = loadDataFromSource(key);
        cache.put(key, data); // Засунул в тайник. Ша!
    }
    return cache.get(key); // Ага, уже есть! Нехуй было париться.
}

Но это, блядь, уровень детского сада. Кэш-то бессмертный получается, места неограниченно, а данные в нём протухают, как суп без холодильника. Надо что-то посерьёзнее.

Вот, например, взяли библиотечку Caffeine — это уже как профессиональный холодильник с таймером и датчиком заполнения:

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

Cache<String, Object> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES) // Через 10 минут — в утиль, протухло
    .maximumSize(1000) // Больше тысячи элементов не влезет, выкинет самое старое (LRU)
    .build();

public Object getData(String key) {
    // Красота! Если в кэше нет — он сам вызовет твою тяжёлую функцию и положит результат.
    return cache.get(key, k -> loadDataFromSource(k));
}

Вот это уже, блядь, цивилизация! Автоматическое вытеснение, время жизни... Красота.

Но главная засада, ёпта, всегда в одном: как не обосраться с устаревшими данными? Ну положил ты в кэш курс доллара, а он там уже неделю лежит, а в мире кризис. Пользователь видит старые цифры и потом тебе же в бухгалтерии мозг выносит. Или в распределённой системе: один сервер обновил данные, а на остальных-то в кэшах лежит пиздец, а не информация. Консистентность — это вообще отдельная песня, там такие танцы с бубном начинаются, что мама не горюй.

Короче, инструмент охуенный, но если применять его, как слон в посудной лавке, можно такую дичь получить, что потом полгода разгребать. Думай головой, что кэшируешь, как инвалидируешь и когда чистишь. А то будет не оптимизация, а один большой пиздец.