# Kiến trúc Caching

> Chiến lược caching nhiều tầng để tối ưu hiệu suất

## Sơ đồ Tổng quan

```mermaid
graph TD
    Request[API Request] --> L1{L1 Cache<br/>Memory}
    
    L1 -->|Hit| Return1[Return<br/>< 1ms]
    L1 -->|Miss| L2{L2 Cache<br/>Redis}
    
    L2 -->|Hit| WarmL1[Warm L1]
    WarmL1 --> Return2[Return<br/>< 5ms]
    
    L2 -->|Miss| DB[(Database)]
    DB --> StoreL2[Store L2 + L1]
    StoreL2 --> Return3[Return<br/>< 50ms]
    
    classDef memory fill:#1b5e20,stroke:#2e7d32,color:#fff
    classDef redis fill:#e65100,stroke:#ef6c00,color:#fff
    classDef db fill:#212121,stroke:#424242,color:#fff
    classDef default fill:#202020,stroke:#505050,color:#fff
    
    class L1,Return1,WarmL1 memory
    class L2,Return2,StoreL2 redis
    class DB,Return3 db
```

## Bối cảnh Hệ thống

```mermaid
C4Context
    title Sơ đồ Bối cảnh Hệ thống Caching

    System(service, "Microservice", "Client service using cache")
    System_Ext(db, "Neon PostgreSQL", "Primary database")
    
    Boundary(caching, "Caching Layer") {
        System(l1, "L1 Cache", "In-memory NodeCache")
        System(l2, "L2 Cache", "Redis Cluster")
    }

    Rel(service, l1, "Reads/Writes", "In-process")
    Rel(service, l2, "Reads/Writes", "Redis Protocol")
    Rel(l1, l2, "Fills from", "On miss")
    Rel(l2, db, "Cache aside", "On miss")
    
    UpdateElementStyle(service, $fontColor="white", $bgColor="#1a237e", $borderColor="#3949ab")
    UpdateElementStyle(db, $fontColor="white", $bgColor="#212121", $borderColor="#424242")
    UpdateElementStyle(l1, $fontColor="white", $bgColor="#1b5e20", $borderColor="#2e7d32")
    UpdateElementStyle(l2, $fontColor="white", $bgColor="#e65100", $borderColor="#ef6c00")
```

### Mô tả Bối cảnh
- **Service**: Giao tiếp trực tiếp với L1 Cache (in-memory) để đạt độ trễ thấp nhất.
- **L1 Cache**: Cache cục bộ, không chia sẻ, tự động hết hạn (TTL ngắn).
- **L2 Cache**: Redis cluster chia sẻ, giữ dữ liệu lâu dài hơn và đồng bộ giữa các instances.
- **Database**: Nguồn dữ liệu gốc (source of truth), chỉ được truy cập khi cache miss.

## Mô tả Kiến trúc

### Caching Nhiều Tầng

Nền tảng GoodGo sử dụng caching 2 tầng để tối ưu hiệu suất:

**L1 Cache (Memory)**:
- In-memory cache trên mỗi service instance
- Truy cập rất nhanh (< 1ms)
- Dung lượng giới hạn (10k keys mặc định)
- TTL ngắn (60 giây mặc định, tối đa 5 phút)
- Không share giữa instances

**L2 Cache (Redis)**:
- Shared distributed cache
- Truy cập nhanh (< 5ms)
- Dung lượng lớn
- TTL dài hơn (configurable, thường 5-15 phút)
- Share giữa tất cả service instances

**Cache Flow**:
```
Request → L1 → L2 → Database
  ↓        ↓    ↓      ↓
40-50%  80-90% 10-20%  Cache miss
hit rate hit rate        rate
```

## Triển khai Cache

### Multi-Layer Cache Service

