Chiến lược Caching đa tầng: Từ Browser đến Database cho ứng dụng hiệu năng cao

Posted on: 4/25/2026 2:17:49 PM

1. Vì sao caching là backbone của mọi hệ thống hiệu năng cao

Trong bất kỳ cuộc phỏng vấn System Design nào, câu trả lời cho "làm sao để hệ thống nhanh hơn?" hầu như luôn bắt đầu bằng caching. Nhưng trong thực tế triển khai, caching không đơn giản chỉ là "thêm Redis". Một hệ thống thực sự hiệu năng cao triển khai caching ở nhiều tầng khác nhau, mỗi tầng giải quyết một lớp latency riêng biệt. Hiểu sai tầng nào nên cache gì sẽ dẫn tới lãng phí tài nguyên, data stale, hoặc tệ hơn — cache stampede làm sập cả hệ thống.

Bài viết này phân tích kiến trúc caching đa tầng từ góc nhìn thực chiến: 4 tầng cache từ browser tới database, 5 chiến lược invalidation, và cách .NET 10 với HybridCache mới giải quyết bài toán L1/L2 cache một cách triệt để.

~5msTruy vấn từ memory cache (L1)
~50msTruy vấn từ distributed cache (L2)
~200msTruy vấn database trung bình
90%+Database load giảm với caching đúng cách

2. Kiến trúc caching đa tầng — 4 lớp bảo vệ

Mỗi tầng cache đóng vai trò như một lớp bảo vệ, chặn request trước khi nó rơi xuống tầng đắt đỏ hơn phía dưới. Càng gần user, latency càng thấp nhưng dung lượng càng hạn chế và khả năng invalidation càng khó kiểm soát.

flowchart TB
    User["👤 User / Browser"]
    BCache["🖥️ Browser Cache
Cache-Control, ETag, Service Worker
~0ms latency"] CDN["🌐 CDN Edge Cache
Cloudflare, CloudFront
~10-30ms latency"] AppCache["⚡ Application Cache
IMemoryCache / HybridCache / Redis
~1-50ms latency"] DB["🗄️ Database
Query Cache, Materialized View
~100-500ms latency"] User -->|"Request"| BCache BCache -->|"Cache Miss"| CDN CDN -->|"Cache Miss"| AppCache AppCache -->|"Cache Miss"| DB DB -->|"Response"| AppCache AppCache -->|"Response + Cache"| CDN CDN -->|"Response + Cache"| BCache BCache -->|"Response"| User style User fill:#e94560,stroke:#fff,color:#fff style BCache fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style CDN fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style AppCache fill:#f8f9fa,stroke:#2196F3,color:#2c3e50 style DB fill:#2c3e50,stroke:#fff,color:#fff

Hình 1: Kiến trúc caching đa tầng — request đi từ trên xuống, mỗi tầng "chặn" trước khi rơi xuống tầng đắt hơn

TầngLatencyDung lượngPhạm viInvalidation
Browser Cache~0ms~50-300MBMột user, một deviceKhó (phụ thuộc TTL, user action)
CDN Edge10-30msTB-levelTất cả user trong regionAPI purge, TTL, tag-based
Application1-50msGB-levelTất cả request tới clusterChủ động (event, TTL, tag)
Database100-500msDisk-levelQuery layerTự động khi data thay đổi

3. Tầng 1: Browser Cache — zero network, zero cost

Browser cache là tầng cache nhanh nhất vì hoàn toàn không cần network request. Trình duyệt lưu response vào ổ đĩa/memory dựa trên HTTP headers mà server trả về.

3.1. Cache-Control — bộ não của browser cache

Header Cache-Control quyết định browser có được cache hay không, cache bao lâu, và có cần xác nhận lại với server không:

// Static assets — cache lâu, immutable
Cache-Control: public, max-age=31536000, immutable

// API response — cache ngắn, phải xác nhận
Cache-Control: private, max-age=0, must-revalidate

// HTML page — cache có điều kiện
Cache-Control: public, max-age=300, stale-while-revalidate=60

stale-while-revalidate — chiến lược "phục vụ cũ, cập nhật ngầm"

stale-while-revalidate=60 cho phép browser trả về bản cache cũ ngay lập tức cho user, đồng thời gửi request ngầm tới server để lấy bản mới. User thấy trang tải nhanh, bản mới sẽ sẵn sàng cho lần sau. Đây là pattern lý tưởng cho trang blog, danh mục sản phẩm — nơi dữ liệu thay đổi không thường xuyên nhưng vẫn cần cập nhật.

3.2. ETag — xác nhận thông minh không tải lại toàn bộ

Khi max-age hết hạn, browser gửi If-None-Match với ETag. Server so sánh: nếu nội dung chưa đổi thì trả về 304 Not Modified (vài byte) thay vì gửi lại toàn bộ response (có thể vài trăm KB). Tiết kiệm băng thông đáng kể cho API có payload lớn.

// Response đầu tiên
HTTP/1.1 200 OK
ETag: "a1b2c3d4e5"
Cache-Control: max-age=300

// Request tiếp theo sau khi cache hết hạn
GET /api/products HTTP/1.1
If-None-Match: "a1b2c3d4e5"

// Server: data chưa đổi
HTTP/1.1 304 Not Modified

3.3. Service Worker — cache layer có thể lập trình

Service Worker cho phép bạn viết logic cache hoàn toàn tùy biến bằng JavaScript. Phổ biến trong PWA, Service Worker chặn mọi fetch request và quyết định: lấy từ cache, từ network, hay kết hợp cả hai (stale-while-revalidate pattern ở tầng code).

// sw.js — Cache-first cho static, Network-first cho API
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    // Network-first cho API calls
    event.respondWith(
      fetch(event.request)
        .then(response => {
          const clone = response.clone();
          caches.open('api-v1').then(cache =>
            cache.put(event.request, clone));
          return response;
        })
        .catch(() => caches.match(event.request))
    );
  } else {
    // Cache-first cho static assets
    event.respondWith(
      caches.match(event.request)
        .then(cached => cached || fetch(event.request))
    );
  }
});

