cds-caching 0.3.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +691 -278
- package/cds-plugin.js +2 -2
- package/index.cds +165 -20
- package/lib/CachingService.d.ts +328 -0
- package/lib/CachingService.js +373 -0
- package/lib/operations/AsyncOperations.js +335 -0
- package/lib/operations/BasicOperations.js +211 -0
- package/lib/operations/CapOperations.js +540 -0
- package/lib/support/CacheStatisticsHandler.js +1124 -0
- package/lib/support/CacheStoreManager.js +120 -0
- package/lib/support/KeyManager.js +132 -0
- package/lib/support/RuntimeConfigurationManager.js +148 -0
- package/lib/support/StatisticsPersistenceManager.js +375 -0
- package/lib/support/TagResolver.js +125 -0
- package/lib/util.js +201 -0
- package/package.json +18 -13
- package/srv/caching-api-service.js +124 -0
- package/srv/CacheStatisticsHandler.js +0 -213
- package/srv/CachingService.js +0 -587
- package/srv/statistics-service.cds +0 -37
- package/srv/statistics-service.js +0 -72
- package/srv/util.js +0 -101
package/README.md
CHANGED
|
@@ -1,39 +1,155 @@
|
|
|
1
1
|
# Welcome to cds-caching
|
|
2
|
+
[](https://www.npmjs.com/package/cds-caching/common)
|
|
3
|
+
[](https://www.npmjs.com/package/cds-caching)
|
|
2
4
|
|
|
3
5
|
## Overview
|
|
4
6
|
|
|
5
7
|
This plugin for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap/docs/) provides a caching service to improve performance in CAP applications.
|
|
8
|
+
|
|
6
9
|
While CAP in general performs well for most use cases, caching can help with:
|
|
7
10
|
- Slow remote service calls
|
|
8
|
-
- Complex
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
+
- Complex operations
|
|
12
|
+
- Slow queries
|
|
13
|
+
- Other performance bottle necks
|
|
11
14
|
|
|
12
|
-
While caching can
|
|
15
|
+
While cds-caching can be a big helper, an additional caching layer also adds complexity and should be used judiciously.
|
|
13
16
|
|
|
14
17
|
Please also read the introduction blog post in the SAP Community: [Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching](https://community.sap.com/t5/technology-blogs-by-members/boosting-performance-in-sap-cloud-application-programming-model-cap/ba-p/14002015).
|
|
15
18
|
|
|
16
|
-
### Caching vs. Replication
|
|
17
|
-
|
|
18
|
-
It's important to understand the difference between **caching** and **data replication**:
|
|
19
|
-
|
|
20
|
-
* **Caching** temporarily stores data to reduce latency and improve response times. It's ideal for read-heavy workloads but does not maintain data integrity or understand data semantics.
|
|
21
|
-
|
|
22
|
-
* **Replication** creates full, persistent copies of remote data within your application to ensure availability and enable seamless data sharing across systems. It focuses on resilience rather than performance optimization.
|
|
23
|
-
|
|
24
|
-
cds-caching is specifically designed for efficient caching, not data replication.
|
|
25
|
-
|
|
26
19
|
### Key Features
|
|
27
20
|
|
|
28
21
|
* **Flexible Key-Value Store** – Store and retrieve data using simple key-based access.
|
|
29
22
|
* **CachingService** – A cds.Service implementation with an intuitive API for seamless integration into CAP.
|
|
30
|
-
* **
|
|
23
|
+
* **Read-Through Capabilities** – Let the caching service handle the cache set and get operatios for you
|
|
31
24
|
* **CAP-specific Caching** – Effortlessly cache CQN queries or CAP cds.Requests using code or the @cache annotation.
|
|
32
25
|
* **TTL Support** – Automatically manage data expiration with configurable time-to-live (TTL) settings.
|
|
33
26
|
* **Tag Support** – Use dynamic tags for flexible cache invalidation options.
|
|
34
27
|
* **Pluggable Storage Options** – Choose between in-memory caching, SQLite or Redis.
|
|
35
28
|
* **Compression** – Compress cached data to save memory using LZ4 or GZIP.
|
|
36
|
-
* **Integrated
|
|
29
|
+
* **Integrated Metrics** – Monitor cache performance with hit rates, latencies, and more.
|
|
30
|
+
* **API** – Access basic cache operations and metrics via API
|
|
31
|
+
* **Event Handling** – Monitor and react to cache events, such as before/after storage and retrieval.
|
|
32
|
+
|
|
33
|
+
### Checkout detailed information on how to use cds-caching
|
|
34
|
+
|
|
35
|
+
> - [Programmatic API](docs/programmatic-api.md)
|
|
36
|
+
> - [Key Management](docs/key-management.md)
|
|
37
|
+
> - [Metrics Guide](docs/metrics-guide.md)
|
|
38
|
+
> - [OData API Reference](docs/odata-api.md)
|
|
39
|
+
|
|
40
|
+
## 🚨 Breaking Changes: Migrating from cds-caching 0.x
|
|
41
|
+
|
|
42
|
+
> **⚠️ Important:** Version 1.x contains breaking changes. Please review the migration guide below.
|
|
43
|
+
|
|
44
|
+
### 🔄 API Changes for read-through methods
|
|
45
|
+
|
|
46
|
+
Version 1.x introduces new methods that provide more insights into the read-through caching as they also directly return the genrated cache `key` and some caching `metadata`. The should be preferrably used instead of the old methods.
|
|
47
|
+
|
|
48
|
+
| **Old Method** | **New Method** | **Key Differences** |
|
|
49
|
+
|----------------|----------------|---------------------|
|
|
50
|
+
| `cache.run()` | `cache.rt.run()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
51
|
+
| `cache.send()` | `cache.rt.send()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
52
|
+
| `cache.wrap()` | `cache.rt.wrap()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
53
|
+
| `cache.exec()` | `cache.rt.exec()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
54
|
+
|
|
55
|
+
### 🔑 Key Template Changes
|
|
56
|
+
|
|
57
|
+
**Before (0.x):**
|
|
58
|
+
```javascript
|
|
59
|
+
// Old syntax - object with template property
|
|
60
|
+
await cache.set(query, result, {
|
|
61
|
+
key: { template: "user:{user}:{hash}" }
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**After (1.x):**
|
|
66
|
+
```javascript
|
|
67
|
+
// New syntax - direct string template
|
|
68
|
+
await cache.set(query, result, {
|
|
69
|
+
key: "user:{user}:{hash}"
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 🌍 Context Awareness Changes
|
|
74
|
+
|
|
75
|
+
**Default Behavior Changed:**
|
|
76
|
+
- **0.x:** Context (user, tenant, locale) was automatically included in some cache keys (ODataRequests)
|
|
77
|
+
- **1.x:** Context is **disabled by default** and can be enabled for **ALL** keys (unless overwritten)
|
|
78
|
+
|
|
79
|
+
**To Enable Context Awareness:**
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"cds": {
|
|
83
|
+
"requires": {
|
|
84
|
+
"caching": {
|
|
85
|
+
...
|
|
86
|
+
"keyManagement": {
|
|
87
|
+
"isUserAware": true, // Include user context in cache keys
|
|
88
|
+
"isTenantAware": true, // Include tenant context in cache keys
|
|
89
|
+
"isLocaleAware": false // Include locale context in cache keys
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 📚 Migration Examples
|
|
98
|
+
|
|
99
|
+
**Example 1: Basic Caching**
|
|
100
|
+
```javascript
|
|
101
|
+
// ❌ Old way (deprecated, but will still work)
|
|
102
|
+
const result = await cache.run(query, db)
|
|
103
|
+
|
|
104
|
+
// ✅ New way
|
|
105
|
+
const { result, cacheKey, metadata } = await cache.rt.run(query, db)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Example 2: Function Wrapping**
|
|
109
|
+
```javascript
|
|
110
|
+
// ❌ Old way (deprecated, but will still work)
|
|
111
|
+
const cachedFn = cache.wrap("key", expensiveOperation)
|
|
112
|
+
const result = await cachedFn("param1", "param2")
|
|
113
|
+
|
|
114
|
+
// ✅ New way
|
|
115
|
+
const cachedFn = cache.rt.wrap("key", expensiveOperation)
|
|
116
|
+
const { result, cacheKey, metadata } = await cachedFn("param1", "param2")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Example 3: Custom Key Templates**
|
|
120
|
+
```javascript
|
|
121
|
+
// ❌ Old way (will not work anymore)
|
|
122
|
+
await cache.set(data, value, {
|
|
123
|
+
key: { template: "user:{user}:{hash}" }
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// ✅ New way
|
|
127
|
+
await cache.set(data, value, {
|
|
128
|
+
key: "user:{user}:{hash}"
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 🔍 What's New
|
|
133
|
+
|
|
134
|
+
- **Enhanced Metadata:** All read-through operations now return cache keys and performance metadata
|
|
135
|
+
- **Better Performance:** Context awareness is opt-in, reducing unnecessary key complexity
|
|
136
|
+
- **Improved Debugging:** Access to generated cache keys for troubleshooting
|
|
137
|
+
- **Flexible Configuration:** Global and per-operation key template control
|
|
138
|
+
|
|
139
|
+
For detailed API documentation, see [Programmatic API Reference](docs/programmatic-api.md).
|
|
140
|
+
|
|
141
|
+
### Example Application
|
|
142
|
+
|
|
143
|
+
The cds-caching plugin includes a comprehensive example application demonstrating various caching use cases and a UI5-based dashboard for monitoring cache performance.
|
|
144
|
+
|
|
145
|
+

|
|
146
|
+
|
|
147
|
+
The example consists of:
|
|
148
|
+
- **Backend Application** (`examples/app/`) - A CAP application showing annotation-based and programmatic caching patterns
|
|
149
|
+
- **Dashboard** (`examples/dashboard/`) - A UI5-based monitoring interface with real-time metrics, key-level analytics, and historical data
|
|
150
|
+
|
|
151
|
+
[See the full Example Application Guide →](docs/example-app.md)
|
|
152
|
+
|
|
37
153
|
|
|
38
154
|
### Installation
|
|
39
155
|
|
|
@@ -43,56 +159,136 @@ Installing and using cds-caching is straightforward since it's a CAP plugin. Sim
|
|
|
43
159
|
npm install cds-caching
|
|
44
160
|
```
|
|
45
161
|
|
|
46
|
-
|
|
162
|
+
### Configuration
|
|
47
163
|
|
|
48
|
-
|
|
164
|
+
The cds-caching plugin supports comprehensive configuration through `package.json`. Here are all available configuration options:
|
|
165
|
+
|
|
166
|
+
#### Basic Service Configuration
|
|
167
|
+
|
|
168
|
+
**Minimal setup** (in-memory cache for development):
|
|
169
|
+
|
|
170
|
+
```json
|
|
49
171
|
{
|
|
50
172
|
"cds": {
|
|
51
173
|
"requires": {
|
|
52
174
|
"caching": {
|
|
53
175
|
"impl": "cds-caching",
|
|
54
|
-
"namespace": "
|
|
176
|
+
"namespace": "caching"
|
|
55
177
|
},
|
|
56
|
-
//
|
|
178
|
+
// Recommended: Define a specific caching service for different caching requirements
|
|
57
179
|
"bp-caching": {
|
|
58
180
|
"impl": "cds-caching",
|
|
59
|
-
"namespace": "
|
|
181
|
+
"namespace": "bp-caching"
|
|
60
182
|
}
|
|
61
183
|
}
|
|
62
184
|
}
|
|
63
185
|
}
|
|
64
186
|
```
|
|
65
187
|
|
|
66
|
-
|
|
188
|
+
**Advanced configuration** with all options:
|
|
67
189
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```javascript
|
|
190
|
+
```json
|
|
71
191
|
{
|
|
72
192
|
"cds": {
|
|
73
193
|
"requires": {
|
|
74
194
|
"caching": {
|
|
75
195
|
"impl": "cds-caching",
|
|
76
|
-
"namespace": "
|
|
77
|
-
"store": "in-memory", // "in-memory"
|
|
196
|
+
"namespace": "caching",
|
|
197
|
+
"store": "in-memory", // "in-memory", "sqlite", or "redis"
|
|
78
198
|
"compression": "lz4", // "lz4" or "gzip"
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
// Redis
|
|
199
|
+
"throwOnErrors": false, // Whether basic operations should throw errors (default: false)
|
|
200
|
+
"credentials": {
|
|
201
|
+
// Redis configuration
|
|
82
202
|
"host": "localhost",
|
|
83
203
|
"port": 6379,
|
|
84
204
|
"password": "optional",
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
// SQLite
|
|
88
|
-
"url": "sqlite://./cache.sqlite"
|
|
205
|
+
"url": "redis://..." // Alternative: Redis connection URI
|
|
206
|
+
|
|
207
|
+
// SQLite configuration
|
|
208
|
+
"url": "sqlite://./cache.sqlite",
|
|
89
209
|
"table": "cache",
|
|
90
210
|
"busyTimeout": 10000
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Read-Through (RT) Key Configuration
|
|
219
|
+
|
|
220
|
+
Configure default key templates for read-through operations:
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"cds": {
|
|
225
|
+
"requires": {
|
|
226
|
+
"caching": {
|
|
227
|
+
...
|
|
228
|
+
"keyManagement": {
|
|
229
|
+
"isUserAware": true, // Include user context in cache keys
|
|
230
|
+
"isTenantAware": true, // Include tenant context in cache keys
|
|
231
|
+
"isLocaleAware": false // Include locale context in cache keys
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Default behavior** (if not configured): All context elements are disabled by default.
|
|
240
|
+
|
|
241
|
+
#### Error Handling Configuration
|
|
242
|
+
|
|
243
|
+
Configure how the caching service handles errors:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"cds": {
|
|
248
|
+
"requires": {
|
|
249
|
+
"caching": {
|
|
250
|
+
...
|
|
251
|
+
"throwOnErrors": true, // Basic operations (set, get, delete, has) throw errors
|
|
252
|
+
// Default: false - operations return undefined/null instead of throwing
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Error Handling Behavior:**
|
|
260
|
+
|
|
261
|
+
- **Basic Operations** (`set`, `get`, `delete`, `has`):
|
|
262
|
+
- When `throwOnErrors: false` (default): Operations return `undefined`/`null` on errors
|
|
263
|
+
- When `throwOnErrors: true`: Operations throw errors for connection issues, etc.
|
|
264
|
+
|
|
265
|
+
- **Read-Through Operations** (`rt.run`, `rt.send`, `rt.wrap`, `rt.exec`):
|
|
266
|
+
- Never throw errors, regardless of `throwOnErrors` setting
|
|
267
|
+
- Include `cacheErrors` array in response when errors occur
|
|
268
|
+
- Always fetch from remote service when cache operations fail
|
|
269
|
+
- Log errors for monitoring and debugging
|
|
270
|
+
|
|
271
|
+
#### Environment-Specific Configuration
|
|
272
|
+
|
|
273
|
+
You can override settings for different environments:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"cds": {
|
|
278
|
+
"requires": {
|
|
279
|
+
"caching": {
|
|
280
|
+
"impl": "cds-caching",
|
|
281
|
+
"store": "redis",
|
|
282
|
+
"[development]": {
|
|
283
|
+
"credentials": {
|
|
284
|
+
"host": "localhost",
|
|
285
|
+
"port": 6379
|
|
286
|
+
}
|
|
91
287
|
},
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
288
|
+
"[production]": {
|
|
289
|
+
"credentials": {
|
|
290
|
+
"url": "redis://production-redis:6379"
|
|
291
|
+
}
|
|
96
292
|
}
|
|
97
293
|
}
|
|
98
294
|
}
|
|
@@ -100,6 +296,19 @@ For more control, you can specify additional options:
|
|
|
100
296
|
}
|
|
101
297
|
```
|
|
102
298
|
|
|
299
|
+
For detailed information on RT key generation and advanced configuration options, see [Key Management](docs/key-management.md) and [Programmatic API Reference](docs/programmatic-api.md).
|
|
300
|
+
|
|
301
|
+
### Service Definition
|
|
302
|
+
|
|
303
|
+
Add the following cds definition to your data model:
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
using {plugin.cds_caching.CachingApiService} from 'cds-caching/index.cds';
|
|
307
|
+
|
|
308
|
+
// Don't forget to protect the service, e.g.
|
|
309
|
+
annotate CachingApiService with @requires: 'authenticated-user';
|
|
310
|
+
```
|
|
311
|
+
|
|
103
312
|
### Real-World Usage and Deployment
|
|
104
313
|
|
|
105
314
|
|
|
@@ -124,6 +333,7 @@ cds-caching provides 3 storage options:
|
|
|
124
333
|
- Works across multiple app instances, making it ideal for scalable applications
|
|
125
334
|
- Available on SAP BTP via hyperscaler options (e.g., AWS, Azure, Google Cloud)
|
|
126
335
|
- Even trial accounts provide Redis access
|
|
336
|
+
- Redis will be non-blocking
|
|
127
337
|
|
|
128
338
|
#### Redis Development Setup
|
|
129
339
|
|
|
@@ -200,8 +410,20 @@ resources:
|
|
|
200
410
|
|
|
201
411
|
### Usage Patterns
|
|
202
412
|
|
|
203
|
-
|
|
204
|
-
|
|
413
|
+
> ⚠️ **Deprecation Notice**: The following methods are deprecated since version 1.0 and will be removed in a future version:
|
|
414
|
+
> - `cache.run()` - use `cache.rt.run()` instead
|
|
415
|
+
> - `cache.exec()` - use `cache.rt.exec()` instead
|
|
416
|
+
> - `cache.wrap()` - use `cache.rt.wrap()` instead
|
|
417
|
+
> - `cache.send()` - use `cache.rt.send()` instead
|
|
418
|
+
>
|
|
419
|
+
> The `rt.xxx` methods provide enhanced functionality including:
|
|
420
|
+
> - **Read-through metadata**: Information about cache hits/misses and latency
|
|
421
|
+
> - **Consistent return format**: All methods return `{ result, cacheKey, metadata }` by default
|
|
422
|
+
>
|
|
423
|
+
> **Migration**: Simply replace `cache.method()` with `cache.rt.method()` and access the result via `.result` property if needed.
|
|
424
|
+
|
|
425
|
+
The caching service provides a flexible API for caching data in CAP applications ([full API](docs/programmatic-api.md)). Here are the key usage patterns:
|
|
426
|
+
#### 1. Low-Level Key-Value API for Read-Aside Caching
|
|
205
427
|
|
|
206
428
|
The most basic way to use cds-caching is through its key-value API:
|
|
207
429
|
|
|
@@ -210,28 +432,53 @@ The most basic way to use cds-caching is through its key-value API:
|
|
|
210
432
|
const cache = await cds.connect.to("caching")
|
|
211
433
|
|
|
212
434
|
// Store a value (can be any object)
|
|
213
|
-
await cache.set("
|
|
435
|
+
await cache.set("bp:1000001", businessPartnerData)
|
|
214
436
|
|
|
215
437
|
// Retrieve the value
|
|
216
|
-
await cache.get("
|
|
438
|
+
await cache.get("bp:1000001") // => businessPartnerData
|
|
217
439
|
|
|
218
440
|
// Check if the key exists
|
|
219
|
-
await cache.has("
|
|
441
|
+
await cache.has("bp:1000001") // => true/false
|
|
220
442
|
|
|
221
443
|
// Delete the key
|
|
222
|
-
await cache.delete("
|
|
444
|
+
await cache.delete("bp:1000001")
|
|
223
445
|
|
|
224
446
|
// Clear the whole cache
|
|
225
447
|
await cache.clear()
|
|
226
448
|
```
|
|
227
449
|
|
|
450
|
+
**Error Handling for Basic Operations:**
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
// With throwOnErrors: false (default)
|
|
454
|
+
try {
|
|
455
|
+
const value = await cache.get("bp:1000001")
|
|
456
|
+
if (value === undefined) {
|
|
457
|
+
// Handle cache miss or error
|
|
458
|
+
console.log("Value not found or cache error occurred")
|
|
459
|
+
}
|
|
460
|
+
} catch (error) {
|
|
461
|
+
// Only thrown for non-cache related errors
|
|
462
|
+
console.error("Unexpected error:", error)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// With throwOnErrors: true
|
|
466
|
+
try {
|
|
467
|
+
const value = await cache.get("bp:1000001")
|
|
468
|
+
// Value will be undefined if not found, but errors will be thrown
|
|
469
|
+
} catch (error) {
|
|
470
|
+
// Errors thrown for connection issues, etc.
|
|
471
|
+
console.error("Cache error:", error)
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
228
475
|
#### 2. CQN Query Caching
|
|
229
476
|
|
|
230
477
|
For more advanced CAP integration, cache CAP's CQN queries directly. By passing in the query, a dynamic key is generated based on the CQN structure of the query. Note, that passing in queries with dynamic parameters (e.g. `SELECT.from(Foo).where({id: 1})`) will result in a different key for each query execution.
|
|
231
478
|
|
|
232
479
|
```javascript
|
|
233
480
|
// Create and execute a CQN query
|
|
234
|
-
const query = SELECT.from(
|
|
481
|
+
const query = SELECT.from(BusinessPartners).where({ businessPartnerType: '2' })
|
|
235
482
|
const result = await db.run(query)
|
|
236
483
|
|
|
237
484
|
// Cache the result
|
|
@@ -240,20 +487,43 @@ await cache.set(query, result)
|
|
|
240
487
|
// Retrieve from cache using the same query
|
|
241
488
|
const cachedResult = await cache.get(query)
|
|
242
489
|
```
|
|
243
|
-
Handling the cache manually via read-aside pattern is possible, but the caching service provides a more convenient way to cache and retrieve CQN queries. By using the `run` method, the caching service will transparently cache the result of the query and return the cached result if available for all further requests.
|
|
490
|
+
Handling the cache manually via read-aside pattern is possible, but the caching service provides a more convenient way to cache and retrieve CQN queries. By using the `rt.run` method, the caching service will transparently cache the result of the query and return the cached result if available for all further requests.
|
|
244
491
|
|
|
245
492
|
```javascript
|
|
246
|
-
const query = SELECT.from(
|
|
493
|
+
const query = SELECT.from(BusinessPartners).where({ businessPartnerType: '2' })
|
|
247
494
|
|
|
248
495
|
// Runs the query internally and caches the result
|
|
249
|
-
const result = await cache.run(query, db)
|
|
496
|
+
const { result } = await cache.rt.run(query, db)
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
Because the cache key has been dynamically created at runtime, it will also be returned:
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
// Access the cacheKey for later usage
|
|
504
|
+
const { result, cacheKey } = await cache.rt.run(query, db)
|
|
250
505
|
```
|
|
251
506
|
|
|
252
|
-
|
|
507
|
+
**Error Handling for Read-Through Operations:**
|
|
508
|
+
|
|
509
|
+
Read-through operations never throw errors, even when cache operations fail. Instead, they include error information in the response:
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
// Read-through operations always return a result, even on cache errors
|
|
513
|
+
const { result, cacheKey, metadata, cacheErrors } = await cache.rt.run(query, db)
|
|
514
|
+
|
|
515
|
+
if (cacheErrors && cacheErrors.length > 0) {
|
|
516
|
+
console.log("Cache errors occurred:", cacheErrors)
|
|
517
|
+
// Result will be fetched from remote service despite cache errors
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// The result is always available, regardless of cache errors
|
|
521
|
+
return result
|
|
522
|
+
```
|
|
253
523
|
|
|
254
524
|
#### 3. RemoteService Request-Level Caching
|
|
255
525
|
|
|
256
|
-
Cache entire CAP requests with context awareness (e.g. user, tenant, locale, etc.), which is useful for caching slow remote service calls or even application services. The caching service will automatically generate a key for the request based on the request object and the current user, tenant and locale.
|
|
526
|
+
Cache entire CAP requests with context awareness (e.g. user, tenant, locale, etc.), which is useful for caching slow remote service calls or even application services. The caching service will automatically generate a key for the request based on the request object and the current user, tenant and locale (if not configured otherwise).
|
|
257
527
|
|
|
258
528
|
```javascript
|
|
259
529
|
// Cache the requests to an exposed external entity
|
|
@@ -262,18 +532,19 @@ this.on('READ', BusinessPartners, async (req, next) => {
|
|
|
262
532
|
let value = await cache.get(req)
|
|
263
533
|
if(!value) {
|
|
264
534
|
value = await bupa.run(req)
|
|
265
|
-
await cache.set(req, value, { ttl:
|
|
535
|
+
await cache.set(req, value, { ttl: 30000 })
|
|
266
536
|
}
|
|
267
537
|
return value
|
|
268
538
|
})
|
|
269
539
|
```
|
|
270
540
|
|
|
271
|
-
Alternatively use read-through caching via the `run` method to let the caching service handle the caching transparently:
|
|
541
|
+
Alternatively use read-through caching via the `rt.run` method to let the caching service handle the caching transparently:
|
|
272
542
|
|
|
273
543
|
```javascript
|
|
274
544
|
this.on('READ', BusinessPartners, async (req, next) => {
|
|
275
545
|
const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
|
|
276
|
-
|
|
546
|
+
const { result } = await cache.rt.run(req, bupa)
|
|
547
|
+
return result
|
|
277
548
|
})
|
|
278
549
|
|
|
279
550
|
```
|
|
@@ -283,7 +554,7 @@ This will transparently cache the result of the request and return the cached re
|
|
|
283
554
|
### 4. ApplicationService Request-Level Caching
|
|
284
555
|
|
|
285
556
|
|
|
286
|
-
> Caching an entire entity should be used with caution, as it will cache all permutations of requests ($filter, $expand, $orderby, etc.) on the entity, which
|
|
557
|
+
> Caching an entire entity should be used with caution, as it will cache all permutations of requests ($select, $filter, $expand, $orderby, etc.) on the entity, which may lead to a huge number of cache entries. Use this only for entities where you can guarantee a low number of different queries.
|
|
287
558
|
|
|
288
559
|
|
|
289
560
|
But not only external services can be cached, it's also possible to cache requests against an ApplicationService.
|
|
@@ -299,7 +570,8 @@ class MyService extends cds.ApplicationService {
|
|
|
299
570
|
const { MyEntity } = this.entities;
|
|
300
571
|
this.on('READ', MyEntity, async (req, next) => {
|
|
301
572
|
const cache = cds.connect.to("caching");
|
|
302
|
-
|
|
573
|
+
const { result } = await cache.rt.run(req, next)
|
|
574
|
+
return result;
|
|
303
575
|
});
|
|
304
576
|
});
|
|
305
577
|
return super.init()
|
|
@@ -314,7 +586,7 @@ Alternatively to doing this via code, you can use annotations to enable caching
|
|
|
314
586
|
```
|
|
315
587
|
service MyService {
|
|
316
588
|
@cache: {
|
|
317
|
-
ttl:
|
|
589
|
+
ttl: 10000 // 10 seconds
|
|
318
590
|
}
|
|
319
591
|
entity BusinessPartners as projection on BusinessPartner {
|
|
320
592
|
// ... entity definition
|
|
@@ -322,7 +594,7 @@ service MyService {
|
|
|
322
594
|
|
|
323
595
|
|
|
324
596
|
@cache: {
|
|
325
|
-
ttl:
|
|
597
|
+
ttl: 100000, // 10 seconds
|
|
326
598
|
tags: [{
|
|
327
599
|
template: 'user-{user}'
|
|
328
600
|
}]
|
|
@@ -337,35 +609,55 @@ While not directly related to CAP functionality, the caching service provides tw
|
|
|
337
609
|
|
|
338
610
|
```javascript
|
|
339
611
|
// Using wrap() to create a cached version of a function
|
|
340
|
-
const
|
|
341
|
-
// ... some expensive computation
|
|
342
|
-
return
|
|
612
|
+
const fetchBusinessPartnerData = async (businessPartnerId, includeAddresses) => {
|
|
613
|
+
// ... some expensive computation to fetch BP data
|
|
614
|
+
return businessPartnerData
|
|
343
615
|
}
|
|
344
616
|
|
|
345
|
-
// Creates a cached version of the function
|
|
346
|
-
const
|
|
617
|
+
// Creates a cached version of the function.
|
|
618
|
+
const cachedBpOperation = cache.rt.wrap("bp-data", fetchBusinessPartnerData, {
|
|
347
619
|
ttl: 3600,
|
|
348
|
-
tags: ['
|
|
620
|
+
tags: ['business-partner']
|
|
349
621
|
})
|
|
350
622
|
|
|
351
623
|
// Each call checks cache first, only executes if cache miss
|
|
352
|
-
const result = await
|
|
624
|
+
const result = await cachedBpOperation("1000001", true)
|
|
353
625
|
|
|
354
626
|
// Using exec() for immediate execution with caching
|
|
355
|
-
const result = await cache.exec("
|
|
356
|
-
// ... some expensive computation
|
|
357
|
-
return
|
|
358
|
-
}, {
|
|
627
|
+
const result = await cache.rt.exec("product-data", async (productId) => {
|
|
628
|
+
// ... some expensive computation to fetch product data
|
|
629
|
+
return productData
|
|
630
|
+
}, ["1000001"], {
|
|
359
631
|
ttl: 3600,
|
|
360
|
-
tags: ['
|
|
632
|
+
tags: ['product']
|
|
361
633
|
})
|
|
362
634
|
```
|
|
363
635
|
|
|
364
|
-
The key differences between `wrap()` and `exec()`:
|
|
365
|
-
- `wrap()` returns a new function that includes caching logic
|
|
366
|
-
- `exec()` immediately executes the function and caches the result
|
|
367
|
-
- Use `wrap()` when you need to reuse the cached function multiple times
|
|
368
|
-
- Use `exec()` for one-off executions with caching
|
|
636
|
+
The key differences between `rt.wrap()` and `rt.exec()`:
|
|
637
|
+
- `rt.wrap()` returns a new function that includes caching logic
|
|
638
|
+
- `rt.exec()` immediately executes the function and caches the result
|
|
639
|
+
- Use `rt.wrap()` when you need to reuse the cached function multiple times
|
|
640
|
+
- Use `rt.exec()` for one-off executions with caching
|
|
641
|
+
|
|
642
|
+
#### Dynamic Key Generation
|
|
643
|
+
|
|
644
|
+
All `rt.xxx` methods automatically generate dynamic cache keys based on function arguments (`wrap`, `exec`) and request/query parameters. This ensures that different function calls with different arguments are cached separately.
|
|
645
|
+
|
|
646
|
+
```javascript
|
|
647
|
+
// Different arguments = different cache keys
|
|
648
|
+
const result1 = await cachedBpOperation("1000001", true) // Cache key: "bp-data:1000001:true"
|
|
649
|
+
const result2 = await cachedBpOperation("1000002", false) // Cache key: "bp-data:1000002:false"
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
You can override this behavior by providing a custom key template:
|
|
653
|
+
|
|
654
|
+
```javascript
|
|
655
|
+
const cachedOperation = cache.rt.wrap("bp-profile", fetchBusinessPartnerData, {
|
|
656
|
+
key: "profile:{args[0]}:{args[1]}"
|
|
657
|
+
})
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
For detailed information on how read-through keys are generated and configured, see [Key Management](docs/key-management.md).
|
|
369
661
|
|
|
370
662
|
### Cache Invalidation Strategies
|
|
371
663
|
|
|
@@ -383,19 +675,19 @@ The TTL can be specified for individually through all cache methods (e.g. `set`,
|
|
|
383
675
|
await cache.set("key", "value", { ttl: 60000 })
|
|
384
676
|
|
|
385
677
|
// Run with 30 seconds TTL
|
|
386
|
-
const result = await cache.run(query, db, { ttl: 30000 })
|
|
678
|
+
const { result } = await cache.rt.run(query, db, { ttl: 30000 })
|
|
387
679
|
|
|
388
680
|
// Send with 10 seconds TTL
|
|
389
|
-
const result = await cache.send(request, service, { ttl: 10000 })
|
|
681
|
+
const { result } = await cache.rt.send(request, service, { ttl: 10000 })
|
|
390
682
|
|
|
391
683
|
// Wrap with 10 seconds TTL
|
|
392
|
-
const cachedOperation = cache.wrap("key", expensiveOperation, { ttl: 10000 })
|
|
684
|
+
const cachedOperation = cache.rt.wrap("key", expensiveOperation, { ttl: 10000 })
|
|
393
685
|
|
|
394
686
|
// Exec with 10 seconds TTL
|
|
395
|
-
const result = await cache.exec("key", async () => {
|
|
687
|
+
const { result } = await cache.rt.exec("key", async () => {
|
|
396
688
|
// ... some expensive computation
|
|
397
689
|
return result
|
|
398
|
-
}, {
|
|
690
|
+
}, [] {
|
|
399
691
|
ttl: 10000
|
|
400
692
|
})
|
|
401
693
|
```
|
|
@@ -408,43 +700,42 @@ Key-based invalidation is a way to invalidate cache entries based on a specific
|
|
|
408
700
|
await cache.delete("key")
|
|
409
701
|
```
|
|
410
702
|
|
|
411
|
-
Keys are critical for cache invalidation. To allow custom key management, you can override the auto-generated key. This option is available for all essential methods (e.g cache.set, cache.run, cache.send, cache.createKey) and for the annotations.
|
|
703
|
+
Keys are critical for cache invalidation. To allow custom key management, you can override the auto-generated key. This option is available for all essential methods (e.g cache.set, cache.rt.run, cache.rt.send, cache.createKey) and for the annotations.
|
|
704
|
+
|
|
705
|
+
**Read-Through (RT) Methods**: All `rt.xxx` methods automatically generate dynamic cache keys and return them in the response. The generated keys include configurable context (user, tenant, locale) and a content hash. For detailed information on RT key generation, see [Key Management](docs/key-management.md).
|
|
412
706
|
|
|
413
707
|
```javascript
|
|
414
708
|
// No key override given, string will just be used as keys
|
|
415
|
-
await cache.set('
|
|
709
|
+
await cache.set('bp:1000001', businessPartnerData) // key: bp:1000001
|
|
416
710
|
|
|
417
711
|
// No key override given, objects will be smartly hashed
|
|
418
|
-
await cache.set(SELECT.from(
|
|
712
|
+
await cache.set(SELECT.from(BusinessPartners)) // key: bd3f3690d3e96a569bd89d9e207a89af
|
|
419
713
|
|
|
420
714
|
// Automatically build the key for retrieval/deletion
|
|
421
|
-
cache.createKey(SELECT.from(
|
|
715
|
+
cache.createKey(SELECT.from(BusinessPartners)) // key: bd3f3690d3e96a569bd89d9e207a89af
|
|
422
716
|
|
|
423
717
|
// Override and use your own key based on a fixed value
|
|
424
|
-
await cache.set(SELECT.from(
|
|
718
|
+
await cache.set(SELECT.from(BusinessPartners, 1000001), { key: "bp:1000001" })
|
|
425
719
|
|
|
426
|
-
//
|
|
427
|
-
|
|
720
|
+
// RT methods return the generated cache key
|
|
721
|
+
const { result, cacheKey } = await cache.rt.run(query, db)
|
|
722
|
+
console.log('Generated key:', cacheKey) // e.g., "tenant-acme:user-john:locale-en:hash-abc123"
|
|
428
723
|
|
|
429
|
-
//
|
|
430
|
-
await cache.
|
|
431
|
-
```
|
|
724
|
+
// Override RT key template for requests
|
|
725
|
+
await cache.rt.run(req, remoteService, { key: "mykey:{tenant}:{user}:{locale}:{hash}" })
|
|
432
726
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
- `prefix` – will add this piece at the beginning
|
|
436
|
-
- `suffix` - will ad this piece at the end
|
|
437
|
-
- `template` - will set a value filled with placeholders, available placeholders are (only relevant for cds.Requests):
|
|
438
|
-
- `{user}`: The current user
|
|
439
|
-
- `{tenant}`: The current tenant
|
|
440
|
-
- `{locale}`: The current locale
|
|
441
|
-
- `{hash}`: The hash of the request query/params/data/path/etc.
|
|
727
|
+
// This requests will be cached for all users and for each locale
|
|
728
|
+
await cache.rt.run(req, remoteService, { key: "mykey:{user}:{locale}:{hash}" })
|
|
442
729
|
|
|
443
|
-
|
|
730
|
+
// Function wrapping with custom key template
|
|
731
|
+
const cachedFunction = cache.rt.wrap("user-data", expensiveOperation, {
|
|
732
|
+
key: "user:{user}:{args[0]}"
|
|
733
|
+
})
|
|
734
|
+
```
|
|
444
735
|
|
|
445
736
|
#### 3. Tag-Based
|
|
446
737
|
|
|
447
|
-
Tags are a way to invalidate cache entries based on a specific tag. Tags need to be provided explicitly when storing a value in the cache and are supported for all cache methods (e.g. `set`, `run`, `send`, `wrap`, `exec`).
|
|
738
|
+
Tags are a way to invalidate cache entries based on a specific tag. Tags need to be provided explicitly when storing a value in the cache and are supported for all cache methods (e.g. `set`, `rt.run`, `rt.send`, `rt.wrap`, `rt.exec`).
|
|
448
739
|
Tags can be provided as an array of strings or as an array of objects with the following properties:
|
|
449
740
|
- `value`: The value to use for the tag.
|
|
450
741
|
- `data`: A field from the value to use for the tag. This is working for objects and arrays of objects.
|
|
@@ -461,22 +752,22 @@ Templates support the following properties:
|
|
|
461
752
|
|
|
462
753
|
```javascript
|
|
463
754
|
// Store with static tag
|
|
464
|
-
await cache.set("
|
|
465
|
-
tags: [{ value: "
|
|
755
|
+
await cache.set("bp:1000001", businessPartnerData, {
|
|
756
|
+
tags: [{ value: "bp-1000001" }]
|
|
466
757
|
})
|
|
467
758
|
|
|
468
759
|
// Store with template tag (will generate a tag like "tenant-global-user-anonymous")
|
|
469
|
-
await cache.set("
|
|
760
|
+
await cache.set("bp:1000001", businessPartnerData, {
|
|
470
761
|
tags: [{ template: "tenant-{tenant}-user-{user}" }]
|
|
471
762
|
})
|
|
472
763
|
|
|
473
764
|
// Store with data-based tag
|
|
474
|
-
await cache.set("
|
|
475
|
-
tags: [{ data: "
|
|
765
|
+
await cache.set("product:1000001", { productId: 1000001, name: "Laptop Computer" }, {
|
|
766
|
+
tags: [{ data: "productId", prefix: "product-" }]
|
|
476
767
|
})
|
|
477
768
|
|
|
478
769
|
// Invalidate by tag
|
|
479
|
-
await cache.deleteByTag('
|
|
770
|
+
await cache.deleteByTag('bp-1000001')
|
|
480
771
|
```
|
|
481
772
|
This is really useful for invalidating cache entries based on a specific attribute or context.
|
|
482
773
|
|
|
@@ -488,17 +779,17 @@ Dynamic tags using data `data` property are a way to invalidate cache entries ba
|
|
|
488
779
|
|
|
489
780
|
const businessPartners = [
|
|
490
781
|
{
|
|
491
|
-
businessPartner:
|
|
492
|
-
name: '
|
|
782
|
+
businessPartner: 1000001,
|
|
783
|
+
name: 'Acme Corporation'
|
|
493
784
|
},
|
|
494
785
|
{
|
|
495
|
-
businessPartner:
|
|
496
|
-
name: '
|
|
786
|
+
businessPartner: 1000002,
|
|
787
|
+
name: 'Tech Solutions Ltd'
|
|
497
788
|
}
|
|
498
789
|
]
|
|
499
790
|
|
|
500
791
|
// Store with dynamic tags
|
|
501
|
-
await cache.set("
|
|
792
|
+
await cache.set("bp-list", businessPartners, {
|
|
502
793
|
tags: [
|
|
503
794
|
{ data: 'businessPartner', prefix: 'bp-' },
|
|
504
795
|
{ value: "businessPartner" }
|
|
@@ -506,17 +797,17 @@ await cache.set("key", businessPartners, {
|
|
|
506
797
|
})
|
|
507
798
|
|
|
508
799
|
// Introspect the tags
|
|
509
|
-
const tags = await cache.tags("
|
|
800
|
+
const tags = await cache.tags("bp-list") // => ["bp-1000001", "bp-1000002", "businessPartner"]
|
|
510
801
|
|
|
511
802
|
// Invalidate by tag
|
|
512
|
-
await cache.deleteByTag('bp-
|
|
513
|
-
await cache.deleteByTag('bp-
|
|
803
|
+
await cache.deleteByTag('bp-1000001')
|
|
804
|
+
await cache.deleteByTag('bp-1000002')
|
|
514
805
|
```
|
|
515
806
|
|
|
516
|
-
This is really usefull for caching results with multiple rows where you can't predict the tags beforehand or when you want to invalidate cache entries based on the data itself. This is also possible for the `run` method.
|
|
807
|
+
This is really usefull for caching results with multiple rows where you can't predict the tags beforehand or when you want to invalidate cache entries based on the data itself. This is also possible for the `rt.run` method.
|
|
517
808
|
|
|
518
809
|
```javascript
|
|
519
|
-
const result = await cache.run(query, db, {
|
|
810
|
+
const result = await cache.rt.run(query, db, {
|
|
520
811
|
tags: [{ data: 'businessPartner', prefix: 'bp-' }]
|
|
521
812
|
})
|
|
522
813
|
```
|
|
@@ -537,6 +828,36 @@ for await (const entry of iterator) {
|
|
|
537
828
|
|
|
538
829
|
This will return an iterator over all cache entries. You can use this to traverse all cache entries and invalidate them based on a specific condition. You should only use this for small caches (e.g. by using multiple caching services with different namespaces).
|
|
539
830
|
|
|
831
|
+
### TypeScript Support
|
|
832
|
+
|
|
833
|
+
cds-caching includes comprehensive TypeScript definitions. The library is written in JavaScript but provides full TypeScript support for better development experience.
|
|
834
|
+
|
|
835
|
+
#### Basic Usage with TypeScript
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
import { CachingService, CacheOptions, ReadThroughResult } from 'cds-caching';
|
|
839
|
+
|
|
840
|
+
const cache = await cds.connect.to('caching') as CachingService;
|
|
841
|
+
|
|
842
|
+
// Basic cache operations
|
|
843
|
+
await cache.set('my-key', { data: 'value' }, { ttl: 3600 });
|
|
844
|
+
const value = await cache.get('my-key');
|
|
845
|
+
|
|
846
|
+
// Read-through operations with full type safety
|
|
847
|
+
const { result, cacheKey, metadata } = await cache.rt.send(request, service, {
|
|
848
|
+
ttl: 1800,
|
|
849
|
+
tags: ['user-data']
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// Function wrapping with type inference
|
|
853
|
+
const cachedFunction = cache.rt.wrap('expensive-operation', async (id: string) => {
|
|
854
|
+
return await this.performExpensiveOperation(id);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const { result: operationResult } = await cachedFunction('user-123');
|
|
858
|
+
|
|
859
|
+
```
|
|
860
|
+
|
|
540
861
|
### OData Service Caching Considerations
|
|
541
862
|
|
|
542
863
|
While caching individual requests can improve performance, **caching an entire OData service is generally not recommended**. Here's why:
|
|
@@ -582,222 +903,314 @@ Instead of caching entire OData service responses, focus on:
|
|
|
582
903
|
3. **Multi-Tenant**: Use appropriate namespacing and key strategies
|
|
583
904
|
4. **Redis Setup**: Ensure proper configuration for production use
|
|
584
905
|
|
|
585
|
-
##
|
|
586
|
-
|
|
587
|
-
### `cache.createKey(key: any)` : `string`
|
|
588
|
-
|
|
589
|
-
Creates a key from a string or an object. This method is used internally when passing keys to the cache methods, so you don't need to call it directly other then to retrieve the dynamic generated key for a given object.
|
|
590
|
-
|
|
591
|
-
#### `key: any`
|
|
592
|
-
|
|
593
|
-
The key to create the key from. The key can be a string or an object. If an object is used, it will be hashed to a string key using MD5. cds.Requests are handled explicitly as the dynamic generated key includes the user, tenant and locale and query hash.
|
|
594
|
-
|
|
595
|
-
#### Returns
|
|
596
|
-
|
|
597
|
-
A string key.
|
|
598
|
-
|
|
599
|
-
---
|
|
600
|
-
|
|
601
|
-
### `await cache.set(key: any, value: any[, options: object])`
|
|
602
|
-
|
|
603
|
-
Sets a value in the cache.
|
|
604
|
-
|
|
605
|
-
#### `key: any`
|
|
606
|
-
|
|
607
|
-
The key to store the value under. The key handling is the same as for the `≈` method.
|
|
608
|
-
|
|
609
|
-
#### `value: any`
|
|
610
|
-
|
|
611
|
-
The value to store in the cache. The value will be serialized to a string using `JSON.stringify` (unless the value is already a string).
|
|
612
|
-
|
|
613
|
-
#### `options: object`
|
|
614
|
-
|
|
615
|
-
Object literal containing cache options.
|
|
616
|
-
|
|
617
|
-
The following properties are accepted:
|
|
906
|
+
## Enhanced Statistics & Monitoring
|
|
618
907
|
|
|
619
|
-
|
|
620
|
-
| ------------- | ------------- | ----------
|
|
621
|
-
| ttl | Time-to-live in milliseconds. | `1000`
|
|
622
|
-
| key | Key override for the cache for full control over the key management (see chapter Cache Invalidation Strategies) | `{template: 'user-{user}', value: '123'}`
|
|
623
|
-
| tags | Array of tags to associate with the value. Tags can be dynamic based on the stored cache data (see chapter Cache Invalidation Strategies) | `[{template: 'user-{user}', value: '123'}]`
|
|
908
|
+
The plugin now includes comprehensive statistics and monitoring capabilities that provide deep insights into cache performance and help optimize cache usage.
|
|
624
909
|
|
|
625
|
-
|
|
910
|
+
[See the full Metrics Guide →](docs/metrics-guide.md)
|
|
626
911
|
|
|
627
|
-
###
|
|
628
|
-
|
|
629
|
-
Gets a value from the cache.
|
|
630
|
-
|
|
631
|
-
#### `key: any`
|
|
632
|
-
|
|
633
|
-
The key to retrieve the value from. The key handling is the same as for the `createKey` method.
|
|
634
|
-
|
|
635
|
-
#### Returns
|
|
636
|
-
|
|
637
|
-
The deserialized value from the cache or `undefined` if the value does not exist.
|
|
638
|
-
|
|
639
|
-
---
|
|
640
|
-
|
|
641
|
-
### `await cache.has(key: any)`
|
|
642
|
-
|
|
643
|
-
Checks if a value exists in the cache.
|
|
644
|
-
|
|
645
|
-
#### `key: any`
|
|
646
|
-
|
|
647
|
-
The key to check for existence. The key handling is the same as for the `createKey` method.
|
|
648
|
-
|
|
649
|
-
#### Returns
|
|
650
|
-
|
|
651
|
-
`true` if the value exists in the cache, `false` otherwise.
|
|
652
|
-
|
|
653
|
-
---
|
|
654
|
-
|
|
655
|
-
### `await cache.delete(key: any)`
|
|
656
|
-
|
|
657
|
-
Deletes a value from the cache.
|
|
658
|
-
|
|
659
|
-
#### `key: any`
|
|
660
|
-
|
|
661
|
-
The key to delete the value from. The key handling is the same as for the `createKey` method.
|
|
662
|
-
|
|
663
|
-
---
|
|
664
|
-
|
|
665
|
-
### `await cache.clear()`
|
|
666
|
-
|
|
667
|
-
Clears the whole cache.
|
|
668
|
-
|
|
669
|
-
---
|
|
670
|
-
|
|
671
|
-
### `await cache.deleteByTag(tag: string)`
|
|
672
|
-
|
|
673
|
-
Deletes all values from the cache that are associated with the given tag.
|
|
674
|
-
|
|
675
|
-
#### `tag: string`
|
|
676
|
-
|
|
677
|
-
The tag to delete the values from.
|
|
678
|
-
|
|
679
|
-
---
|
|
680
|
-
|
|
681
|
-
### `await cache.run(query: cds.CQN , service: cds.Service)`
|
|
682
|
-
|
|
683
|
-
Runs a query against the provided service and caches the result for all further requests. This method is useful for read-through caching. (see Usage Patterns and [CAP docs](https://cap.cloud.sap/docs/node.js/core-services#srv-run-query) for more information)
|
|
684
|
-
|
|
685
|
-
#### `object: cds.CQN`
|
|
686
|
-
|
|
687
|
-
The CQN query to run.
|
|
688
|
-
|
|
689
|
-
#### `service: cds.Service`
|
|
690
|
-
|
|
691
|
-
The service to run the query on.
|
|
692
|
-
|
|
693
|
-
#### Returns
|
|
694
|
-
|
|
695
|
-
The result of the query, either from the cache or the service.
|
|
696
|
-
|
|
697
|
-
---
|
|
912
|
+
### Key Features
|
|
698
913
|
|
|
699
|
-
|
|
914
|
+
- **Real-time Metrics**: Monitor cache performance with detailed hit rates, latencies, and throughput
|
|
915
|
+
- **Key-level Tracking**: Track performance metrics for individual cache keys
|
|
916
|
+
- **Historical Data**: Store and analyze metrics over time (hourly/daily periods)
|
|
917
|
+
- **Performance Analytics**: Calculate cache efficiency, error rates, and response times
|
|
918
|
+
- **Runtime Configuration**: Enable/disable metrics at runtime without restart
|
|
919
|
+
- **API Access**: Access metrics programmatically or via OData service
|
|
700
920
|
|
|
701
|
-
|
|
921
|
+
### Metrics Overview
|
|
702
922
|
|
|
703
|
-
|
|
923
|
+
cds-caching provides two types of metrics:
|
|
704
924
|
|
|
705
|
-
|
|
925
|
+
#### 1. General Cache Metrics
|
|
926
|
+
Track overall cache performance including:
|
|
927
|
+
- **Hit/Miss Statistics**: Total hits, misses, and hit ratios
|
|
928
|
+
- **Latency Metrics**: Average, min, max, and percentile latencies for hits and misses
|
|
929
|
+
- **Performance Metrics**: Throughput (requests/second), error rates, cache efficiency
|
|
930
|
+
- **Memory Usage**: Current memory consumption and item count
|
|
931
|
+
- **Native Operations**: Counts of direct cache operations (set, get, delete, etc.)
|
|
706
932
|
|
|
707
|
-
####
|
|
933
|
+
#### 2. Key-level Metrics
|
|
934
|
+
Track performance for individual cache keys including:
|
|
935
|
+
- **Key-specific Statistics**: Hits, misses, and hit ratios per key
|
|
936
|
+
- **Context Information**: Data type, service name, entity name, operation type
|
|
937
|
+
- **Enhanced Metadata**: Query text, request info, function names, user/tenant context
|
|
938
|
+
- **Performance Tracking**: Latency and throughput metrics per key
|
|
708
939
|
|
|
709
|
-
|
|
940
|
+
### Enabling Metrics
|
|
710
941
|
|
|
711
|
-
|
|
942
|
+
Metrics are disabled by default to minimize performance impact. They can only be enabled/disabled via the programmatic API or OData API at runtime, not through package.json configuration.
|
|
712
943
|
|
|
713
|
-
|
|
944
|
+
To enable metrics programmatically:
|
|
714
945
|
|
|
715
|
-
|
|
946
|
+
```javascript
|
|
947
|
+
// Connect to the caching service
|
|
948
|
+
const cache = await cds.connect.to("caching")
|
|
716
949
|
|
|
717
|
-
|
|
950
|
+
// Enable metrics at runtime
|
|
951
|
+
await cache.setMetricsEnabled(true)
|
|
952
|
+
await cache.setKeyMetricsEnabled(true)
|
|
953
|
+
```
|
|
718
954
|
|
|
719
|
-
|
|
955
|
+
Or via OData API:
|
|
720
956
|
|
|
721
|
-
|
|
957
|
+
```http
|
|
958
|
+
### Enable general metrics
|
|
959
|
+
POST http://localhost:4004/odata/v4/caching-api/Caches('caching')/setMetricsEnabled
|
|
960
|
+
Content-Type: application/json
|
|
722
961
|
|
|
723
|
-
|
|
962
|
+
{
|
|
963
|
+
"enabled": true
|
|
964
|
+
}
|
|
724
965
|
|
|
725
|
-
|
|
966
|
+
### Enable key-level metrics
|
|
967
|
+
POST http://localhost:4004/odata/v4/caching-api/Caches('caching')/setKeyMetricsEnabled
|
|
968
|
+
Content-Type: application/json
|
|
726
969
|
|
|
727
|
-
|
|
970
|
+
{
|
|
971
|
+
"enabled": true
|
|
972
|
+
}
|
|
973
|
+
```
|
|
728
974
|
|
|
729
|
-
|
|
975
|
+
### Accessing Metrics via Caching Service
|
|
730
976
|
|
|
731
|
-
The
|
|
977
|
+
The caching service provides comprehensive metrics collection and persistence capabilities. Metrics are automatically collected during cache operations and can be accessed both in real-time and from historical data.
|
|
732
978
|
|
|
733
|
-
####
|
|
979
|
+
#### Metrics Persistence
|
|
734
980
|
|
|
735
|
-
|
|
981
|
+
**Transient Metrics**: Current statistics are kept in memory and provide real-time insights into cache performance:
|
|
982
|
+
- Hit/miss ratios
|
|
983
|
+
- Current latency statistics
|
|
984
|
+
- Active cache entries
|
|
985
|
+
- Key-level performance data
|
|
736
986
|
|
|
737
|
-
|
|
987
|
+
**Persisted Metrics**: Historical data is automatically stored in the database for long-term analysis:
|
|
988
|
+
- Hourly aggregated statistics
|
|
989
|
+
- Key-level metrics over time
|
|
990
|
+
- Performance trends and patterns
|
|
991
|
+
- Cache efficiency analysis
|
|
738
992
|
|
|
739
|
-
|
|
993
|
+
#### Current Statistics
|
|
740
994
|
|
|
741
|
-
|
|
995
|
+
```javascript
|
|
996
|
+
// Connect to the caching service
|
|
997
|
+
const cache = await cds.connect.to("caching")
|
|
742
998
|
|
|
743
|
-
|
|
999
|
+
// Get current statistics
|
|
1000
|
+
const stats = await cache.getCurrentStats()
|
|
1001
|
+
console.log('Hit ratio:', stats.hitRatio)
|
|
1002
|
+
console.log('Average hit latency:', stats.avgHitLatency)
|
|
1003
|
+
console.log('Throughput:', stats.throughput)
|
|
1004
|
+
|
|
1005
|
+
// Get current key metrics
|
|
1006
|
+
const keyMetrics = await cache.getCurrentKeyMetrics()
|
|
1007
|
+
for (const [key, metrics] of keyMetrics) {
|
|
1008
|
+
console.log(`Key ${key}:`, {
|
|
1009
|
+
hits: metrics.hits,
|
|
1010
|
+
misses: metrics.misses,
|
|
1011
|
+
hitRatio: metrics.hitRatio,
|
|
1012
|
+
avgHitLatency: metrics.avgHitLatency
|
|
1013
|
+
})
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
744
1016
|
|
|
745
|
-
|
|
1017
|
+
#### Historical Metrics
|
|
746
1018
|
|
|
747
|
-
|
|
1019
|
+
```javascript
|
|
1020
|
+
// Get metrics for a specific time period
|
|
1021
|
+
const from = new Date('2024-01-01')
|
|
1022
|
+
const to = new Date('2024-01-31')
|
|
1023
|
+
const historicalStats = await cache.getMetrics(from, to)
|
|
748
1024
|
|
|
749
|
-
|
|
1025
|
+
// Get key-specific metrics
|
|
1026
|
+
const keyStats = await cache.getKeyMetrics('my-cache-key', from, to)
|
|
1027
|
+
```
|
|
750
1028
|
|
|
751
|
-
####
|
|
1029
|
+
#### Runtime Configuration
|
|
752
1030
|
|
|
753
|
-
|
|
1031
|
+
```javascript
|
|
1032
|
+
// Enable/disable metrics at runtime
|
|
1033
|
+
await cache.setMetricsEnabled(true)
|
|
1034
|
+
await cache.setKeyMetricsEnabled(true)
|
|
1035
|
+
|
|
1036
|
+
// Get current configuration
|
|
1037
|
+
const config = await cache.getRuntimeConfiguration()
|
|
1038
|
+
console.log('Metrics enabled:', config.metricsEnabled)
|
|
1039
|
+
console.log('Key metrics enabled:', config.keyMetricsEnabled)
|
|
1040
|
+
|
|
1041
|
+
// Clear metrics
|
|
1042
|
+
await cache.clearMetrics()
|
|
1043
|
+
await cache.clearKeyMetrics()
|
|
1044
|
+
```
|
|
754
1045
|
|
|
755
|
-
|
|
1046
|
+
### Metrics Data Structure
|
|
756
1047
|
|
|
757
|
-
|
|
1048
|
+
#### General Cache Statistics (Metrics Entity)
|
|
758
1049
|
|
|
759
|
-
|
|
1050
|
+
```javascript
|
|
1051
|
+
{
|
|
1052
|
+
// Entity identification
|
|
1053
|
+
ID: "daily:2024-01-15", // Unique identifier (period:date)
|
|
1054
|
+
cache: "caching", // Cache name
|
|
1055
|
+
timestamp: "2024-01-15T10:30:00Z", // When metrics were recorded
|
|
1056
|
+
period: "daily", // Aggregation period (hourly/daily/monthly)
|
|
1057
|
+
|
|
1058
|
+
// Read-through metrics
|
|
1059
|
+
hits: 1500, // Number of cache hits
|
|
1060
|
+
misses: 300, // Number of cache misses
|
|
1061
|
+
errors: 5, // Number of errors
|
|
1062
|
+
totalRequests: 1800, // Total read-through requests
|
|
1063
|
+
|
|
1064
|
+
// Read-through latency metrics (milliseconds)
|
|
1065
|
+
avgHitLatency: 2.5, // Average hit latency
|
|
1066
|
+
minHitLatency: 0.1, // Minimum hit latency
|
|
1067
|
+
maxHitLatency: 15.2, // Maximum hit latency
|
|
1068
|
+
avgMissLatency: 45.8, // Average miss latency
|
|
1069
|
+
minMissLatency: 12.3, // Minimum miss latency
|
|
1070
|
+
maxMissLatency: 120.5, // Maximum miss latency
|
|
1071
|
+
avgReadThroughLatency: 8.9, // Average read-through latency
|
|
1072
|
+
|
|
1073
|
+
// Read-through performance metrics
|
|
1074
|
+
hitRatio: 0.833, // Hit ratio as percentage (83.3%)
|
|
1075
|
+
throughput: 25.5, // Requests per second
|
|
1076
|
+
errorRate: 0.003, // Error rate as percentage (0.3%)
|
|
1077
|
+
cacheEfficiency: 18.3, // Miss latency / hit latency ratio
|
|
1078
|
+
|
|
1079
|
+
// Native operation metrics
|
|
1080
|
+
nativeSets: 200, // Number of direct set operations
|
|
1081
|
+
nativeGets: 800, // Number of direct get operations
|
|
1082
|
+
nativeDeletes: 50, // Number of direct delete operations
|
|
1083
|
+
nativeClears: 2, // Number of clear operations
|
|
1084
|
+
nativeDeleteByTags: 10, // Number of delete-by-tag operations
|
|
1085
|
+
nativeErrors: 1, // Number of native operation errors
|
|
1086
|
+
totalNativeOperations: 1063, // Total native operations
|
|
1087
|
+
nativeThroughput: 17.7, // Native operations per second
|
|
1088
|
+
nativeErrorRate: 0.001, // Native operation error rate (0.1%)
|
|
1089
|
+
|
|
1090
|
+
// System metrics
|
|
1091
|
+
memoryUsage: 52428800, // Memory usage in bytes
|
|
1092
|
+
itemCount: 150, // Number of items in cache
|
|
1093
|
+
uptimeMs: 7200000 // Cache uptime in milliseconds
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
760
1096
|
|
|
761
|
-
|
|
1097
|
+
#### Key-level Metrics (KeyMetrics Entity)
|
|
762
1098
|
|
|
763
|
-
|
|
1099
|
+
```javascript
|
|
1100
|
+
{
|
|
1101
|
+
// Entity identification
|
|
1102
|
+
ID: "key:user-preferences:123", // Unique identifier
|
|
1103
|
+
cache: "caching", // Cache name
|
|
1104
|
+
keyName: "user-preferences:123", // Cache key name
|
|
1105
|
+
lastAccess: "2024-01-15T10:30:00Z", // Last access time
|
|
1106
|
+
period: "current", // Period type (current/hourly/daily)
|
|
1107
|
+
operationType: "read_through", // Operation category (read_through/native/mixed)
|
|
1108
|
+
|
|
1109
|
+
// Read-through metrics
|
|
1110
|
+
hits: 45, // Number of hits for this key
|
|
1111
|
+
misses: 5, // Number of misses for this key
|
|
1112
|
+
errors: 0, // Number of errors for this key
|
|
1113
|
+
totalRequests: 50, // Total requests for this key
|
|
1114
|
+
hitRatio: 0.9, // Hit ratio for this key (90%)
|
|
1115
|
+
cacheEfficiency: 21.2, // Cache efficiency for this key
|
|
1116
|
+
|
|
1117
|
+
// Read-through latency metrics (milliseconds)
|
|
1118
|
+
avgHitLatency: 1.2, // Average hit latency for this key
|
|
1119
|
+
minHitLatency: 0.5, // Minimum hit latency for this key
|
|
1120
|
+
maxHitLatency: 3.1, // Maximum hit latency for this key
|
|
1121
|
+
avgMissLatency: 25.4, // Average miss latency for this key
|
|
1122
|
+
minMissLatency: 15.2, // Minimum miss latency for this key
|
|
1123
|
+
maxMissLatency: 45.8, // Maximum miss latency for this key
|
|
1124
|
+
avgReadThroughLatency: 3.8, // Average read-through latency for this key
|
|
1125
|
+
|
|
1126
|
+
// Read-through performance metrics
|
|
1127
|
+
throughput: 2.5, // Requests per second for this key
|
|
1128
|
+
errorRate: 0.0, // Error rate for this key (0%)
|
|
1129
|
+
|
|
1130
|
+
// Native operation metrics for this key
|
|
1131
|
+
nativeHits: 10, // Native hits for this key
|
|
1132
|
+
nativeMisses: 2, // Native misses for this key
|
|
1133
|
+
nativeSets: 5, // Native sets for this key
|
|
1134
|
+
nativeDeletes: 1, // Native deletes for this key
|
|
1135
|
+
nativeClears: 0, // Native clears for this key
|
|
1136
|
+
nativeDeleteByTags: 0, // Native delete-by-tags for this key
|
|
1137
|
+
nativeErrors: 0, // Native errors for this key
|
|
1138
|
+
totalNativeOperations: 18, // Total native operations for this key
|
|
1139
|
+
nativeThroughput: 0.5, // Native operations per second for this key
|
|
1140
|
+
nativeErrorRate: 0.0, // Native error rate for this key
|
|
1141
|
+
|
|
1142
|
+
// Context and metadata
|
|
1143
|
+
dataType: "request", // Type of data (query/request/function/custom)
|
|
1144
|
+
operation: "READ", // Cache operation type
|
|
1145
|
+
metadata: '{"ttl":3600}', // JSON string with additional metadata
|
|
1146
|
+
context: '{"user":"john.doe","tenant":"acme"}', // JSON string with context
|
|
1147
|
+
query: "SELECT * FROM UserPreferences WHERE userId = '123'", // CQL query text
|
|
1148
|
+
subject: '{"entity":"UserPreferences"}', // JSON string with subject info
|
|
1149
|
+
target: "UserService", // Target service name
|
|
1150
|
+
tenant: "acme", // Tenant information
|
|
1151
|
+
user: "john.doe", // User information
|
|
1152
|
+
locale: "en-US", // Locale information
|
|
1153
|
+
cacheOptions: '{"ttl":3600}', // JSON string with cache options
|
|
1154
|
+
timestamp: "2024-01-15T09:00:00Z" // When this key was first accessed
|
|
1155
|
+
}
|
|
1156
|
+
```
|
|
764
1157
|
|
|
765
|
-
|
|
1158
|
+
### Best Practices for Metrics
|
|
766
1159
|
|
|
767
|
-
|
|
1160
|
+
1. **Enable Selectively**: Only enable metrics when needed for monitoring or debugging
|
|
1161
|
+
2. **Monitor Memory Usage**: Key metrics can consume significant memory for large caches
|
|
1162
|
+
3. **Set Appropriate Intervals**: Balance persistence frequency with performance impact
|
|
1163
|
+
4. **Use Historical Data**: Analyze trends over time to optimize cache configuration
|
|
1164
|
+
5. **Monitor Error Rates**: High error rates may indicate configuration issues
|
|
1165
|
+
6. **Track Cache Efficiency**: Aim for high cache efficiency (miss latency >> hit latency)
|
|
768
1166
|
|
|
769
|
-
|
|
1167
|
+
## API Reference
|
|
770
1168
|
|
|
771
|
-
|
|
1169
|
+
The cds-caching plugin provides two APIs for managing cache operations:
|
|
772
1170
|
|
|
773
|
-
|
|
1171
|
+
- **Programmatic API** - JavaScript methods for use within your CAP application code
|
|
1172
|
+
- **OData API** - REST endpoints for external applications and monitoring tools
|
|
774
1173
|
|
|
775
|
-
|
|
1174
|
+
### Programmatic API
|
|
776
1175
|
|
|
777
|
-
The
|
|
1176
|
+
The programmatic API provides methods for direct cache operations within your CAP application:
|
|
778
1177
|
|
|
779
|
-
|
|
1178
|
+
```javascript
|
|
1179
|
+
// Connect to the caching service
|
|
1180
|
+
const cache = await cds.connect.to("caching")
|
|
780
1181
|
|
|
781
|
-
|
|
1182
|
+
// Basic operations
|
|
1183
|
+
await cache.set("key", "value")
|
|
1184
|
+
const value = await cache.get("key")
|
|
1185
|
+
await cache.delete("key")
|
|
782
1186
|
|
|
783
|
-
|
|
1187
|
+
// Read-through operations
|
|
1188
|
+
const result = await cache.rt.run(query, db)
|
|
1189
|
+
const result = await cache.rt.send(request, service)
|
|
784
1190
|
|
|
785
|
-
|
|
1191
|
+
// Metrics and statistics
|
|
1192
|
+
const stats = await cache.getCurrentStats()
|
|
1193
|
+
const keyMetrics = await cache.getCurrentKeyMetrics()
|
|
1194
|
+
```
|
|
786
1195
|
|
|
787
|
-
|
|
1196
|
+
[See the full Programmatic API Reference →](docs/programmatic-api.md)
|
|
788
1197
|
|
|
789
|
-
|
|
1198
|
+
### OData API
|
|
790
1199
|
|
|
791
|
-
The
|
|
1200
|
+
The OData API provides REST endpoints for external applications, monitoring tools, and administrative interfaces:
|
|
792
1201
|
|
|
793
|
-
|
|
1202
|
+
```http
|
|
1203
|
+
### Get cache statistics
|
|
1204
|
+
GET /odata/v4/caching-api/Metrics?$filter=cache eq 'mycache'
|
|
794
1205
|
|
|
795
|
-
|
|
1206
|
+
### Get cache entries
|
|
1207
|
+
GET /odata/v4/caching-api/Caches('mycache')/getEntries()
|
|
796
1208
|
|
|
797
|
-
|
|
798
|
-
-
|
|
1209
|
+
### Clear cache
|
|
1210
|
+
POST /odata/v4/caching-api/Caches('mycache')/clear()
|
|
1211
|
+
```
|
|
799
1212
|
|
|
800
|
-
|
|
1213
|
+
[See the full OData API Reference →](docs/odata-api.md)
|
|
801
1214
|
|
|
802
1215
|
### Contributing
|
|
803
1216
|
|