```typescript
// Triển khai multi-layer cache
export class MultiLayerCache {
  private l1Cache: NodeCache;
  private l2Cache: Redis;
  
  constructor() {
    // L1: Memory cache
    this.l1Cache = new NodeCache({
      stdTTL: 60,        // 60 giây mặc định
      maxKeys: 10000,    // Tối đa 10k keys
      checkperiod: 120   // Kiểm tra expired keys mỗi 2 phút
    });
    
    // L2: Redis cache
    this.l2Cache = new Redis({
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT),
      db: 0
    });
  }
  
  async get<T>(key: string): Promise<T | null> {
    // Thử L1 trước
    const l1Value = this.l1Cache.get<T>(key);
    if (l1Value) {
      logger.debug('L1 cache hit', { key });
      return l1Value;
    }
    
    // Thử L2
    const l2Value = await this.l2Cache.get(key);
    if (l2Value) {
      logger.debug('L2 cache hit', { key });
      const parsed = JSON.parse(l2Value) as T;
      
      // Làm ấm L1 cache
      this.l1Cache.set(key, parsed);
      return parsed;
    }
    
    logger.debug('Cache miss', { key });
    return null;
  }
  
  async set(key: string, value: any, ttl: number = 300): Promise<void> {
    // Lưu vào cả L1 và L2
    this.l1Cache.set(key, value, Math.min(ttl, 300)); // L1 tối đa 5 phút
    await this.l2Cache.setex(key, ttl, JSON.stringify(value));
  }
  
  async del(key: string): Promise<void> {
    this.l1Cache.del(key);
    await this.l2Cache.del(key);
  }
  
  async invalidatePattern(pattern: string): Promise<void> {
    // L1: Xóa tất cả (cách đơn giản)
    this.l1Cache.flushAll();
    
    // L2: Xóa theo pattern
    const keys = await this.l2Cache.keys(pattern);
    if (keys.length > 0) {
      await this.l2Cache.del(...keys);
    }
  }
}
```

### Quy ước Đặt tên Key

**Pattern**: `{service}:{entity}:{identifier}:{sub-resource}`

**Ví dụ**:
```typescript
// User cache keys
const keys = {
  user: (userId: string) => `iam:user:${userId}`,
  userPermissions: (userId: string) => `iam:user:${userId}:permissions`,
  userRoles: (userId: string) => `iam:user:${userId}:roles`,
  session: (sessionId: string) => `iam:session:${sessionId}`,
};

// Sử dụng
const user = await cache.get(keys.user('user_123'));
const permissions = await cache.get(keys.userPermissions('user_123'));
```

## Chiến lược TTL

```mermaid
graph LR
    subgraph "TTL Tiers"
        Short[Short TTL<br/>60-300s<br/>Frequently changing]
        Medium[Medium TTL<br/>300-1800s<br/>Moderately changing]
        Long[Long TTL<br/>1800-3600s<br/>Rarely changing]
    end
    
    Short --> Permissions[User Permissions]
    Short --> Sessions[Session Data]
    
    Medium --> UserProfiles[User Profiles]
    Medium --> OrgData[Organization Data]
    
    Long --> Config[Static Config]
    Long --> RefData[Reference Data]
    
    classDef tier fill:#202020,stroke:#505050,color:#fff
    classDef short fill:#b71c1c,stroke:#f44336,color:#fff
    classDef medium fill:#e65100,stroke:#ef6c00,color:#fff
    classDef long fill:#1b5e20,stroke:#2e7d32,color:#fff
    
    class Short short
    class Medium medium
    class Long long
    class Permissions,Sessions,UserProfiles,OrgData,Config,RefData tier
```

**Hướng dẫn TTL**:
| Loại Dữ liệu | TTL | Lý do |
|---------------------------|-----|----------------|
| User permissions | 5 min | Nhạy cảm bảo mật |
| Session data | Varies | Dựa trên độ dài session |
| User profiles | 10 min | Tần suất cập nhật vừa phải |
| Organization data | 15 min | Cập nhật không thường xuyên |
| Static config | 30-60 min | Rất ổn định |
| Reference data | 1-2 hours | Hầu như không thay đổi |

## Vô hiệu hóa Cache

```mermaid
sequenceDiagram
    participant API
    participant Service
    participant Cache
    participant DB
    
    API->>Service: Update User
    Service->>DB: UPDATE user
    DB-->>Service: Success
    
    Service->>Cache: Invalidate user:123
    Service->>Cache: Invalidate user:123:permissions
    Service->>Cache: Invalidate user:123:roles
    Cache-->>Service: Cleared
    
    Service-->>API: Success
    
    Note over Service,Cache: Next request will fetch fresh data
```

