Кешу нужны синхронизация, TTL и изоляция данных от внешних изменений. Один экземпляр передают всем обработчикам через зависимости.
In‑memory cache for user profiles and their orders
Кешу нужны синхронизация, TTL и изоляция данных от внешних изменений. Один экземпляр передают всем обработчикам через зависимости.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Реализуйте кеш в памяти, который по заданному ключу возвращает профиль пользователя вместе со списком его заказов.
Требования:
- Кеш должен поддерживать TTL (время жизни) для сохранённых записей.
- Кеш должен быть доступен из любой функции, обрабатывающей заказы.
- Предоставьте сценарии тестирования, демонстрирующие поведение кеша.
package main
import (
"log"
"time"
)
type UserProfile struct {
UUID string
Name string
Orders []Order
}
type Order struct {
UUID string
CreateAt time.Time
UpdateAt time.Time
Basket interface{}
}
func main() {
result := 2 + 2
if result != 4 {
log.Fatalf("expected 4, got %d", result)
}
log.Println("Tests passed")
}
Ответ
Кешу нужны синхронизация, TTL и изоляция данных от внешних изменений. Один экземпляр передают всем обработчикам через зависимости.
Для данного Basket interface{} примем явный контракт: JSON-совместимые данные без сохранения конкретных Go-типов внутри Basket. Сериализация даёт отдельную копию; несовместимые значения возвращают ошибку.
import (
"encoding/json"
"errors"
"sync"
"time"
)
type profileEntry struct {
data []byte
expires time.Time
}
type ProfileCache struct {
mu sync.Mutex
entries map[string]profileEntry
}
func (c *ProfileCache) Set(key string, p UserProfile, ttl time.Duration) error {
if ttl <= 0 { return errors.New("ttl must be positive") }
data, err := json.Marshal(p)
if err != nil { return err }
c.mu.Lock()
defer c.mu.Unlock()
if c.entries == nil { c.entries = make(map[string]profileEntry) }
c.entries[key] = profileEntry{data, time.Now().Add(ttl)}
return nil
}
func (c *ProfileCache) Get(key string) (UserProfile, bool, error) {
c.mu.Lock()
entry, ok := c.entries[key]
if ok && !time.Now().Before(entry.expires) {
delete(c.entries, key)
ok = false
}
c.mu.Unlock()
if !ok { return UserProfile{}, false, nil }
var p UserProfile
err := json.Unmarshal(entry.data, &p)
return p, err == nil, err
}
func (c *ProfileCache) PurgeExpired() {
c.mu.Lock()
defer c.mu.Unlock()
now := time.Now()
for key, entry := range c.entries {
if !now.Before(entry.expires) { delete(c.entries, key) }
}
}
Вызовите PurgeExpired периодически из управляемого жизненного цикла сервиса: иначе непрочитанные просроченные записи останутся в памяти. Для большого кеша добавляют лимит размера. JSON-числа в Basket при таком декодировании становятся float64; если важны точность или конкретный тип, замените interface{} типизированной моделью и явным копированием.
Сценарии проверки: miss; hit до TTL; miss после TTL; обновление продлевает срок; изменение возвращённых Orders не меняет кеш; конкурентные Get/Set; ошибка сериализации Basket. Проверка 2+2 из исходника кеш вообще не проверяет.