I recently worked on an SAP CAP application that involves a master data entity, which contains more than 1 million records. This dataset is rarely updated but is frequently accessed, making it a read-heavy scenario. Given the size of the data and the frequency of queries, I quickly realized that caching could be a game-changer in improving the performance of the application, particularly for read-intensive operations.
In this blog, I’ll walk you through how I addressed this issue in my SAP Cloud Application Programming Model (CAP) application by integrating the cds-caching plugin. The solution significantly improved performance for both read-intensive operations (like querying large datasets) and external API calls, reducing load on both the backend and the external systems while enhancing overall responsiveness.
The cds-caching plugin for SAP Cloud Application Programming (CAP) is designed to enhance the performance of CAP applications by providing efficient and flexible caching capabilities.
Why Caching?
Caching is an ideal solution for scenarios where data is accessed frequently but changes infrequently. This includes not only read-heavy operations on large datasets but also external API integrations that might be slow or have rate limits. Caching helps reduce load on the underlying systems by storing frequently accessed data in memory, allowing for faster retrieval.
Implementing Caching with cds-caching
Here’s how I implemented the cds-caching plugin to optimize performance for both internal database queries and external API calls:
Define the Entity:
The Pincode entity, which holds data such as Pin Code, Village Name, District Name, and State Name, is defined:
namespace MasterData;
using { cuid } from ‘@sap/cds/common’;
entity Pincode : cuid {
: {Label: ‘Pin Code’}
Pincode : String;
: {Label: ‘Village/Locality Name’}
VillageName : String;
: {Label: ‘District Name’}
DistrictName : String;
: {Label: ‘State Name’}
StateName : String;
}
Service Definition:
The Pincode entity is exposed via a service:
using { MasterData.Pincode } from ‘../db/MasterDataSchema’;
service MasterDataService {
entity Pincodes as projection on Pincode;
}
Installing and using cds-caching is straightforward since it’s a CAP plugin. Simply run:
npm install cds-caching
Setup Configuration for cds-caching in package.json.. I am running it locally but this can be easily integrated with redis for production.
“cds”: {
“requires”: {
“caching”: {
“impl”: “cds-caching”,
“store”: “in-memory”,
“namespace”: “masterdata::app::caching”
}
}
}
Caching Logic Implementation:
key: This is the unique identifier for the data being requested. It represents a query for a specific set of Pincode records.
const cds = require(“@sap/cds”);
module.exports = async (srv) => {
const cache = await cds.connect.to(“caching”);
srv.on(“READ”, “Pincodes”, async (request, next) => {
console.log(“Time Stamp:”, new Date().toLocaleTimeString());
let key = `Pincodes_${JSON.stringify(request.req.query)}`;
console.log(“key:”, key);
// Attempt to get data from the cache
const cachedResults = await cache.get(key);
if (!cachedResults) {
let results = await next(); // Fetch data from database or external API if cache miss
await cache.set(key, results, { ttl: 36000 });
console.log(“Cache miss…Delivering from Database or API”);
return results;
}
console.log(“Cache Hit!!!..Delivering from cache”);
return cachedResults; // Return data from cache if cache hit
});
};
Thats it.. Start the server …
Now Magic Begins :
Time Stamp: 3:17:25 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache miss…Delivering from Database
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:17:33 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:17:54 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
First Request: The data for the first 90 records isn’t in the cache, so the application fetches it from the database (cache miss).Second Request: The same data is requested again, and this time it is found in the cache, so it is served from there (cache hit).Third Request: The data is still in the cache and is served directly from it (cache hit again).
The caching mechanism helps avoid repeated database queries for the same data, improving performance by serving data faster from memory (cache) after the first request.
What is TTL (Time To Live)?
TTL is a setting used in caching that defines how long a cached item remains in the cache before it is considered stale and needs to be re-fetched (either from the cache again or from the original data source, such as a database). It helps to ensure that the data is not out-of-date while also avoiding unnecessary database calls. In my case it is one minute.
TTL in Action:
Time Stamp: 3:17:54 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:18:05 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache miss…Delivering from Database
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:18:11 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
Subsequent Request (3:17:33 PM): The data is now in the cache and available, so a cache hit occurs.TTL Expiry (3:18:05 PM): After a certain period, the cached data expires (due to TTL), resulting in a cache miss and a re-fetch from the database.Re-caching (3:18:11 PM): The new data fetched from the database is cached again and available for subsequent requests.
Performance Improvement Metric:
Initial Response Time: 794ms
Full cycle of database query, network communication, and application processing.
Cached Response Time: 18ms
Direct memory access from cache, significantly reducing query and network latency.
Performance Improvement:
From 794ms to 18ms
((794ms – 18ms) / 794ms) * 100 = 97.73% in response time .
I have tried with multiple usual behavior scenarios such filtering and scrolling through the records and the results were amazing.
Thanks to @mike_zaschka for your wonderful contribution.
Regards,
Naveen Chandar Sivakumar
references:
Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching
I recently worked on an SAP CAP application that involves a master data entity, which contains more than 1 million records. This dataset is rarely updated but is frequently accessed, making it a read-heavy scenario. Given the size of the data and the frequency of queries, I quickly realized that caching could be a game-changer in improving the performance of the application, particularly for read-intensive operations.In this blog, I’ll walk you through how I addressed this issue in my SAP Cloud Application Programming Model (CAP) application by integrating the cds-caching plugin. The solution significantly improved performance for both read-intensive operations (like querying large datasets) and external API calls, reducing load on both the backend and the external systems while enhancing overall responsiveness.The cds-caching plugin for SAP Cloud Application Programming (CAP) is designed to enhance the performance of CAP applications by providing efficient and flexible caching capabilities.Why Caching?Caching is an ideal solution for scenarios where data is accessed frequently but changes infrequently. This includes not only read-heavy operations on large datasets but also external API integrations that might be slow or have rate limits. Caching helps reduce load on the underlying systems by storing frequently accessed data in memory, allowing for faster retrieval.Implementing Caching with cds-cachingHere’s how I implemented the cds-caching plugin to optimize performance for both internal database queries and external API calls:Define the Entity:The Pincode entity, which holds data such as Pin Code, Village Name, District Name, and State Name, is defined: namespace MasterData;
using { cuid } from ‘@sap/cds/common’;
entity Pincode : cuid {
: {Label: ‘Pin Code’}
Pincode : String;
: {Label: ‘Village/Locality Name’}
VillageName : String;
: {Label: ‘District Name’}
DistrictName : String;
: {Label: ‘State Name’}
StateName : String;
} Service Definition:The Pincode entity is exposed via a service: using { MasterData.Pincode } from ‘../db/MasterDataSchema’;
service MasterDataService {
entity Pincodes as projection on Pincode;
} Installing and using cds-caching is straightforward since it’s a CAP plugin. Simply run: npm install cds-caching Setup Configuration for cds-caching in package.json.. I am running it locally but this can be easily integrated with redis for production. “cds”: {
“requires”: {
“caching”: {
“impl”: “cds-caching”,
“store”: “in-memory”,
“namespace”: “masterdata::app::caching”
}
}
} Caching Logic Implementation:key: This is the unique identifier for the data being requested. It represents a query for a specific set of Pincode records. const cds = require(“@sap/cds”);
module.exports = async (srv) => {
const cache = await cds.connect.to(“caching”);
srv.on(“READ”, “Pincodes”, async (request, next) => {
console.log(“Time Stamp:”, new Date().toLocaleTimeString());
let key = `Pincodes_${JSON.stringify(request.req.query)}`;
console.log(“key:”, key);
// Attempt to get data from the cache
const cachedResults = await cache.get(key);
if (!cachedResults) {
let results = await next(); // Fetch data from database or external API if cache miss
await cache.set(key, results, { ttl: 36000 });
console.log(“Cache miss…Delivering from Database or API”);
return results;
}
console.log(“Cache Hit!!!..Delivering from cache”);
return cachedResults; // Return data from cache if cache hit
});
}; Thats it.. Start the server …Now Magic Begins : Time Stamp: 3:17:25 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache miss…Delivering from Database
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:17:33 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:17:54 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
} First Request: The data for the first 90 records isn’t in the cache, so the application fetches it from the database (cache miss).Second Request: The same data is requested again, and this time it is found in the cache, so it is served from there (cache hit).Third Request: The data is still in the cache and is served directly from it (cache hit again).The caching mechanism helps avoid repeated database queries for the same data, improving performance by serving data faster from memory (cache) after the first request.What is TTL (Time To Live)?TTL is a setting used in caching that defines how long a cached item remains in the cache before it is considered stale and needs to be re-fetched (either from the cache again or from the original data source, such as a database). It helps to ensure that the data is not out-of-date while also avoiding unnecessary database calls. In my case it is one minute.TTL in Action: Time Stamp: 3:17:54 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:18:05 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache miss…Delivering from Database
[odata] – POST /odata/v4/master-data/$batch
[odata] – > READ /Pincodes {
‘$count’: ‘true’,
‘$select’: ‘DistrictName,ID,Pincode,StateName,VillageName’,
‘$skip’: ‘0’,
‘$top’: ’90’
}
Time Stamp: 3:18:11 PM
key: Pincodes_{“$count”:”true”,”$select”:”DistrictName,ID,Pincode,StateName,VillageName”,”$skip”:”0″,”$top”:”90″}
Cache Hit!!!..Delivering from cache Subsequent Request (3:17:33 PM): The data is now in the cache and available, so a cache hit occurs.TTL Expiry (3:18:05 PM): After a certain period, the cached data expires (due to TTL), resulting in a cache miss and a re-fetch from the database.Re-caching (3:18:11 PM): The new data fetched from the database is cached again and available for subsequent requests.Performance Improvement Metric:Initial Response Time: 794msFull cycle of database query, network communication, and application processing.Cached Response Time: 18msDirect memory access from cache, significantly reducing query and network latency.Performance Improvement:From 794ms to 18ms((794ms – 18ms) / 794ms) * 100 = 97.73% in response time .I have tried with multiple usual behavior scenarios such filtering and scrolling through the records and the results were amazing. Thanks to @mike_zaschka for your wonderful contribution.Regards,Naveen Chandar Sivakumarreferences:Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching Read More Technology Blogs by Members articles
#SAP
#SAPTechnologyblog