**Chiến lược Invalidation**:

```typescript
// 1. Invalidation single key
async updateUser(userId: string, data: UpdateUserDto): Promise<User> {
  const user = await userRepository.update(userId, data);
  
  // Vô hiệu hóa user cache
  await cache.del(cacheKeys.user(userId));
  
  return user;
}

// 2. Invalidation theo pattern
async updateUserRole(userId: string, roleId: string): Promise<void> {
  await userRoleRepository.assign(userId, roleId);
  
  // Vô hiệu hóa tất cả cache liên quan đến user
  await cache.invalidatePattern(`iam:user:${userId}:*`);
}

// 3. Invalidation theo thời gian (TTL expiry)
// Tự động xử lý bởi cache
```

## Làm ấm Cache

```typescript
// Preload dữ liệu thường xuyên truy cập
async warmCache(): Promise<void> {
  logger.info('Starting cache warming');
  
  // Làm ấm user permissions cho active users
  const activeUsers = await userRepository.findActive({ limit: 1000 });
  
  for (const user of activeUsers) {
    const permissions = await rbacService.getUserPermissions(user.id);
    
    await cache.set(
      cacheKeys.userPermissions(user.id),
      permissions,
      300 // 5 phút
    );
  }
  
  logger.info('Cache warming completed', { count: activeUsers.length });
}

// Chạy khi service khởi động
warmCache().catch(err => logger.error('Cache warming failed', { err }));
```

## Quyết định Thiết kế

### Quyết định 1: Multi-layer Caching (L1 + L2)

**Bối cảnh**: Cần giảm tải cho Redis và đạt độ trễ cực thấp cho dữ liệu hot.
**Quyết định**: Sử dụng kết hợp L1 (NodeCache) và L2 (Redis).
**Hậu quả**:
- ✅ Độ trễ < 1ms cho 40-50% requests.
- ✅ Giảm network traffic tới Redis.
- ❌ Phức tạp trong đồng bộ (L1 có thể stale trong thời gian ngắn).

## Đặc điểm Hiệu suất

### Mục tiêu Hiệu suất
| Chỉ số | Mục tiêu | Ghi chú |
|-----------------|-------------------|-----------------|
| **L1 Hit Latency** | < 0.5ms | In-memory lookup |
| **L2 Hit Latency** | < 5ms | Network RTT + Redis processing |
| **Combine Hit Rate** | > 90% | L1 + L2 combined |
| **L1 Capacity** | 10k items | Per instance limit to protect heap |
| **Cache Warmup Time** | < 30s | At service startup |

## Cân nhắc Bảo mật

### Bảo mật Cache
- **Encryption**: Dữ liệu nhạy cảm (PII) PHẢI được mã hóa trước khi lưu vào L2 Redis (AES-256). L1 có thể lưuplaintext vì nằm trong memory process (trừ khi memory dump).
- **Isolation**: Redis instance được bảo vệ bằng mật khẩu và Network Policy (chỉ allow traffic từ nội bộ K8s).
- **TLS**: Kết nối tới Redis qua TLS 1.2+.
- **Data Sanitization**: Không cache toàn bộ user object nếu chứa password hash hoặc secrets.

## Triển khai

```mermaid
graph TD
    subgraph "Redis Cluster"
        subgraph "Masters"
            M1[Redis Master 1<br/>Slots: 0-5460]
            M2[Redis Master 2<br/>Slots: 5461-10922]
            M3[Redis Master 3<br/>Slots: 10923-16383]
        end
        
        subgraph "Slaves"
            S1[Redis Slave 1<br/>Replica of M1]
            S2[Redis Slave 2<br/>Replica of M2]
            S3[Redis Slave 3<br/>Replica of M3]
        end
        
        M1 --> S1
        M2 --> S2
        M3 --> S3
        
        Sentinel[Redis Sentinel<br/>3 nodes]
        
        Sentinel -.->|Monitor| M1
        Sentinel -.->|Monitor| M2
        Sentinel -.->|Monitor| M3
    end
    
    subgraph "Services"
        Service1[Service A]
        Service2[Service B]
        Service3[Service C]
    end
    
    Service1 --> M1
    Service1 --> M2
    Service1 --> M3
    
    Service2 --> M1
    Service2 --> M2
    Service2 --> M3
    
    Service3 --> M1
    Service3 --> M2
    Service3 --> M3
    
    classDef master fill:#e65100,stroke:#ef6c00,color:#fff
    classDef slave fill:#f57c00,stroke:#e65100,color:#fff
    classDef sentinel fill:#4a148c,stroke:#7b1fa2,color:#fff
    classDef service fill:#1a237e,stroke:#3949ab,color:#fff
    classDef default fill:#202020,stroke:#505050,color:#fff
    
    class M1,M2,M3 master
    class S1,S2,S3 slave
    class Sentinel sentinel
    class Service1,Service2,Service3 service
```