4. Tầng 2: CDN Edge Cache — giảm latency theo địa lý

CDN đặt bản sao nội dung tại hàng trăm edge server trên toàn cầu. Khi user ở Việt Nam request, CDN trả về từ node Singapore thay vì từ origin server ở US — giảm latency từ 200ms xuống 20ms.

4.1. Cloudflare — cache rules thông minh miễn phí

Cloudflare cung cấp cache rules mạnh mẽ ngay ở Free plan. Bạn có thể cache theo path, query string, header, và thiết lập TTL khác nhau cho từng loại content:

# Ví dụ Cloudflare Cache Rules (qua Dashboard hoặc API)

# Rule 1: Cache static assets 1 năm
URI Path matches "/assets/*"
→ Cache eligible, Edge TTL: 365 days, Browser TTL: 365 days

# Rule 2: Cache API responses 5 phút
URI Path matches "/api/products*"
→ Cache eligible, Edge TTL: 300s, Browser TTL: 0
→ Cache Key: include query string

# Rule 3: Bypass cache cho authenticated
Cookie contains "auth_token"
→ Bypass cache

Cache Key — quyết định "giống nhau" hay "khác nhau"

Cache key xác định hai request có trả cùng một bản cache không. Mặc định CDN dùng URL + query string làm cache key. Nhưng nếu bạn phục vụ nội dung khác nhau theo header (ví dụ Accept-Language cho đa ngôn ngữ), phải thêm header đó vào cache key — nếu không, user Việt có thể nhận được bản English đã cache.

4.2. Cache Purge và Tag-based Invalidation

