Files
pos-system/docs/en/architecture/caching-architecture.md
Ho Ngoc Hai 3ed499ef7c docs: Update architecture documentation for GoodGo Platform
- Translated and revised architecture documents to enhance clarity and accessibility for both English and Vietnamese audiences.
- Improved diagrams and descriptions for caching, data consistency, event-driven architecture, microservices communication, observability, and security architecture.
- Ensured consistent formatting and terminology across all documents to facilitate better understanding and navigation.
- Added quick tips and troubleshooting sections to assist developers in implementing and managing the architecture effectively.
2026-01-14 13:07:19 +07:00

19 KiB

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

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

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 (.NET)

// EN: Multi-layer cache implementation
// VI: Triển khai cache đa lớp
public class MultiLayerCacheService : ICacheService
{
    private readonly IMemoryCache _l1Cache;
    private readonly IConnectionMultiplexer _redis;
    private readonly IDatabase _l2Cache;
    private readonly ILogger<MultiLayerCacheService> _logger;
    
    public MultiLayerCacheService(
        IMemoryCache l1Cache,
        IConnectionMultiplexer redis,
        ILogger<MultiLayerCacheService> logger)
    {
        _l1Cache = l1Cache;
        _redis = redis;
        _l2Cache = redis.GetDatabase();
        _logger = logger;
    }
    
    public async Task<T?> GetAsync<T>(string key, CancellationToken ct = default)
    {
        // L1: Memory cache check
        if (_l1Cache.TryGetValue(key, out T? l1Value))
        {
            _logger.LogDebug("L1 cache hit for key: {Key}", key);
            return l1Value;
        }
        
        // L2: Redis cache check
        var l2Value = await _l2Cache.StringGetAsync(key);
        if (!l2Value.IsNullOrEmpty)
        {
            _logger.LogDebug("L2 cache hit for key: {Key}", key);
            var parsed = JsonSerializer.Deserialize<T>(l2Value!);
            
            // Warm L1 cache
            _l1Cache.Set(key, parsed, TimeSpan.FromMinutes(1));
            return parsed;
        }
        
        _logger.LogDebug("Cache miss for key: {Key}", key);
        return default;
    }
    
    public async Task SetAsync<T>(string key, T value, TimeSpan? ttl = null, CancellationToken ct = default)
    {
        var expiry = ttl ?? TimeSpan.FromMinutes(5);
        var l1Expiry = TimeSpan.FromMinutes(Math.Min(expiry.TotalMinutes, 5));
        
        // L1: Memory cache (max 5 min)
        _l1Cache.Set(key, value, l1Expiry);
        
        // L2: Redis cache
        var json = JsonSerializer.Serialize(value);
        await _l2Cache.StringSetAsync(key, json, expiry);
    }
    
    public async Task RemoveAsync(string key, CancellationToken ct = default)
    {
        _l1Cache.Remove(key);
        await _l2Cache.KeyDeleteAsync(key);
    }
}

Quy ước Đặt tên Key

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

Ví dụ (C#):

// Cache key constants
public static class CacheKeys
{
    public static string User(string userId) => $"iam:user:{userId}";
    public static string UserPermissions(string userId) => $"iam:user:{userId}:permissions";
    public static string UserRoles(string userId) => $"iam:user:{userId}:roles";
    public static string Session(string sessionId) => $"iam:session:{sessionId}";
    public static string UserQuota(string userId) => $"storage:quota:{userId}";
}

// Sử dụng
var user = await _cache.GetAsync<UserDto>(CacheKeys.User("user_123"));
var permissions = await _cache.GetAsync<List<string>>(CacheKeys.UserPermissions("user_123"));

Chiến lược TTL

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

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:

// 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

// 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

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:

# 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:

// 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:

# 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:

# 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:

// 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:

// 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


Cập nhật Lần cuối: 2026-01-14
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