Many applications repeatedly retrieve:

When the same data is queried thousands of times per minute, the impact becomes clear:

1. Database CPU Rises Quickly

Repeated reads on small lookup tables often account for 40–60 percent of CPU usage in OLTP systems.

2. Network Round-Trips Add Latency

Even a fast query becomes costly when repeated millions of times.

3. Locks And Latches Slow Everything

Repeated selects can still escalate into contention on hot pages.

4. Downstream Microservices Become Slow

Distributed lookups cause cascading latency.

Cache-aware design attempts to solve these problems at the root, not by tweaking indexes or optimizing slow queries individually.

What Is Cache-Aware Design?

Cache-aware design means:

It is not simply “put Redis in front of SQL Server.”
It is a holistic design approach.

Types Of Lookups That Benefit Most From Caching

The following lookup patterns benefit the most:

1. High-Frequency, Low-Volatility Data

Examples:
Role permissions, state master, currency metadata, country list.

2. Read-Mostly Entities

Examples:
Product details, pricing slabs updated once a day, configuration settings.

3. Pre-Computation Check Values

Examples:
Pre-approved promo codes, ledger account validity, routing rules.

4. Complex Computation Results

If the computation is deterministic and repeated, cache it.

The Four Layers Of a Cache-Aware Architecture

Here is a typical multi-layer caching design:

+---------------------------+
|        Client Cache       | (Browser, Mobile, Angular services) - L0
+---------------------------+
              |
              v
+---------------------------+
|     Application Cache     | (MemoryCache, IMemoryCache) - L1
+---------------------------+
              |
              v
+---------------------------+
|    Distributed Cache      | (Redis, Memcached) - L2
+---------------------------+
              |
              v
+---------------------------+
|        SQL Database       | (Authoritative Store)
+---------------------------+

L0: Client-Side Caching (Angular Focus)

Angular helps with high-frequency lookups through:

Example Angular service for caching lookups:

@Injectable({ providedIn: 'root' })
export class LookupService {
  private cache = new Map<string, any>();

  constructor(private http: HttpClient) {}

  getCurrencyCodes(): Observable<any> {
    const key = 'currencyCodes';

    if (this.cache.has(key)) {
      return of(this.cache.get(key));
    }

    return this.http.get('/api/lookups/currencyCodes').pipe(
      tap(result => this.cache.set(key, result))
    );
  }
}

This prevents repeated network calls within the same session.

L1: In-Memory Cache (ASP.NET Core)

In-process memory caching is extremely fast and ideal for:

Example:

public async Task<List<Country>> GetCountriesAsync()
{
    return await _cache.GetOrCreateAsync("countries", async entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6);
        return await _db.Countries.AsNoTracking().ToListAsync();
    });
}

L2: Distributed Cache (Redis)

Distributed cache is designed for:

Redis is ideal when you need:

Designing Optimal Cache Keys

A cache-aware system uses human-friendly, pattern-based keys:

lookup:country:all
lookup:country:IN
lookup:roles:admin
lookup:ledger:code:4001
config:paymentgateway:stripe
pricing:slab:gold

Principles:

Example versioned key:

lookup:country:v2:all

Query Flow When Cache Is Enabled

Request -> Check L1 Memory Cache -> If miss -> Check Redis (L2)
     -> If miss -> Query SQL -> Update Redis -> Update L1 -> Return Result

Expanded:

+------------------+
|   API Request    |
+------------------+
          |
          v
+-----------------------------+
| L1 In-Memory Cache (Fastest)|
+-----------------------------+
          |
      MISS|
          v
+-----------------------------+
|   Redis Distributed Cache   |
+-----------------------------+
          |
      MISS|
          v
+-----------------------------+
|       SQL Database          |
+-----------------------------+

Detecting When Your System Needs Caching

Check for these indicators:

1. Excessive Reads On Small Reference Tables

If a 100-row master table is queried 10,000 times per minute, caching is mandatory.

2. CPU Saturation On Simple Lookup Queries

Query complexity does not matter; volume overwhelms SQL CPU.

3. Duplicate Queries Across Application Layers

E.g., repeated permission checks across many API endpoints.

4. High Network Latency

If calls travel across regions, caching gives immediate performance gains.

Cache Invalidation Strategies That Actually Work

Cache invalidation is the hardest part. These strategies are reliable:

1. Time-To-Live (TTL)

Safe and simple when data changes infrequently.

2. Event-Based Invalidation

Use SQL dependency or triggers to publish updates to Redis channels.

Example:

UPDATE Country SET Name = @NewName WHERE Code = 'IN';
PUBLISH redis:invalidate 'lookup:country:IN';

3. Versioned Keys

When the structure changes, bump a version.

4. Soft Refresh

If TTL expires, return stale data and refresh asynchronously.

High-Frequency Lookup Optimization Patterns

Pattern 1: Lookup Pre-Loading On App Startup

Preload lookup sets into memory when application starts.

Pattern 2: Lookup Context Flow (Per-Request)

Collect all required lookups once and share across services, instead of querying repeatedly.

Pattern 3: Cache With Lazy Refresh

Caches refresh only when needed, not on every expiry.

Pattern 4: Cache Stampede Prevention

Use distributed locking so that only one server refreshes an expired key.

Example: Optimizing Ledger Code Validations

Scenario:

A financial system checks ledger codes for every posting.
Each posting results in:

Instead of three database calls, use a composite Redis key:

ledger:validation:set

Store:

Then one in-memory call handles all checks.

Example: Optimizing E-Commerce Pricing Lookups

A pricing service requires:

All can be cached as:

pricing:v1:product:{productId}
pricing:v1:tax:{stateCode}
pricing:v1:tier:{tierId}

TTL: 30 minutes
Version changes when rules change.

Cache Behavior Under Load (Illustration)

High Throughput Scenario:
100,000 requests/min

Without Cache:
100,000 DB reads
=> High CPU
=> High network latency
=> Contention on hot tables

With Cache:
L1 Cache Hits: 80,000
L2 Redis Hits: 15,000
DB Reads: 5,000
=> Databases stay cold
=> Application runs fast
=> No contention

Common Mistakes In Cache Design

How To Measure Cache Effectiveness

Track:

Reference Architecture For Cache-Aware Systems

+--------------------------------------------------------+
|                 Global Discovery Layer                 |
|     (Feature Flags, Routing, Rate Limits, Config)      |
+----------------------------+---------------------------+
                             |
                             v
+--------------------------------------------------------+
|              Distributed Cache (Redis Cluster)         |
+--------------------------------------------------------+
                |                           |
                v                           v
+-------------------------------+   +-------------------------------+
|    Application Server API 1   |   |    Application Server API 2   |
|  L1 Memory Cache + Lookup ctx |   |  L1 Memory Cache + Lookup ctx |
+-------------------------------+   +-------------------------------+
                |                           |
                -----------SQL Database------

When To Skip Caching

Caching is not useful when:

For these, design alternatives include:

Final Recommendations

Cache-aware design is not optional for modern, high-scale systems.
It is essential for:

A good caching strategy uses:

Once implemented, high-frequency lookup performance improves 10–100x.