CDN cho phép purge cache bằng URL cụ thể, prefix (purge tất cả /api/products/*), hoặc theo cache tag. Cloudflare Enterprise và AWS CloudFront đều hỗ trợ tag-based purge — khi một sản phẩm thay đổi, purge tag product-123 sẽ xóa mọi response liên quan ở tất cả edge server trong vài giây.

sequenceDiagram
    participant App as Application
    participant CDN as CDN Edge
    participant User as Users

    App->>CDN: Response + Cache-Tag: product-123
    CDN->>User: Serve cached response
    Note over App: Product 123 updated
    App->>CDN: Purge cache tag "product-123"
    CDN->>CDN: Invalidate all entries with tag
    User->>CDN: Next request
    CDN->>App: Cache MISS → fetch fresh
    App->>CDN: New response + Cache-Tag: product-123
    CDN->>User: Serve fresh response

Hình 2: Flow tag-based cache invalidation — purge theo tag thay vì từng URL

5. Tầng 3: Application Cache — trái tim của caching strategy

Đây là tầng cache bạn có toàn quyền kiểm soát, nằm trong process hoặc ở distributed store. Trên .NET, hệ sinh thái caching đã phát triển qua ba thế hệ:

.NET Core 1.0+
IMemoryCache — in-process, nhanh nhưng không chia sẻ giữa các instance. Mất cache khi app restart.
.NET Core 2.0+
IDistributedCache — interface cho Redis/SQL Server/NCache. Chia sẻ giữa instances nhưng API thô (byte array), không có stampede protection.
.NET 9 → 10 (GA)
HybridCache — kết hợp L1 (memory) + L2 (distributed), tích hợp stampede protection, tag-based invalidation, serialization tự động. Đây là tương lai.

5.1. IMemoryCache — đơn giản nhưng có bẫy

IMemoryCache lưu object trực tiếp trong process memory, truy xuất ở tốc độ nanosecond. Phù hợp cho single-instance hoặc dữ liệu chỉ cần cache cục bộ (config, lookup table nhỏ).

// Đăng ký trong Program.cs
builder.Services.AddMemoryCache();

// Sử dụng
public class ProductService
{
    private readonly IMemoryCache _cache;
    private readonly IProductRepository _repo;

    public async Task<Product?> GetByIdAsync(int id)
    {
        var key = $"product:{id}";

        if (_cache.TryGetValue(key, out Product? cached))
            return cached;

        var product = await _repo.GetByIdAsync(id);
        if (product is not null)
        {
            _cache.Set(key, product, new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
                SlidingExpiration = TimeSpan.FromMinutes(10),
                Size = 1 // nếu dùng SizeLimit
            });
        }
        return product;
    }
}

Bẫy Cache Stampede với IMemoryCache

Khi cache entry hết hạn, nếu có 100 request đồng thời, tất cả 100 đều thấy cache miss và đồng loạt gọi database. Với IMemoryCache, bạn phải tự xử lý bằng SemaphoreSlim hoặc Lazy<Task>. Đây chính là lý do HybridCache ra đời — stampede protection được tích hợp sẵn.

5.2. IDistributedCache — chia sẻ nhưng thô

Khi chạy nhiều instance (Kubernetes, load balancer), IMemoryCache ở mỗi pod là độc lập. User set cache ở Pod 1, nhưng request tiếp theo rơi vào Pod 2 → cache miss. IDistributedCache với Redis giải quyết vấn đề này:

// Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
    options.InstanceName = "myapp:";
});

// Sử dụng — API thô, phải tự serialize
public async Task<Product?> GetByIdAsync(int id)
{
    var key = $"product:{id}";
    var bytes = await _cache.GetAsync(key);

    if (bytes is not null)
        return JsonSerializer.Deserialize<Product>(bytes);

    var product = await _repo.GetByIdAsync(id);
    if (product is not null)
    {
        var json = JsonSerializer.SerializeToUtf8Bytes(product);
        await _cache.SetAsync(key, json, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
        });
    }
    return product;
}

Nhược điểm rõ ràng: API làm việc với byte[], phải tự serialize/deserialize, không có stampede protection, và mỗi lần đọc cache đều qua network (dù hit).

5.3. HybridCache — tương lai của caching trên .NET

HybridCache (GA từ .NET 9, stable trong .NET 10) kết hợp ưu điểm của cả hai: L1 in-memory cho tốc độ, L2 distributed cho tính nhất quán, và thêm stampede protection + tag-based invalidation:

flowchart LR
    Request["Request"] --> HC["HybridCache
GetOrCreateAsync"] HC --> L1{"L1 Memory
Cache Hit?"} L1 -->|"Hit"| Return["Return
~nanoseconds"] L1 -->|"Miss"| L2{"L2 Redis
Cache Hit?"} L2 -->|"Hit"| PopL1["Populate L1
Return ~ms"] L2 -->|"Miss"| Factory["Factory Method
(DB Query)"] Factory --> PopBoth["Populate L1 + L2
Return"] style Request fill:#e94560,stroke:#fff,color:#fff style HC fill:#2c3e50,stroke:#fff,color:#fff style L1 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style L2 fill:#f8f9fa,stroke:#2196F3,color:#2c3e50 style Factory fill:#f8f9fa,stroke:#ff9800,color:#2c3e50 style Return fill:#4CAF50,stroke:#fff,color:#fff style PopL1 fill:#4CAF50,stroke:#fff,color:#fff style PopBoth fill:#4CAF50,stroke:#fff,color:#fff

Hình 3: HybridCache flow — L1 memory → L2 Redis → Factory, stampede protection ở mỗi tầng

// Program.cs — setup HybridCache
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(30),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
    options.MaximumPayloadBytes = 1024 * 1024; // 1MB limit
});

// Thêm Redis làm L2 backend
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

// Sử dụng — API sạch, tự serialize, có stampede protection
public class ProductService(HybridCache cache, IProductRepository repo)
{
    public async Task<Product?> GetByIdAsync(int id,
        CancellationToken ct = default)
    {
        return await cache.GetOrCreateAsync(
            $"product:{id}",
            async cancel => await repo.GetByIdAsync(id, cancel),
            new HybridCacheEntryOptions
            {
                Expiration = TimeSpan.FromMinutes(30),
                LocalCacheExpiration = TimeSpan.FromMinutes(5)
            },
            tags: ["products", $"product:{id}"],
            cancellationToken: ct
        );
    }

    public async Task UpdateAsync(Product product,
        CancellationToken ct = default)
    {
        await repo.UpdateAsync(product, ct);
        // Invalidate bằng tag — xóa tất cả entry liên quan
        await cache.RemoveByTagAsync($"product:{product.Id}", ct);
    }
}
Tính năngIMemoryCacheIDistributedCacheHybridCache
L1 (in-process)
L2 (distributed)
Stampede protection
Tag-based invalidation
Auto serializationN/A (object ref)❌ (byte[])
Multi-instance safe
Latency (hit)~ns~ms~ns (L1) / ~ms (L2)

6. Tầng 4: Database Cache — tối ưu ở nguồn dữ liệu

Dù application cache chặn được phần lớn request, vẫn luôn có request rơi xuống database. Tối ưu tầng này giúp giảm latency cho cache miss và giảm áp lực khi cache bị invalidate hàng loạt.

6.1. Query Plan Cache

SQL Server và PostgreSQL đều cache execution plan cho các prepared statement. Dùng parameterized query thay vì string concatenation không chỉ chống SQL injection mà còn cho phép database tái sử dụng plan đã compile:

// ❌ Mỗi giá trị id tạo plan mới
var sql = $"SELECT * FROM Products WHERE Id = {id}";

// ✅ Plan được cache và tái sử dụng
var sql = "SELECT * FROM Products WHERE Id = @Id";
cmd.Parameters.AddWithValue("@Id", id);

6.2. Materialized View — pre-computed data

Thay vì chạy aggregate query phức tạp mỗi lần, Materialized View lưu kết quả đã tính sẵn. PostgreSQL hỗ trợ REFRESH MATERIALIZED VIEW CONCURRENTLY cho phép refresh không block read. SQL Server dùng Indexed View với tương tự mục đích.

-- PostgreSQL: Tạo materialized view cho dashboard stats
CREATE MATERIALIZED VIEW product_stats AS
SELECT
    category_id,
    COUNT(*) as total_products,
    AVG(price) as avg_price,
    MAX(updated_at) as last_updated
FROM products
GROUP BY category_id;

-- Refresh song song, không block read
REFRESH MATERIALIZED VIEW CONCURRENTLY product_stats;

7. Cache Invalidation — "chỉ có hai thứ khó trong CS"

Phil Karlton từng nói: "Chỉ có hai thứ khó trong khoa học máy tính: invalidation cache và đặt tên biến." Hiểu rõ 5 chiến lược invalidation giúp bạn chọn đúng pattern cho từng use case:

flowchart TB
    subgraph CacheAside["Cache-Aside (Lazy Loading)"]
        CA1["App kiểm tra cache"] --> CA2{"Hit?"}
        CA2 -->|"Yes"| CA3["Trả về"]
        CA2 -->|"No"| CA4["Query DB"]
        CA4 --> CA5["Ghi vào cache"]
        CA5 --> CA3
    end

    subgraph WriteThrough["Write-Through"]
        WT1["App ghi data"] --> WT2["Ghi vào cache"]
        WT2 --> WT3["Cache ghi vào DB"]
        WT3 --> WT4["Confirm"]
    end

    subgraph WriteBehind["Write-Behind (Write-Back)"]
        WB1["App ghi data"] --> WB2["Ghi vào cache"]
        WB2 --> WB3["Confirm ngay"]
        WB2 -.->|"Async"| WB4["Cache ghi DB sau"]
    end

    style CacheAside fill:#f8f9fa,stroke:#e94560
    style WriteThrough fill:#f8f9fa,stroke:#4CAF50
    style WriteBehind fill:#f8f9fa,stroke:#2196F3

Hình 4: Ba pattern chính của cache write strategy

PatternRead perfWrite perfConsistencyPhù hợp với
Cache-AsideChậm lần đầu (cold start)Nhanh (chỉ ghi DB)Eventual95% use case — mặc định nên dùng
Read-ThroughNhư Cache-Aside nhưng cache tự fetchN/AEventualKhi muốn cache layer tự quản lý
Write-ThroughLuôn nhanh (data có sẵn)Chậm hơn (ghi 2 nơi sync)StrongData quan trọng cần nhất quán cao
Write-BehindLuôn nhanhRất nhanh (async)Eventual (rủi ro mất data)High-write throughput, chấp nhận risk
Write-AroundChậm lần đầuNhanh (bypass cache)EventualData ít đọc lại sau khi ghi

Khung chọn pattern nhanh

Mặc định: Cache-Aside + TTL safety net. 95% ứng dụng web dùng pattern này là đủ.
Nếu cần strong consistency: Write-Through (ví dụ: inventory count, account balance).
Nếu write throughput là ưu tiên: Write-Behind (ví dụ: analytics event, view count).
Nếu data ít đọc lại: Write-Around (ví dụ: audit log, notification history).

8. Cache Stampede & Thundering Herd — phòng thủ chủ động

Cache stampede xảy ra khi một cache entry phổ biến hết hạn, hàng trăm request đồng thời phát hiện cache miss và đồng loạt query database. Đây là nguyên nhân hàng đầu gây sập hệ thống liên quan tới caching.

8.1. Các kỹ thuật phòng chống

// Kỹ thuật 1: Mutex/Lock — chỉ 1 request fetch, còn lại chờ
private static readonly SemaphoreSlim _lock = new(1, 1);

public async Task<Product?> GetWithLockAsync(int id)
{
    var key = $"product:{id}";
    if (_cache.TryGetValue(key, out Product? cached))
        return cached;

    await _lock.WaitAsync();
    try
    {
        // Double-check sau khi acquire lock
        if (_cache.TryGetValue(key, out cached))
            return cached;

        var product = await _repo.GetByIdAsync(id);
        _cache.Set(key, product, TimeSpan.FromMinutes(30));
        return product;
    }
    finally { _lock.Release(); }
}

// Kỹ thuật 2: Probabilistic Early Expiration
// Cache entry "tự giác" refresh trước khi hết hạn thực sự
public async Task<Product?> GetWithEarlyRefreshAsync(int id)
{
    var key = $"product:{id}";
    var entry = _cache.Get<CacheEntry<Product>>(key);

    if (entry is not null)
    {
        var timeToExpiry = entry.ExpiresAt - DateTime.UtcNow;
        var totalTtl = entry.Ttl;
        // Xác suất refresh tăng dần khi gần hết hạn
        var probability = Math.Exp(-timeToExpiry / totalTtl * 10);
        if (Random.Shared.NextDouble() > probability)
            return entry.Value;
    }

    // Fetch và cache lại
    var product = await _repo.GetByIdAsync(id);
    // ... set cache
    return product;
}

HybridCache giải quyết stampede tự động

Với HybridCache.GetOrCreateAsync, khi 100 request đồng thời gọi cùng key, chỉ đúng 1 request chạy factory method (query DB), 99 request còn lại chờ kết quả. Không cần SemaphoreSlim, không cần double-check pattern. Đây là lý do chính để migrate từ IMemoryCache sang HybridCache.

9. Monitoring & Observability cho Cache

Cache không có monitoring giống như lái xe bịt mắt — bạn không biết mình đang tiết kiệm hay lãng phí. Ba metrics quan trọng nhất:

Hit RateTỷ lệ cache hit / tổng request. Mục tiêu: > 90%
Eviction RateSố entry bị đuổi/giây. Cao = cần tăng dung lượng
Latency P99Thời gian đọc cache percentile 99. L1 < 1ms, L2 < 10ms
Memory Usage% memory dùng cho cache. Tránh OOM bằng SizeLimit
// Expose cache metrics qua OpenTelemetry
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddMeter("MyApp.Cache");
    });

// Custom meter cho cache
public class CacheMetrics
{
    private static readonly Meter _meter = new("MyApp.Cache");
    private static readonly Counter<long> _hits =
        _meter.CreateCounter<long>("cache.hits");
    private static readonly Counter<long> _misses =
        _meter.CreateCounter<long>("cache.misses");
    private static readonly Histogram<double> _duration =
        _meter.CreateHistogram<double>("cache.duration.ms");

    public void RecordHit(string cacheLayer) =>
        _hits.Add(1, new("layer", cacheLayer));
    public void RecordMiss(string cacheLayer) =>
        _misses.Add(1, new("layer", cacheLayer));
    public void RecordDuration(string cacheLayer, double ms) =>
        _duration.Record(ms, new("layer", cacheLayer));
}

10. Production Checklist

Trước khi deploy hệ thống có caching, hãy kiểm tra danh sách sau:

#Hạng mụcGhi chú
1TTL luôn được setKhông bao giờ cache vĩnh viễn — dù chỉ là config. Set cả absolute và sliding expiration.
2Cache key có namespacePrefix bằng tên service + version: myapp:v2:product:123. Tránh collision khi nhiều service dùng chung Redis.
3Stampede protectionDùng HybridCache hoặc implement mutex. Không để cache miss fan-out ra database.
4Serialization format nhất quánChọn JSON hoặc MessagePack và giữ nguyên. Thay đổi format = invalidate toàn bộ cache.
5Circuit breaker cho cache layerKhi Redis down, fallback về database trực tiếp — không để timeout cache block request.
6Monitoring đầy đủHit rate, eviction rate, memory usage, latency. Alert khi hit rate < 80%.
7Không cache PII/sensitive dataHoặc nếu phải cache thì encrypt + TTL ngắn. Audit trail cho cache access.
8Warm-up strategySau deploy mới, cache lạnh. Cân nhắc pre-warm hot keys hoặc canary deploy.

Quy tắc vàng của caching

Cache data được đọc nhiều hơn ghi. Nếu write/read ratio > 50%, caching có thể gây overhead nhiều hơn lợi ích — bạn sẽ tốn thời gian invalidation hơn thời gian tiết kiệm được từ cache hit. Trong trường hợp này, hãy tối ưu database trực tiếp (indexing, query optimization, connection pooling) thay vì thêm cache layer.

11. Tổng kết

Caching đa tầng không phải là "thêm Redis vào giữa app và DB". Đó là việc thiết kế một hệ thống phòng thủ nhiều lớp, mỗi lớp có vai trò, trade-off, và chiến lược invalidation riêng. Browser cache giảm network request, CDN giảm latency địa lý, application cache giảm database load, và database cache tối ưu ở nguồn dữ liệu.

Với HybridCache trong .NET 10, Microsoft đã giải quyết phần lớn complexity của application caching: L1/L2 tự động, stampede protection built-in, tag-based invalidation, và API sạch sẽ. Nếu bạn đang dùng IMemoryCache hoặc IDistributedCache riêng lẻ, đây là thời điểm tốt để migrate.

Nhớ rằng: cache tốt nhất là cache bạn không cần. Trước khi thêm cache, hãy hỏi: dữ liệu này có thực sự được đọc nhiều không? Query có thể tối ưu trực tiếp không? Cache chỉ nên là giải pháp sau khi bạn đã tối ưu ở tầng dữ liệu.

12. Tham khảo