← Tilbage til koncepter

Data-caching

Ydeevne

Teknik til at gemme ofte brugt data i hurtig lagring for at reducere latenstid og databasebelastning.

Beskrivelse

Caching er praksis hvor hyppigt tilgået data gemmes i et hurtigt lag (typisk in-memory) så fremtidige forespørgsler kan serveres meget hurtigere. I stedet for at forespørge databasen hver gang, tjekker man først cachen - hvis data findes (cache hit), returneres det direkte. Hvis ikke (cache miss), hentes data fra databasen og gemmes i cachen til næste gang. Populære caching-strategier inkluderer: Cache-Aside (applikation tjekker cache først), Write-Through (skriv til både cache og DB), Write-Behind (skriv til cache, DB asynkront) og Read-Through (cache selv henter fra DB). Almindelige caching-teknologier er Redis og Memcached. Vigtige overvejelser inkluderer cache eviction-politikker (LRU, LFU, FIFO), TTL (Time To Live) og cache-invalidering (det svære problem). Korrekt caching kan reducere latenstid fra 100ms til under 1ms og reducere databasebelastning med 90%+.

Problem

Databaseforespørgsler kan være langsomme - disk I/O, komplekse joins, aggregeringer. Når tusinder af brugere forespørger samme data (f.eks. produktkatalog, artikler), bombarderer de databasen med identiske forespørgsler. Dette spilder ressourcer og giver dårlig ydeevne.

Løsning

Cache hyppigt tilgået data i RAM. Første forespørgsel henter fra databasen og gemmer i cache. De næste 1000 forespørgsler serveres direkte fra cache med sub-millisekund latenstid. Databasebelastning reduceres drastisk.

Eksempel

// Cache-Aside Pattern (Lazy Loading)
class ProductService {
  constructor(db, cache) {
    this.db = db;
    this.cache = cache; // Redis client
  }

  async getProduct(productId) {
    const cacheKey = `product:${productId}`;
    
    // 1. Check cache først
    let product = await this.cache.get(cacheKey);
    
    if (product) {
      console.log('Cache HIT');
      return JSON.parse(product);
    }
    
    // 2. Cache MISS - hent fra database
    console.log('Cache MISS - querying database');
    product = await this.db.query(
      'SELECT * FROM products WHERE id = $1',
      [productId]
    );
    
    // 3. Gem i cache med TTL (1 time)
    await this.cache.setex(
      cacheKey,
      3600, // TTL i sekunder
      JSON.stringify(product)
    );
    
    return product;
  }

  async updateProduct(productId, data) {
    // Update database
    await this.db.query(
      'UPDATE products SET name=$1, price=$2 WHERE id=$3',
      [data.name, data.price, productId]
    );
    
    // Cache Invalidation - fjern cached version
    await this.cache.del(`product:${productId}`);
    // Næste read vil cache miss og hente fresh data
  }
}

-----------------------------------------------------

// Write-Through Cache
async function saveUser(userId, userData) {
  // Skriv til BÅDE database OG cache
  await Promise.all([
    db.query('INSERT INTO users VALUES ($1, $2)', [userId, userData]),
    cache.set(`user:${userId}`, JSON.stringify(userData))
  ]);
  // Cache er altid synchronized med database
}

-----------------------------------------------------

// Cache with TTL and Layers
class CacheService {
  async getPopularProducts() {
    const cacheKey = 'products:popular';
    
    // L1: In-memory cache (meget hurtig)
    if (this.memoryCache.has(cacheKey)) {
      return this.memoryCache.get(cacheKey);
    }
    
    // L2: Redis cache
    const redisData = await redis.get(cacheKey);
    if (redisData) {
      this.memoryCache.set(cacheKey, JSON.parse(redisData));
      return JSON.parse(redisData);
    }
    
    // L3: Database (slowest)
    const dbData = await db.query(`
      SELECT * FROM products 
      ORDER BY sales_count DESC 
      LIMIT 20
    `);
    
    // Cache i begge layers
    await redis.setex(cacheKey, 300, JSON.stringify(dbData));
    this.memoryCache.set(cacheKey, dbData);
    
    return dbData;
  }
}

Fordele

  • Drastisk reduceret latenstid (100ms → 1ms)
  • Reduceret databasebelastning (90%+ reduktion)
  • Bedre skalering af læsetunge arbejdsbyrder
  • Lavere hostingomkostninger (færre DB-ressourcer)
  • Forbedret brugeroplevelse

Udfordringer

  • Cache-invalidering er svært ('sværeste problem')
  • Forældet data hvis ikke invalideret korrekt
  • Ekstra kompleksitet og bevægelige dele
  • Hukommelsesomkostninger for cache-lag
  • Kold cache-ydeevne (efter genstart)

Anvendelsesområder

  • Forsidens indhold og navigation
  • Produktkataloger i e-handel
  • Brugersessioner og autentificeringstokens
  • API rate limiting-tællere
  • Beregnede/aggregerede data

Eksempler fra den virkelige verden

  • Facebook feed: Cache opslag for millioner af brugere
  • Netflix: Cache filmmetadata og thumbnails
  • Amazon: Cache produktdetaljer og priser
  • Wikipedia: Cache artikler (læses meget oftere end opdateres)
  • Nyhedssider: Cache artikler med TTL på 5 minutter