Ответ
TTL (Time To Live) — это механизм, который определяет время жизни записи в кеше. По истечении этого времени запись считается «протухшей» (stale) и подлежит удалению или обновлению при следующем запросе.
Зачем нужен TTL?
- Актуальность данных: Гарантирует, что пользователи не будут получать устаревшую информацию.
- Управление памятью: Автоматически очищает ненужные данные, предотвращая переполнение кеша и утечки памяти.
Реализация в Go
Существует два основных подхода к реализации TTL-кеша:
- Пассивная очистка (Lazy Eviction): Время жизни проверяется только в момент запроса данных (
Get). Если запись просрочена, она удаляется и не возвращается пользователю. Это самый простой способ. - Активная очистка (Active Eviction): Фоновая горутина периодически сканирует весь кеш и удаляет просроченные записи. Этот подход лучше управляет памятью, но добавляет сложности.
Для большинства задач проще и надежнее использовать готовые библиотеки, например, github.com/patrickmn/go-cache.
Пример простой реализации (пассивная очистка)
import (
"sync"
"time"
)
type CacheItem struct {
Value interface{}
Expiry int64 // Время истечения в Unix наносекундах
}
type TtlCache struct {
mu sync.RWMutex
items map[string]CacheItem
}
func (c *TtlCache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
if c.items == nil {
c.items = make(map[string]CacheItem)
}
c.items[key] = CacheItem{
Value: value,
Expiry: time.Now().Add(ttl).UnixNano(),
}
}
func (c *TtlCache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[key]
if !found {
return nil, false
}
// Проверяем, не истекло ли время жизни
if time.Now().UnixNano() > item.Expiry {
// Можно добавить удаление ключа здесь, но это потребует `c.mu.Lock()`
return nil, false
}
return item.Value, true
} Ответ 18+ 🔞
А, ТТЛ, блядь! Ну это ж классика, ёпта! Это как молоко в холодильнике — стоит поставишь дату, а потом смотришь, а оно уже скисло, пиздец. Вот ТТЛ — это и есть та самая дата на упаковке для данных в кеше.
Ну и нахуя он нужен-то, этот ТТЛ?
- Чтобы не жрать просрочку. Представь, ты кешируешь курс доллара, а он там уже неделю как обновился, а у тебя в кеше лежит старая цифра. Клиенту выдашь — он тебе такую ебучую иску влепит, мало не покажется. ТТЛ гарантирует, что данные не залежатся, как тот самый творог на задней полке.
- Чтобы память не проебать. Если всё кешировать навечно, то рано или поздно оперативка скажет «ну всё, пидоры, я устала, я ухожу» и твой сервис накроется медным тазом. ТТЛ сам выносит мусор, как образцовый дворник.
Как это в Go сделать, блядь?
Тут два главных пути, как и в жизни: можно быть ленивой жопой, а можно — занудой с графиком уборки.
- Ленивая жопа (Пассивная очистка). Проверяем срок годности только когда лезем в холодильник за едой (
Get). Открыл баночку, а там плесень — ну нахуй, выкинул и сделал вид, что её и не было. Проще пареной репы. - Зануда с графиком (Активная очистка). Запускаем фоновую уборщицу-горутину, которая раз в N времени проходит по всем полкам и выкидывает всё просроченное. Память чище, но возни больше — надо следить, чтобы уборщица сама не сожрала все ресурсы.
Серьёзно, для прода лучше не выёбывайся и бери готовую библиотеку, типа github.com/patrickmn/go-cache. Там всё уже придумано до нас, эти, умные дядьки.
Ну ладно, вот тебе пример, как ленивая жопа работает:
import (
"sync"
"time"
)
type CacheItem struct {
Value interface{}
Expiry int64 // Когда сдохнуть, в наносекундах Unix
}
type TtlCache struct {
mu sync.RWMutex
items map[string]CacheItem
}
func (c *TtlCache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
if c.items == nil {
c.items = make(map[string]CacheItem)
}
c.items[key] = CacheItem{
Value: value,
Expiry: time.Now().Add(ttl).UnixNano(),
}
}
func (c *TtlCache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[key]
if !found {
return nil, false
}
// А не протухло ли?
if time.Now().UnixNano() > item.Expiry {
// Тут можно было бы удалить ключ, но для этого нужна уже полная блокировка (Lock), а не RLock.
// Короче, нахуй, вернём "не найдено".
return nil, false
}
return item.Value, true
}
Вот и вся магия, блядь. Положил с датой смерти, достал — проверил, не сдохла ли запись. Если сдохла — делаешь вид, что её никогда и не было. Как в том анекдоте про тещу.