### Chiến lược Triển khai

**Redis Cluster Configuration**:
- **Mode**: Cluster mode với 3 masters + 3 slaves
- **Replication**: Mỗi master có 1 slave cho high availability
- **Sentinel**: 3-node Sentinel ensemble cho automatic failover
- **Sharding**: 16384 hash slots phân chia đều giữa 3 masters
- **Persistence**: RDB snapshots mỗi 5 phút, AOF disabled (performance)

**Resource Allocation**:
| Component | CPU | Memory | Disk | Replicas |
|-----------|-----|--------|------|----------|
| **Redis Master** | 1 core | 2GB | 10GB SSD | 3 |
| **Redis Slave** | 1 core | 2GB | 10GB SSD | 3 |
| **Sentinel** | 500m | 512MB | 5GB | 3 |

**Redis Configuration**:
```yaml
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru  # Evict least recently used keys
timeout 300  # Close idle connections after 5min
tcp-keepalive 60
save 300 10  # RDB snapshot every 5min if 10+ keys changed
appendonly no  # Disable AOF for performance

# Cluster config
cluster-enabled yes
cluster-node-timeout 5000
cluster-replica-validity-factor 0
```

**High Availability**:
- Automatic failover với Redis Sentinel
- Slave promotion khi master fails
- Client-side retry logic
- Connection pooling (max 50 connections per service)

**Scaling Strategy**:
- **Vertical**: Tăng memory per node (2GB → 4GB → 8GB)
- **Horizontal**: Thêm master nodes (3 → 5 → 7)
- **Read Scaling**: Route reads to slaves
- **Monitoring**: Auto-alert khi memory usage > 80%

## Giám sát & Khả năng quan sát

### Chỉ số Chính

**Cache Performance Metrics**:
```typescript
// Custom metrics cho cache performance
import { Counter, Histogram, Gauge } from 'prom-client';

export const cacheHits = new Counter({
  name: 'cache_hits_total',
  labelNames: ['layer', 'key_prefix'] // layer: l1/l2, key_prefix: user/session/etc
});

export const cacheMisses = new Counter({
  name: 'cache_misses_total',
  help: 'Tổng số cache misses',
  labelNames: ['key_prefix']
});

export const cacheLatency = new Histogram({
  name: 'cache_operation_duration_seconds',
  help: 'Thời gian thực hiện cache operation',
  labelNames: ['operation', 'layer'], // operation: get/set/del
  buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1]
});

export const cacheSize = new Gauge({
  name: 'cache_size_bytes',
  help: 'Kích thước cache (bytes)',
  labelNames: ['layer']
});

export const cacheEvictions = new Counter({
  name: 'cache_evictions_total',
  help: 'Tổng số cache evictions',
  labelNames: ['layer', 'reason'] // reason: ttl_expired/memory_full
});
```

**Redis Metrics**:
- `redis_connected_clients` - Connected clients
- `redis_used_memory_bytes` - Memory usage
- `redis_memory_fragmentation_ratio` - Memory fragmentation
- `redis_keyspace_hits_total` - Cache hits
- `redis_keyspace_misses_total` - Cache misses
- `redis_evicted_keys_total` - Evicted keys
- `redis_expired_keys_total` - Expired keys
- `redis_commands_processed_total` - Commands processed

