Architecting Apex Platform Cache for High-Volume Data Retrieval
Learn how to implement a Cache Service wrapper in Apex to reduce SOQL consumption and avoid governor limits using Platform Cache Org and Session scopes.
30 Jun 2026, 19:15 UTC

The Governor Limit Bottleneck
In high-volume Salesforce environments, repetitive SOQL queries for static or semi-static data—such as custom settings, complex pricing tables, or organizational metadata—often lead to LimitException: Too many SOQL queries: 101. While caching is the standard solution, implementing it directly within business logic creates tight coupling and makes cache invalidation difficult to manage.
The goal is to decouple data retrieval from the database, reducing CPU time and SOQL consumption by utilizing the Platform Cache, a distributed key-value store available in two scopes: Org Cache (shared across all users) and Session Cache (unique to a specific user session).
The Smallest Suitable Design: The Cache Service Wrapper
To prevent cache-miss logic from polluting the business layer, implement a Cache Service wrapper. This pattern ensures that the calling code does not need to know whether data is coming from the database or the cache.
Implementation Example
The following pattern demonstrates a generic wrapper for retrieving a list of records based on a unique key. This example assumes a Platform Cache partition named DataCache has been created in Setup.
public class CacheService {
private static final String PARTITION = 'DataCache';
public static List<Account> getAccountsByRegion(String region) {
String key = 'Accounts_' + region;
// Attempt to retrieve from Org Cache
List<Account> cachedData = (List<Account>) Cache.Org.get(PARTITION, key);
if (cachedData != null) {
return cachedData;
}
// Cache Miss: Retrieve from DB
List<Account> dbData = [SELECT Id, Name FROM Account WHERE Region__c = :region];
// Store in cache for 30 minutes
Cache.Org.put(PARTITION, key, dbData, 1800);
return dbData;
}
}
Trust and Data Boundaries
When choosing between Org and Session cache, the primary boundary is data visibility. Using the wrong scope can lead to severe security vulnerabilities or data leakage.
- Org Cache: Use only for data that is identical for every user in the organization. If the data is filtered by User Permissions or Sharing Rules, Org Cache is inappropriate.
- Session Cache: Use for user-specific preferences or temporary calculation results. To prevent cross-user leakage, ensure keys are prefixed with the User ID or a session-unique identifier.
Operational Checks and Verification
Platform Cache is not a permanent storage solution; it is a volatile memory layer. You must monitor its health to ensure it is providing a performance benefit rather than adding overhead.
Verification Steps
- Partition Monitoring: Navigate to Setup > Platform Cache. Monitor the "Usage" percentage. If usage consistently hits 100%, the system will trigger Least Recently Used (LRU) eviction, causing more frequent cache misses and increasing DB load.
- Query Comparison: Run a transaction in the Developer Console. Compare the
Number of SOQL queriesin the debug log for the first run (cold start) versus the second run (warm cache). - Session Isolation: Log in as two different users. Verify that data stored in
Cache.Sessionfor User A is not accessible to User B.
Failure Modes and Risks
Implementing a cache introduces new failure vectors that do not exist in standard CRUD operations.
The Cache Stampede
A "cache stampede" occurs when a high-traffic key expires. Multiple concurrent requests find the cache empty simultaneously, and all trigger the same expensive SOQL query. This can spike CPU usage and potentially lock database rows.
Stale Data (Consistency)
Platform Cache is not transactional. If a record is updated via the UI or an API call, the cache entry remains unchanged until it expires or is manually removed using Cache.Org.remove(). This creates a window of inconsistency where the application reads outdated information.
Design Evolution: When to Pivot
The current design of a simple wrapper is sufficient for low-volatility data. However, you must change the architecture if the following conditions occur:
- High Volatility: If data changes every few seconds, the overhead of
putoperations and the risk of stale data outweigh the benefits. Shift to a short-lived Session cache or remove caching entirely. - Large Payloads: If the cached objects are large, you will exhaust your partition size quickly. Pivot to caching only the IDs of the records and performing a targeted query for the remaining fields.
- Complex Invalidation: If you need real-time consistency, implement a trigger-based invalidation strategy that calls
Cache.Org.remove()whenever the source record is updated.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.