**Calculated Metrics**:
```promql
# Cache hit rate
rate(cache_hits_total[5m]) / (rate(cache_hits_total[5m]) + rate(cache_misses_total[5m]))

# L1 hit rate
rate(cache_hits_total{layer="l1"}[5m]) / rate(cache_hits_total[5m])

# L2 hit rate
rate(cache_hits_total{layer="l2"}[5m]) / rate(cache_hits_total[5m])

# Average cache latency
histogram_quantile(0.95, cache_operation_duration_seconds_bucket)

# Memory usage percentage
redis_used_memory_bytes / redis_maxmemory_bytes * 100
```

**Alerting Rules**:
```yaml
# Quy tắc cảnh báo cho cache
groups:
  - name: cache_alerts
    interval: 30s
    rules:
      # Low cache hit rate
      - alert: LowCacheHitRate
        expr: |
          rate(cache_hits_total[5m]) / 
          (rate(cache_hits_total[5m]) + rate(cache_misses_total[5m])) < 0.5
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Tỷ lệ cache hit thấp"
          description: "Tỷ lệ cache hit là {{ $value | humanizePercentage }}"
      
      # High memory usage
      - alert: HighRedisMemoryUsage
        expr: redis_used_memory_bytes / redis_maxmemory_bytes > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Sử dụng bộ nhớ Redis cao"
          description: "Bộ nhớ Redis sử dụng là {{ $value | humanizePercentage }}"
      
      # High eviction rate
      - alert: HighEvictionRate
        expr: rate(redis_evicted_keys_total[5m]) > 100
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Tỷ lệ cache eviction cao"
          description: "Tỷ lệ eviction là {{ $value }}/giây"
      
      # Redis down
      - alert: RedisDown
        expr: redis_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Redis bị down"
      
      # High replication lag
      - alert: HighReplicationLag
        expr: redis_replication_lag_seconds > 5
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Độ trễ replication cao"
          description: "Độ trễ replication là {{ $value }}s"
```

**Dashboards**:
- **Cache Overview**: Hit rate, miss rate, latency, size
- **Redis Cluster**: Memory usage, connections, commands/sec
- **Performance**: L1 vs L2 hit rates, operation latency
- **Evictions**: Eviction rate, reasons, trends

**Logging**:
```typescript
// Structured logging cho cache operations
logger.debug('Cache operation', {
  operation: 'get',
  layer: 'l1',
  key: cacheKey,
  hit: true,
  latency: duration,
  correlationId: req.correlationId
});

logger.warn('Cache eviction', {
  layer: 'l2',
  reason: 'memory_full',
  evictedKeys: count,
  memoryUsage: usagePercent
});

logger.error('Cache error', {
  operation: 'set',
  layer: 'l2',
  error: error.message,
  key: cacheKey
});
```

**Health Checks**:
```typescript
// Health check cho Redis
async function checkRedisHealth(): Promise<boolean> {
  try {
    await redis.ping();
    const info = await redis.info('memory');
    const memoryUsage = parseMemoryUsage(info);
    
    return memoryUsage < 0.9; // Healthy if < 90% memory
  } catch (error) {
    logger.error('Redis health check failed', { error });
    return false;
  }
}
```


## Tài liệu Liên quan

- [System Design](./system-design.md) - Kiến trúc tổng thể với caching
- [Data Consistency Patterns](./data-consistency-patterns.md) - Cache invalidation patterns

---

**Cập nhật Lần cuối**: 2026-01-07  
**Tác giả**: GoodGo Architecture Team

## Quick Tips

### Mermaid Common Issues
- **Arrow Syntax**: Use `-->` for solid arrows, `-.->` for dotted arrows.
- **Node IDs**: Avoid spaces/special chars in IDs (e.g., `Node-A` not `Node A`).
- **Subgraphs**: Ensure `subgraph` names are unique and descriptive.

### Color Pattern Quick Reference
| Element | Dark Color | Text Color |
|---------|------------|------------|
| **Service (Blue)** | `#1a237e` | `#ffffff` |
| **Storage (Gray)** | `#212121` | `#ffffff` |
| **Cache L2 (Orange)** | `#e65100` | `#ffffff` |
| **Cache L1 (Green)** | `#1b5e20` | `#ffffff` |
| **Monitoring (Purple)** | `#4a148c` | `#ffffff` |

### Visual Indicators
- ✅ **Recommended / Khuyên dùng**
- ❌ **Not Recommended / Không khuyên dùng**
- ⚠️ **Warning / Cảnh báo**
