cds-caching 0.3.2 → 1.0.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 +603 -278
- package/cds-plugin.js +2 -2
- package/index.cds +165 -20
- package/lib/CachingService.d.ts +328 -0
- package/lib/CachingService.js +369 -0
- package/lib/operations/AsyncOperations.js +172 -0
- package/lib/operations/BasicOperations.js +201 -0
- package/lib/operations/CapOperations.js +332 -0
- package/lib/support/CacheStatisticsHandler.js +1124 -0
- package/lib/support/CacheStoreManager.js +103 -0
- package/lib/support/KeyManager.js +132 -0
- package/lib/support/RuntimeConfigurationManager.js +146 -0
- package/lib/support/StatisticsPersistenceManager.js +375 -0
- package/lib/support/TagResolver.js +125 -0
- package/lib/util.js +201 -0
- package/package.json +17 -13
- package/srv/caching-api-service.js +124 -0
- package/srv/CacheStatisticsHandler.js +0 -213
- package/srv/CachingService.js +0 -588
- package/srv/statistics-service.cds +0 -37
- package/srv/statistics-service.js +0 -72
- package/srv/util.js +0 -101
package/README.md
CHANGED
|
@@ -3,37 +3,138 @@
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
5
|
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.
|
|
6
|
+
|
|
6
7
|
While CAP in general performs well for most use cases, caching can help with:
|
|
7
8
|
- Slow remote service calls
|
|
8
|
-
- Complex
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- Complex operations
|
|
10
|
+
- Slow queries
|
|
11
|
+
- Other performance bottle necks
|
|
11
12
|
|
|
12
|
-
While caching can
|
|
13
|
+
While cds-caching can be a big helper, an additional caching layer also adds complexity and should be used judiciously.
|
|
13
14
|
|
|
14
15
|
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
16
|
|
|
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
17
|
### Key Features
|
|
27
18
|
|
|
28
19
|
* **Flexible Key-Value Store** – Store and retrieve data using simple key-based access.
|
|
29
20
|
* **CachingService** – A cds.Service implementation with an intuitive API for seamless integration into CAP.
|
|
30
|
-
* **
|
|
21
|
+
* **Read-Through Capabilities** – Let the caching service handle the cache set and get operatios for you
|
|
31
22
|
* **CAP-specific Caching** – Effortlessly cache CQN queries or CAP cds.Requests using code or the @cache annotation.
|
|
32
23
|
* **TTL Support** – Automatically manage data expiration with configurable time-to-live (TTL) settings.
|
|
33
24
|
* **Tag Support** – Use dynamic tags for flexible cache invalidation options.
|
|
34
25
|
* **Pluggable Storage Options** – Choose between in-memory caching, SQLite or Redis.
|
|
35
26
|
* **Compression** – Compress cached data to save memory using LZ4 or GZIP.
|
|
36
|
-
* **Integrated
|
|
27
|
+
* **Integrated Metrics** – Monitor cache performance with hit rates, latencies, and more.
|
|
28
|
+
* **API** – Access basic cache operations and metrics via API
|
|
29
|
+
* **Event Handling** – Monitor and react to cache events, such as before/after storage and retrieval.
|
|
30
|
+
|
|
31
|
+
### Checkout detailed information on how to use cds-caching
|
|
32
|
+
|
|
33
|
+
> - [Programmatic API](docs/programmatic-api.md)
|
|
34
|
+
> - [Key Management](docs/key-management.md)
|
|
35
|
+
> - [Metrics Guide](docs/metrics-guide.md)
|
|
36
|
+
> - [OData API Reference](docs/odata-api.md)
|
|
37
|
+
|
|
38
|
+
## 🚨 Breaking Changes: Migrating from cds-caching 0.x
|
|
39
|
+
|
|
40
|
+
> **⚠️ Important:** Version 1.x contains breaking changes. Please review the migration guide below.
|
|
41
|
+
|
|
42
|
+
### 🔄 API Changes for read-through methods
|
|
43
|
+
|
|
44
|
+
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.
|
|
45
|
+
|
|
46
|
+
| **Old Method** | **New Method** | **Key Differences** |
|
|
47
|
+
|----------------|----------------|---------------------|
|
|
48
|
+
| `cache.run()` | `cache.rt.run()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
49
|
+
| `cache.send()` | `cache.rt.send()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
50
|
+
| `cache.wrap()` | `cache.rt.wrap()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
51
|
+
| `cache.exec()` | `cache.rt.exec()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
|
|
52
|
+
|
|
53
|
+
### 🔑 Key Template Changes
|
|
54
|
+
|
|
55
|
+
**Before (0.x):**
|
|
56
|
+
```javascript
|
|
57
|
+
// Old syntax - object with template property
|
|
58
|
+
await cache.set(query, result, {
|
|
59
|
+
key: { template: "user:{user}:{hash}" }
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**After (1.x):**
|
|
64
|
+
```javascript
|
|
65
|
+
// New syntax - direct string template
|
|
66
|
+
await cache.set(query, result, {
|
|
67
|
+
key: "user:{user}:{hash}"
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 🌍 Context Awareness Changes
|
|
72
|
+
|
|
73
|
+
**Default Behavior Changed:**
|
|
74
|
+
- **0.x:** Context (user, tenant, locale) was automatically included in some cache keys (ODataRequests)
|
|
75
|
+
- **1.x:** Context is **disabled by default** and can be enabled for **ALL** keys (unless overwritten)
|
|
76
|
+
|
|
77
|
+
**To Enable Context Awareness:**
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"cds": {
|
|
81
|
+
"requires": {
|
|
82
|
+
"caching": {
|
|
83
|
+
...
|
|
84
|
+
"keyManagement": {
|
|
85
|
+
"isUserAware": true, // Include user context in cache keys
|
|
86
|
+
"isTenantAware": true, // Include tenant context in cache keys
|
|
87
|
+
"isLocaleAware": false // Include locale context in cache keys
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 📚 Migration Examples
|
|
96
|
+
|
|
97
|
+
**Example 1: Basic Caching**
|
|
98
|
+
```javascript
|
|
99
|
+
// ❌ Old way (deprecated, but will still work)
|
|
100
|
+
const result = await cache.run(query, db)
|
|
101
|
+
|
|
102
|
+
// ✅ New way
|
|
103
|
+
const { result, cacheKey, metadata } = await cache.rt.run(query, db)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Example 2: Function Wrapping**
|
|
107
|
+
```javascript
|
|
108
|
+
// ❌ Old way (deprecated, but will still work)
|
|
109
|
+
const cachedFn = cache.wrap("key", expensiveOperation)
|
|
110
|
+
const result = await cachedFn("param1", "param2")
|
|
111
|
+
|
|
112
|
+
// ✅ New way
|
|
113
|
+
const cachedFn = cache.rt.wrap("key", expensiveOperation)
|
|
114
|
+
const { result, cacheKey, metadata } = await cachedFn("param1", "param2")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Example 3: Custom Key Templates**
|
|
118
|
+
```javascript
|
|
119
|
+
// ❌ Old way (will not work anymore)
|
|
120
|
+
await cache.set(data, value, {
|
|
121
|
+
key: { template: "user:{user}:{hash}" }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// ✅ New way
|
|
125
|
+
await cache.set(data, value, {
|
|
126
|
+
key: "user:{user}:{hash}"
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 🔍 What's New
|
|
131
|
+
|
|
132
|
+
- **Enhanced Metadata:** All read-through operations now return cache keys and performance metadata
|
|
133
|
+
- **Better Performance:** Context awareness is opt-in, reducing unnecessary key complexity
|
|
134
|
+
- **Improved Debugging:** Access to generated cache keys for troubleshooting
|
|
135
|
+
- **Flexible Configuration:** Global and per-operation key template control
|
|
136
|
+
|
|
137
|
+
For detailed API documentation, see [Programmatic API Reference](docs/programmatic-api.md).
|
|
37
138
|
|
|
38
139
|
### Installation
|
|
39
140
|
|
|
@@ -43,56 +144,136 @@ Installing and using cds-caching is straightforward since it's a CAP plugin. Sim
|
|
|
43
144
|
npm install cds-caching
|
|
44
145
|
```
|
|
45
146
|
|
|
46
|
-
|
|
147
|
+
### TypeScript Support
|
|
47
148
|
|
|
48
|
-
|
|
149
|
+
cds-caching includes comprehensive TypeScript definitions. The library is written in JavaScript but provides full TypeScript support for better development experience.
|
|
150
|
+
|
|
151
|
+
#### Basic Usage with TypeScript
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { CachingService, CacheOptions, ReadThroughResult } from 'cds-caching';
|
|
155
|
+
|
|
156
|
+
const cache = await cds.connect.to('caching') as CachingService;
|
|
157
|
+
|
|
158
|
+
// Basic cache operations
|
|
159
|
+
await cache.set('my-key', { data: 'value' }, { ttl: 3600 });
|
|
160
|
+
const value = await cache.get('my-key');
|
|
161
|
+
|
|
162
|
+
// Read-through operations with full type safety
|
|
163
|
+
const { result, cacheKey, metadata } = await cache.rt.send(request, service, {
|
|
164
|
+
ttl: 1800,
|
|
165
|
+
tags: ['user-data']
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Function wrapping with type inference
|
|
169
|
+
const cachedFunction = cache.rt.wrap('expensive-operation', async (id: string) => {
|
|
170
|
+
return await this.performExpensiveOperation(id);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const { result: operationResult } = await cachedFunction('user-123');
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
### Configuration
|
|
179
|
+
|
|
180
|
+
The cds-caching plugin supports comprehensive configuration through `package.json`. Here are all available configuration options:
|
|
181
|
+
|
|
182
|
+
#### Basic Service Configuration
|
|
183
|
+
|
|
184
|
+
**Minimal setup** (in-memory cache for development):
|
|
185
|
+
|
|
186
|
+
```json
|
|
49
187
|
{
|
|
50
188
|
"cds": {
|
|
51
189
|
"requires": {
|
|
52
190
|
"caching": {
|
|
53
191
|
"impl": "cds-caching",
|
|
54
|
-
"namespace": "
|
|
192
|
+
"namespace": "caching"
|
|
55
193
|
},
|
|
56
|
-
//
|
|
194
|
+
// Recommended: Define a specific caching service for different caching requirements
|
|
57
195
|
"bp-caching": {
|
|
58
196
|
"impl": "cds-caching",
|
|
59
|
-
"namespace": "
|
|
197
|
+
"namespace": "bp-caching"
|
|
60
198
|
}
|
|
61
199
|
}
|
|
62
200
|
}
|
|
63
201
|
}
|
|
64
202
|
```
|
|
65
203
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
For more control, you can specify additional options:
|
|
204
|
+
**Advanced configuration** with all options:
|
|
69
205
|
|
|
70
|
-
```
|
|
206
|
+
```json
|
|
71
207
|
{
|
|
72
208
|
"cds": {
|
|
73
209
|
"requires": {
|
|
74
210
|
"caching": {
|
|
75
211
|
"impl": "cds-caching",
|
|
76
|
-
"namespace": "
|
|
77
|
-
"store": "in-memory", // "in-memory"
|
|
212
|
+
"namespace": "caching",
|
|
213
|
+
"store": "in-memory", // "in-memory", "sqlite", or "redis"
|
|
78
214
|
"compression": "lz4", // "lz4" or "gzip"
|
|
79
|
-
"credentials": {
|
|
80
|
-
|
|
81
|
-
// Redis specific
|
|
215
|
+
"credentials": {
|
|
216
|
+
// Redis configuration
|
|
82
217
|
"host": "localhost",
|
|
83
218
|
"port": 6379,
|
|
84
219
|
"password": "optional",
|
|
85
220
|
"url": "redis://..." // Alternative: Redis connection URI
|
|
86
|
-
|
|
87
|
-
// SQLite
|
|
88
|
-
"url": "sqlite://./cache.sqlite"
|
|
221
|
+
|
|
222
|
+
// SQLite configuration
|
|
223
|
+
"url": "sqlite://./cache.sqlite",
|
|
89
224
|
"table": "cache",
|
|
90
225
|
"busyTimeout": 10000
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Read-Through (RT) Key Configuration
|
|
234
|
+
|
|
235
|
+
Configure default key templates for read-through operations:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"cds": {
|
|
240
|
+
"requires": {
|
|
241
|
+
"caching": {
|
|
242
|
+
...
|
|
243
|
+
"keyManagement": {
|
|
244
|
+
"isUserAware": true, // Include user context in cache keys
|
|
245
|
+
"isTenantAware": true, // Include tenant context in cache keys
|
|
246
|
+
"isLocaleAware": false // Include locale context in cache keys
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Default behavior** (if not configured): All context elements are disabled by default.
|
|
255
|
+
|
|
256
|
+
#### Environment-Specific Configuration
|
|
257
|
+
|
|
258
|
+
You can override settings for different environments:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"cds": {
|
|
263
|
+
"requires": {
|
|
264
|
+
"caching": {
|
|
265
|
+
"impl": "cds-caching",
|
|
266
|
+
"store": "redis",
|
|
267
|
+
"[development]": {
|
|
268
|
+
"credentials": {
|
|
269
|
+
"host": "localhost",
|
|
270
|
+
"port": 6379
|
|
271
|
+
}
|
|
91
272
|
},
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
273
|
+
"[production]": {
|
|
274
|
+
"credentials": {
|
|
275
|
+
"url": "redis://production-redis:6379"
|
|
276
|
+
}
|
|
96
277
|
}
|
|
97
278
|
}
|
|
98
279
|
}
|
|
@@ -100,6 +281,19 @@ For more control, you can specify additional options:
|
|
|
100
281
|
}
|
|
101
282
|
```
|
|
102
283
|
|
|
284
|
+
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).
|
|
285
|
+
|
|
286
|
+
### Service Definition
|
|
287
|
+
|
|
288
|
+
Add the following cds definition to your data model:
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
using {plugin.cds_caching.CachingApiService} from 'cds-caching/index.cds';
|
|
292
|
+
|
|
293
|
+
// Don't forget to protect the service, e.g.
|
|
294
|
+
annotate CachingApiService with @requires: 'authenticated-user';
|
|
295
|
+
```
|
|
296
|
+
|
|
103
297
|
### Real-World Usage and Deployment
|
|
104
298
|
|
|
105
299
|
|
|
@@ -200,8 +394,20 @@ resources:
|
|
|
200
394
|
|
|
201
395
|
### Usage Patterns
|
|
202
396
|
|
|
203
|
-
|
|
204
|
-
|
|
397
|
+
> ⚠️ **Deprecation Notice**: The following methods are deprecated since version 1.0 and will be removed in a future version:
|
|
398
|
+
> - `cache.run()` - use `cache.rt.run()` instead
|
|
399
|
+
> - `cache.exec()` - use `cache.rt.exec()` instead
|
|
400
|
+
> - `cache.wrap()` - use `cache.rt.wrap()` instead
|
|
401
|
+
> - `cache.send()` - use `cache.rt.send()` instead
|
|
402
|
+
>
|
|
403
|
+
> The `rt.xxx` methods provide enhanced functionality including:
|
|
404
|
+
> - **Read-through metadata**: Information about cache hits/misses and latency
|
|
405
|
+
> - **Consistent return format**: All methods return `{ result, cacheKey, metadata }` by default
|
|
406
|
+
>
|
|
407
|
+
> **Migration**: Simply replace `cache.method()` with `cache.rt.method()` and access the result via `.result` property if needed.
|
|
408
|
+
|
|
409
|
+
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:
|
|
410
|
+
#### 1. Low-Level Key-Value API for Read-Aside Caching
|
|
205
411
|
|
|
206
412
|
The most basic way to use cds-caching is through its key-value API:
|
|
207
413
|
|
|
@@ -210,16 +416,16 @@ The most basic way to use cds-caching is through its key-value API:
|
|
|
210
416
|
const cache = await cds.connect.to("caching")
|
|
211
417
|
|
|
212
418
|
// Store a value (can be any object)
|
|
213
|
-
await cache.set("
|
|
419
|
+
await cache.set("bp:1000001", businessPartnerData)
|
|
214
420
|
|
|
215
421
|
// Retrieve the value
|
|
216
|
-
await cache.get("
|
|
422
|
+
await cache.get("bp:1000001") // => businessPartnerData
|
|
217
423
|
|
|
218
424
|
// Check if the key exists
|
|
219
|
-
await cache.has("
|
|
425
|
+
await cache.has("bp:1000001") // => true/false
|
|
220
426
|
|
|
221
427
|
// Delete the key
|
|
222
|
-
await cache.delete("
|
|
428
|
+
await cache.delete("bp:1000001")
|
|
223
429
|
|
|
224
430
|
// Clear the whole cache
|
|
225
431
|
await cache.clear()
|
|
@@ -231,7 +437,7 @@ For more advanced CAP integration, cache CAP's CQN queries directly. By passing
|
|
|
231
437
|
|
|
232
438
|
```javascript
|
|
233
439
|
// Create and execute a CQN query
|
|
234
|
-
const query = SELECT.from(
|
|
440
|
+
const query = SELECT.from(BusinessPartners).where({ businessPartnerType: '2' })
|
|
235
441
|
const result = await db.run(query)
|
|
236
442
|
|
|
237
443
|
// Cache the result
|
|
@@ -240,20 +446,26 @@ await cache.set(query, result)
|
|
|
240
446
|
// Retrieve from cache using the same query
|
|
241
447
|
const cachedResult = await cache.get(query)
|
|
242
448
|
```
|
|
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.
|
|
449
|
+
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
450
|
|
|
245
451
|
```javascript
|
|
246
|
-
const query = SELECT.from(
|
|
452
|
+
const query = SELECT.from(BusinessPartners).where({ businessPartnerType: '2' })
|
|
247
453
|
|
|
248
454
|
// Runs the query internally and caches the result
|
|
249
|
-
const result = await cache.run(query, db)
|
|
455
|
+
const { result } = await cache.rt.run(query, db)
|
|
250
456
|
```
|
|
251
457
|
|
|
252
|
-
|
|
458
|
+
|
|
459
|
+
Because the cache key has been dynamically created at runtime, it will also be returned:
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
// Access the cacheKey for later usage
|
|
463
|
+
const { result, cacheKey } = await cache.rt.run(query, db)
|
|
464
|
+
```
|
|
253
465
|
|
|
254
466
|
#### 3. RemoteService Request-Level Caching
|
|
255
467
|
|
|
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.
|
|
468
|
+
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
469
|
|
|
258
470
|
```javascript
|
|
259
471
|
// Cache the requests to an exposed external entity
|
|
@@ -262,18 +474,19 @@ this.on('READ', BusinessPartners, async (req, next) => {
|
|
|
262
474
|
let value = await cache.get(req)
|
|
263
475
|
if(!value) {
|
|
264
476
|
value = await bupa.run(req)
|
|
265
|
-
await cache.set(req, value, { ttl:
|
|
477
|
+
await cache.set(req, value, { ttl: 30000 })
|
|
266
478
|
}
|
|
267
479
|
return value
|
|
268
480
|
})
|
|
269
481
|
```
|
|
270
482
|
|
|
271
|
-
Alternatively use read-through caching via the `run` method to let the caching service handle the caching transparently:
|
|
483
|
+
Alternatively use read-through caching via the `rt.run` method to let the caching service handle the caching transparently:
|
|
272
484
|
|
|
273
485
|
```javascript
|
|
274
486
|
this.on('READ', BusinessPartners, async (req, next) => {
|
|
275
487
|
const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
|
|
276
|
-
|
|
488
|
+
const { result } = await cache.rt.run(req, bupa)
|
|
489
|
+
return result
|
|
277
490
|
})
|
|
278
491
|
|
|
279
492
|
```
|
|
@@ -283,7 +496,7 @@ This will transparently cache the result of the request and return the cached re
|
|
|
283
496
|
### 4. ApplicationService Request-Level Caching
|
|
284
497
|
|
|
285
498
|
|
|
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
|
|
499
|
+
> 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
500
|
|
|
288
501
|
|
|
289
502
|
But not only external services can be cached, it's also possible to cache requests against an ApplicationService.
|
|
@@ -299,7 +512,8 @@ class MyService extends cds.ApplicationService {
|
|
|
299
512
|
const { MyEntity } = this.entities;
|
|
300
513
|
this.on('READ', MyEntity, async (req, next) => {
|
|
301
514
|
const cache = cds.connect.to("caching");
|
|
302
|
-
|
|
515
|
+
const { result } = await cache.rt.run(req, next)
|
|
516
|
+
return result;
|
|
303
517
|
});
|
|
304
518
|
});
|
|
305
519
|
return super.init()
|
|
@@ -314,7 +528,7 @@ Alternatively to doing this via code, you can use annotations to enable caching
|
|
|
314
528
|
```
|
|
315
529
|
service MyService {
|
|
316
530
|
@cache: {
|
|
317
|
-
ttl:
|
|
531
|
+
ttl: 10000 // 10 seconds
|
|
318
532
|
}
|
|
319
533
|
entity BusinessPartners as projection on BusinessPartner {
|
|
320
534
|
// ... entity definition
|
|
@@ -322,7 +536,7 @@ service MyService {
|
|
|
322
536
|
|
|
323
537
|
|
|
324
538
|
@cache: {
|
|
325
|
-
ttl:
|
|
539
|
+
ttl: 100000, // 10 seconds
|
|
326
540
|
tags: [{
|
|
327
541
|
template: 'user-{user}'
|
|
328
542
|
}]
|
|
@@ -337,35 +551,55 @@ While not directly related to CAP functionality, the caching service provides tw
|
|
|
337
551
|
|
|
338
552
|
```javascript
|
|
339
553
|
// Using wrap() to create a cached version of a function
|
|
340
|
-
const
|
|
341
|
-
// ... some expensive computation
|
|
342
|
-
return
|
|
554
|
+
const fetchBusinessPartnerData = async (businessPartnerId, includeAddresses) => {
|
|
555
|
+
// ... some expensive computation to fetch BP data
|
|
556
|
+
return businessPartnerData
|
|
343
557
|
}
|
|
344
558
|
|
|
345
|
-
// Creates a cached version of the function
|
|
346
|
-
const
|
|
559
|
+
// Creates a cached version of the function.
|
|
560
|
+
const cachedBpOperation = cache.rt.wrap("bp-data", fetchBusinessPartnerData, {
|
|
347
561
|
ttl: 3600,
|
|
348
|
-
tags: ['
|
|
562
|
+
tags: ['business-partner']
|
|
349
563
|
})
|
|
350
564
|
|
|
351
565
|
// Each call checks cache first, only executes if cache miss
|
|
352
|
-
const result = await
|
|
566
|
+
const result = await cachedBpOperation("1000001", true)
|
|
353
567
|
|
|
354
568
|
// Using exec() for immediate execution with caching
|
|
355
|
-
const result = await cache.exec("
|
|
356
|
-
// ... some expensive computation
|
|
357
|
-
return
|
|
358
|
-
}, {
|
|
569
|
+
const result = await cache.rt.exec("product-data", async (productId) => {
|
|
570
|
+
// ... some expensive computation to fetch product data
|
|
571
|
+
return productData
|
|
572
|
+
}, ["1000001"], {
|
|
359
573
|
ttl: 3600,
|
|
360
|
-
tags: ['
|
|
574
|
+
tags: ['product']
|
|
575
|
+
})
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
The key differences between `rt.wrap()` and `rt.exec()`:
|
|
579
|
+
- `rt.wrap()` returns a new function that includes caching logic
|
|
580
|
+
- `rt.exec()` immediately executes the function and caches the result
|
|
581
|
+
- Use `rt.wrap()` when you need to reuse the cached function multiple times
|
|
582
|
+
- Use `rt.exec()` for one-off executions with caching
|
|
583
|
+
|
|
584
|
+
#### Dynamic Key Generation
|
|
585
|
+
|
|
586
|
+
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.
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
// Different arguments = different cache keys
|
|
590
|
+
const result1 = await cachedBpOperation("1000001", true) // Cache key: "bp-data:1000001:true"
|
|
591
|
+
const result2 = await cachedBpOperation("1000002", false) // Cache key: "bp-data:1000002:false"
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
You can override this behavior by providing a custom key template:
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
const cachedOperation = cache.rt.wrap("bp-profile", fetchBusinessPartnerData, {
|
|
598
|
+
key: "profile:{args[0]}:{args[1]}"
|
|
361
599
|
})
|
|
362
600
|
```
|
|
363
601
|
|
|
364
|
-
|
|
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
|
|
602
|
+
For detailed information on how read-through keys are generated and configured, see [Key Management](docs/key-management.md).
|
|
369
603
|
|
|
370
604
|
### Cache Invalidation Strategies
|
|
371
605
|
|
|
@@ -383,19 +617,19 @@ The TTL can be specified for individually through all cache methods (e.g. `set`,
|
|
|
383
617
|
await cache.set("key", "value", { ttl: 60000 })
|
|
384
618
|
|
|
385
619
|
// Run with 30 seconds TTL
|
|
386
|
-
const result = await cache.run(query, db, { ttl: 30000 })
|
|
620
|
+
const { result } = await cache.rt.run(query, db, { ttl: 30000 })
|
|
387
621
|
|
|
388
622
|
// Send with 10 seconds TTL
|
|
389
|
-
const result = await cache.send(request, service, { ttl: 10000 })
|
|
623
|
+
const { result } = await cache.rt.send(request, service, { ttl: 10000 })
|
|
390
624
|
|
|
391
625
|
// Wrap with 10 seconds TTL
|
|
392
|
-
const cachedOperation = cache.wrap("key", expensiveOperation, { ttl: 10000 })
|
|
626
|
+
const cachedOperation = cache.rt.wrap("key", expensiveOperation, { ttl: 10000 })
|
|
393
627
|
|
|
394
628
|
// Exec with 10 seconds TTL
|
|
395
|
-
const result = await cache.exec("key", async () => {
|
|
629
|
+
const { result } = await cache.rt.exec("key", async () => {
|
|
396
630
|
// ... some expensive computation
|
|
397
631
|
return result
|
|
398
|
-
}, {
|
|
632
|
+
}, [] {
|
|
399
633
|
ttl: 10000
|
|
400
634
|
})
|
|
401
635
|
```
|
|
@@ -408,43 +642,42 @@ Key-based invalidation is a way to invalidate cache entries based on a specific
|
|
|
408
642
|
await cache.delete("key")
|
|
409
643
|
```
|
|
410
644
|
|
|
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.
|
|
645
|
+
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.
|
|
646
|
+
|
|
647
|
+
**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
648
|
|
|
413
649
|
```javascript
|
|
414
650
|
// No key override given, string will just be used as keys
|
|
415
|
-
await cache.set('
|
|
651
|
+
await cache.set('bp:1000001', businessPartnerData) // key: bp:1000001
|
|
416
652
|
|
|
417
653
|
// No key override given, objects will be smartly hashed
|
|
418
|
-
await cache.set(SELECT.from(
|
|
654
|
+
await cache.set(SELECT.from(BusinessPartners)) // key: bd3f3690d3e96a569bd89d9e207a89af
|
|
419
655
|
|
|
420
656
|
// Automatically build the key for retrieval/deletion
|
|
421
|
-
cache.createKey(SELECT.from(
|
|
657
|
+
cache.createKey(SELECT.from(BusinessPartners)) // key: bd3f3690d3e96a569bd89d9e207a89af
|
|
422
658
|
|
|
423
659
|
// Override and use your own key based on a fixed value
|
|
424
|
-
await cache.set(SELECT.from(
|
|
660
|
+
await cache.set(SELECT.from(BusinessPartners, 1000001), { key: "bp:1000001" })
|
|
425
661
|
|
|
426
|
-
//
|
|
427
|
-
|
|
662
|
+
// RT methods return the generated cache key
|
|
663
|
+
const { result, cacheKey } = await cache.rt.run(query, db)
|
|
664
|
+
console.log('Generated key:', cacheKey) // e.g., "tenant-acme:user-john:locale-en:hash-abc123"
|
|
428
665
|
|
|
429
|
-
//
|
|
430
|
-
await cache.
|
|
431
|
-
```
|
|
666
|
+
// Override RT key template for requests
|
|
667
|
+
await cache.rt.run(req, remoteService, { key: "mykey:{tenant}:{user}:{locale}:{hash}" })
|
|
432
668
|
|
|
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.
|
|
669
|
+
// This requests will be cached for all users and for each locale
|
|
670
|
+
await cache.rt.run(req, remoteService, { key: "mykey:{user}:{locale}:{hash}" })
|
|
442
671
|
|
|
443
|
-
|
|
672
|
+
// Function wrapping with custom key template
|
|
673
|
+
const cachedFunction = cache.rt.wrap("user-data", expensiveOperation, {
|
|
674
|
+
key: "user:{user}:{args[0]}"
|
|
675
|
+
})
|
|
676
|
+
```
|
|
444
677
|
|
|
445
678
|
#### 3. Tag-Based
|
|
446
679
|
|
|
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`).
|
|
680
|
+
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
681
|
Tags can be provided as an array of strings or as an array of objects with the following properties:
|
|
449
682
|
- `value`: The value to use for the tag.
|
|
450
683
|
- `data`: A field from the value to use for the tag. This is working for objects and arrays of objects.
|
|
@@ -461,22 +694,22 @@ Templates support the following properties:
|
|
|
461
694
|
|
|
462
695
|
```javascript
|
|
463
696
|
// Store with static tag
|
|
464
|
-
await cache.set("
|
|
465
|
-
tags: [{ value: "
|
|
697
|
+
await cache.set("bp:1000001", businessPartnerData, {
|
|
698
|
+
tags: [{ value: "bp-1000001" }]
|
|
466
699
|
})
|
|
467
700
|
|
|
468
701
|
// Store with template tag (will generate a tag like "tenant-global-user-anonymous")
|
|
469
|
-
await cache.set("
|
|
702
|
+
await cache.set("bp:1000001", businessPartnerData, {
|
|
470
703
|
tags: [{ template: "tenant-{tenant}-user-{user}" }]
|
|
471
704
|
})
|
|
472
705
|
|
|
473
706
|
// Store with data-based tag
|
|
474
|
-
await cache.set("
|
|
475
|
-
tags: [{ data: "
|
|
707
|
+
await cache.set("product:1000001", { productId: 1000001, name: "Laptop Computer" }, {
|
|
708
|
+
tags: [{ data: "productId", prefix: "product-" }]
|
|
476
709
|
})
|
|
477
710
|
|
|
478
711
|
// Invalidate by tag
|
|
479
|
-
await cache.deleteByTag('
|
|
712
|
+
await cache.deleteByTag('bp-1000001')
|
|
480
713
|
```
|
|
481
714
|
This is really useful for invalidating cache entries based on a specific attribute or context.
|
|
482
715
|
|
|
@@ -488,17 +721,17 @@ Dynamic tags using data `data` property are a way to invalidate cache entries ba
|
|
|
488
721
|
|
|
489
722
|
const businessPartners = [
|
|
490
723
|
{
|
|
491
|
-
businessPartner:
|
|
492
|
-
name: '
|
|
724
|
+
businessPartner: 1000001,
|
|
725
|
+
name: 'Acme Corporation'
|
|
493
726
|
},
|
|
494
727
|
{
|
|
495
|
-
businessPartner:
|
|
496
|
-
name: '
|
|
728
|
+
businessPartner: 1000002,
|
|
729
|
+
name: 'Tech Solutions Ltd'
|
|
497
730
|
}
|
|
498
731
|
]
|
|
499
732
|
|
|
500
733
|
// Store with dynamic tags
|
|
501
|
-
await cache.set("
|
|
734
|
+
await cache.set("bp-list", businessPartners, {
|
|
502
735
|
tags: [
|
|
503
736
|
{ data: 'businessPartner', prefix: 'bp-' },
|
|
504
737
|
{ value: "businessPartner" }
|
|
@@ -506,17 +739,17 @@ await cache.set("key", businessPartners, {
|
|
|
506
739
|
})
|
|
507
740
|
|
|
508
741
|
// Introspect the tags
|
|
509
|
-
const tags = await cache.tags("
|
|
742
|
+
const tags = await cache.tags("bp-list") // => ["bp-1000001", "bp-1000002", "businessPartner"]
|
|
510
743
|
|
|
511
744
|
// Invalidate by tag
|
|
512
|
-
await cache.deleteByTag('bp-
|
|
513
|
-
await cache.deleteByTag('bp-
|
|
745
|
+
await cache.deleteByTag('bp-1000001')
|
|
746
|
+
await cache.deleteByTag('bp-1000002')
|
|
514
747
|
```
|
|
515
748
|
|
|
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.
|
|
749
|
+
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
750
|
|
|
518
751
|
```javascript
|
|
519
|
-
const result = await cache.run(query, db, {
|
|
752
|
+
const result = await cache.rt.run(query, db, {
|
|
520
753
|
tags: [{ data: 'businessPartner', prefix: 'bp-' }]
|
|
521
754
|
})
|
|
522
755
|
```
|
|
@@ -582,222 +815,314 @@ Instead of caching entire OData service responses, focus on:
|
|
|
582
815
|
3. **Multi-Tenant**: Use appropriate namespacing and key strategies
|
|
583
816
|
4. **Redis Setup**: Ensure proper configuration for production use
|
|
584
817
|
|
|
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:
|
|
618
|
-
|
|
619
|
-
| Property | Description | Example |
|
|
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'}]`
|
|
624
|
-
|
|
625
|
-
---
|
|
626
|
-
|
|
627
|
-
### `await cache.get(key: any)`
|
|
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)`
|
|
818
|
+
## Enhanced Statistics & Monitoring
|
|
642
819
|
|
|
643
|
-
|
|
820
|
+
The plugin now includes comprehensive statistics and monitoring capabilities that provide deep insights into cache performance and help optimize cache usage.
|
|
644
821
|
|
|
645
|
-
|
|
822
|
+
[See the full Metrics Guide →](docs/metrics-guide.md)
|
|
646
823
|
|
|
647
|
-
|
|
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
|
-
---
|
|
824
|
+
### Key Features
|
|
698
825
|
|
|
699
|
-
|
|
826
|
+
- **Real-time Metrics**: Monitor cache performance with detailed hit rates, latencies, and throughput
|
|
827
|
+
- **Key-level Tracking**: Track performance metrics for individual cache keys
|
|
828
|
+
- **Historical Data**: Store and analyze metrics over time (hourly/daily periods)
|
|
829
|
+
- **Performance Analytics**: Calculate cache efficiency, error rates, and response times
|
|
830
|
+
- **Runtime Configuration**: Enable/disable metrics at runtime without restart
|
|
831
|
+
- **API Access**: Access metrics programmatically or via OData service
|
|
700
832
|
|
|
701
|
-
|
|
833
|
+
### Metrics Overview
|
|
702
834
|
|
|
703
|
-
|
|
835
|
+
cds-caching provides two types of metrics:
|
|
704
836
|
|
|
705
|
-
|
|
837
|
+
#### 1. General Cache Metrics
|
|
838
|
+
Track overall cache performance including:
|
|
839
|
+
- **Hit/Miss Statistics**: Total hits, misses, and hit ratios
|
|
840
|
+
- **Latency Metrics**: Average, min, max, and percentile latencies for hits and misses
|
|
841
|
+
- **Performance Metrics**: Throughput (requests/second), error rates, cache efficiency
|
|
842
|
+
- **Memory Usage**: Current memory consumption and item count
|
|
843
|
+
- **Native Operations**: Counts of direct cache operations (set, get, delete, etc.)
|
|
706
844
|
|
|
707
|
-
####
|
|
845
|
+
#### 2. Key-level Metrics
|
|
846
|
+
Track performance for individual cache keys including:
|
|
847
|
+
- **Key-specific Statistics**: Hits, misses, and hit ratios per key
|
|
848
|
+
- **Context Information**: Data type, service name, entity name, operation type
|
|
849
|
+
- **Enhanced Metadata**: Query text, request info, function names, user/tenant context
|
|
850
|
+
- **Performance Tracking**: Latency and throughput metrics per key
|
|
708
851
|
|
|
709
|
-
|
|
852
|
+
### Enabling Metrics
|
|
710
853
|
|
|
711
|
-
|
|
854
|
+
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
855
|
|
|
713
|
-
|
|
856
|
+
To enable metrics programmatically:
|
|
714
857
|
|
|
715
|
-
|
|
858
|
+
```javascript
|
|
859
|
+
// Connect to the caching service
|
|
860
|
+
const cache = await cds.connect.to("caching")
|
|
716
861
|
|
|
717
|
-
|
|
862
|
+
// Enable metrics at runtime
|
|
863
|
+
await cache.setMetricsEnabled(true)
|
|
864
|
+
await cache.setKeyMetricsEnabled(true)
|
|
865
|
+
```
|
|
718
866
|
|
|
719
|
-
|
|
867
|
+
Or via OData API:
|
|
720
868
|
|
|
721
|
-
|
|
869
|
+
```http
|
|
870
|
+
### Enable general metrics
|
|
871
|
+
POST http://localhost:4004/odata/v4/caching-api/Caches('caching')/setMetricsEnabled
|
|
872
|
+
Content-Type: application/json
|
|
722
873
|
|
|
723
|
-
|
|
874
|
+
{
|
|
875
|
+
"enabled": true
|
|
876
|
+
}
|
|
724
877
|
|
|
725
|
-
|
|
878
|
+
### Enable key-level metrics
|
|
879
|
+
POST http://localhost:4004/odata/v4/caching-api/Caches('caching')/setKeyMetricsEnabled
|
|
880
|
+
Content-Type: application/json
|
|
726
881
|
|
|
727
|
-
|
|
882
|
+
{
|
|
883
|
+
"enabled": true
|
|
884
|
+
}
|
|
885
|
+
```
|
|
728
886
|
|
|
729
|
-
|
|
887
|
+
### Accessing Metrics via Caching Service
|
|
730
888
|
|
|
731
|
-
The
|
|
889
|
+
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
890
|
|
|
733
|
-
####
|
|
891
|
+
#### Metrics Persistence
|
|
734
892
|
|
|
735
|
-
|
|
893
|
+
**Transient Metrics**: Current statistics are kept in memory and provide real-time insights into cache performance:
|
|
894
|
+
- Hit/miss ratios
|
|
895
|
+
- Current latency statistics
|
|
896
|
+
- Active cache entries
|
|
897
|
+
- Key-level performance data
|
|
736
898
|
|
|
737
|
-
|
|
899
|
+
**Persisted Metrics**: Historical data is automatically stored in the database for long-term analysis:
|
|
900
|
+
- Hourly aggregated statistics
|
|
901
|
+
- Key-level metrics over time
|
|
902
|
+
- Performance trends and patterns
|
|
903
|
+
- Cache efficiency analysis
|
|
738
904
|
|
|
739
|
-
|
|
905
|
+
#### Current Statistics
|
|
740
906
|
|
|
741
|
-
|
|
907
|
+
```javascript
|
|
908
|
+
// Connect to the caching service
|
|
909
|
+
const cache = await cds.connect.to("caching")
|
|
742
910
|
|
|
743
|
-
|
|
911
|
+
// Get current statistics
|
|
912
|
+
const stats = await cache.getCurrentStats()
|
|
913
|
+
console.log('Hit ratio:', stats.hitRatio)
|
|
914
|
+
console.log('Average hit latency:', stats.avgHitLatency)
|
|
915
|
+
console.log('Throughput:', stats.throughput)
|
|
916
|
+
|
|
917
|
+
// Get current key metrics
|
|
918
|
+
const keyMetrics = await cache.getCurrentKeyMetrics()
|
|
919
|
+
for (const [key, metrics] of keyMetrics) {
|
|
920
|
+
console.log(`Key ${key}:`, {
|
|
921
|
+
hits: metrics.hits,
|
|
922
|
+
misses: metrics.misses,
|
|
923
|
+
hitRatio: metrics.hitRatio,
|
|
924
|
+
avgHitLatency: metrics.avgHitLatency
|
|
925
|
+
})
|
|
926
|
+
}
|
|
927
|
+
```
|
|
744
928
|
|
|
745
|
-
|
|
929
|
+
#### Historical Metrics
|
|
746
930
|
|
|
747
|
-
|
|
931
|
+
```javascript
|
|
932
|
+
// Get metrics for a specific time period
|
|
933
|
+
const from = new Date('2024-01-01')
|
|
934
|
+
const to = new Date('2024-01-31')
|
|
935
|
+
const historicalStats = await cache.getMetrics(from, to)
|
|
748
936
|
|
|
749
|
-
|
|
937
|
+
// Get key-specific metrics
|
|
938
|
+
const keyStats = await cache.getKeyMetrics('my-cache-key', from, to)
|
|
939
|
+
```
|
|
750
940
|
|
|
751
|
-
####
|
|
941
|
+
#### Runtime Configuration
|
|
752
942
|
|
|
753
|
-
|
|
943
|
+
```javascript
|
|
944
|
+
// Enable/disable metrics at runtime
|
|
945
|
+
await cache.setMetricsEnabled(true)
|
|
946
|
+
await cache.setKeyMetricsEnabled(true)
|
|
947
|
+
|
|
948
|
+
// Get current configuration
|
|
949
|
+
const config = await cache.getRuntimeConfiguration()
|
|
950
|
+
console.log('Metrics enabled:', config.metricsEnabled)
|
|
951
|
+
console.log('Key metrics enabled:', config.keyMetricsEnabled)
|
|
952
|
+
|
|
953
|
+
// Clear metrics
|
|
954
|
+
await cache.clearMetrics()
|
|
955
|
+
await cache.clearKeyMetrics()
|
|
956
|
+
```
|
|
754
957
|
|
|
755
|
-
|
|
958
|
+
### Metrics Data Structure
|
|
756
959
|
|
|
757
|
-
|
|
960
|
+
#### General Cache Statistics (Metrics Entity)
|
|
758
961
|
|
|
759
|
-
|
|
962
|
+
```javascript
|
|
963
|
+
{
|
|
964
|
+
// Entity identification
|
|
965
|
+
ID: "daily:2024-01-15", // Unique identifier (period:date)
|
|
966
|
+
cache: "caching", // Cache name
|
|
967
|
+
timestamp: "2024-01-15T10:30:00Z", // When metrics were recorded
|
|
968
|
+
period: "daily", // Aggregation period (hourly/daily/monthly)
|
|
969
|
+
|
|
970
|
+
// Read-through metrics
|
|
971
|
+
hits: 1500, // Number of cache hits
|
|
972
|
+
misses: 300, // Number of cache misses
|
|
973
|
+
errors: 5, // Number of errors
|
|
974
|
+
totalRequests: 1800, // Total read-through requests
|
|
975
|
+
|
|
976
|
+
// Read-through latency metrics (milliseconds)
|
|
977
|
+
avgHitLatency: 2.5, // Average hit latency
|
|
978
|
+
minHitLatency: 0.1, // Minimum hit latency
|
|
979
|
+
maxHitLatency: 15.2, // Maximum hit latency
|
|
980
|
+
avgMissLatency: 45.8, // Average miss latency
|
|
981
|
+
minMissLatency: 12.3, // Minimum miss latency
|
|
982
|
+
maxMissLatency: 120.5, // Maximum miss latency
|
|
983
|
+
avgReadThroughLatency: 8.9, // Average read-through latency
|
|
984
|
+
|
|
985
|
+
// Read-through performance metrics
|
|
986
|
+
hitRatio: 0.833, // Hit ratio as percentage (83.3%)
|
|
987
|
+
throughput: 25.5, // Requests per second
|
|
988
|
+
errorRate: 0.003, // Error rate as percentage (0.3%)
|
|
989
|
+
cacheEfficiency: 18.3, // Miss latency / hit latency ratio
|
|
990
|
+
|
|
991
|
+
// Native operation metrics
|
|
992
|
+
nativeSets: 200, // Number of direct set operations
|
|
993
|
+
nativeGets: 800, // Number of direct get operations
|
|
994
|
+
nativeDeletes: 50, // Number of direct delete operations
|
|
995
|
+
nativeClears: 2, // Number of clear operations
|
|
996
|
+
nativeDeleteByTags: 10, // Number of delete-by-tag operations
|
|
997
|
+
nativeErrors: 1, // Number of native operation errors
|
|
998
|
+
totalNativeOperations: 1063, // Total native operations
|
|
999
|
+
nativeThroughput: 17.7, // Native operations per second
|
|
1000
|
+
nativeErrorRate: 0.001, // Native operation error rate (0.1%)
|
|
1001
|
+
|
|
1002
|
+
// System metrics
|
|
1003
|
+
memoryUsage: 52428800, // Memory usage in bytes
|
|
1004
|
+
itemCount: 150, // Number of items in cache
|
|
1005
|
+
uptimeMs: 7200000 // Cache uptime in milliseconds
|
|
1006
|
+
}
|
|
1007
|
+
```
|
|
760
1008
|
|
|
761
|
-
|
|
1009
|
+
#### Key-level Metrics (KeyMetrics Entity)
|
|
762
1010
|
|
|
763
|
-
|
|
1011
|
+
```javascript
|
|
1012
|
+
{
|
|
1013
|
+
// Entity identification
|
|
1014
|
+
ID: "key:user-preferences:123", // Unique identifier
|
|
1015
|
+
cache: "caching", // Cache name
|
|
1016
|
+
keyName: "user-preferences:123", // Cache key name
|
|
1017
|
+
lastAccess: "2024-01-15T10:30:00Z", // Last access time
|
|
1018
|
+
period: "current", // Period type (current/hourly/daily)
|
|
1019
|
+
operationType: "read_through", // Operation category (read_through/native/mixed)
|
|
1020
|
+
|
|
1021
|
+
// Read-through metrics
|
|
1022
|
+
hits: 45, // Number of hits for this key
|
|
1023
|
+
misses: 5, // Number of misses for this key
|
|
1024
|
+
errors: 0, // Number of errors for this key
|
|
1025
|
+
totalRequests: 50, // Total requests for this key
|
|
1026
|
+
hitRatio: 0.9, // Hit ratio for this key (90%)
|
|
1027
|
+
cacheEfficiency: 21.2, // Cache efficiency for this key
|
|
1028
|
+
|
|
1029
|
+
// Read-through latency metrics (milliseconds)
|
|
1030
|
+
avgHitLatency: 1.2, // Average hit latency for this key
|
|
1031
|
+
minHitLatency: 0.5, // Minimum hit latency for this key
|
|
1032
|
+
maxHitLatency: 3.1, // Maximum hit latency for this key
|
|
1033
|
+
avgMissLatency: 25.4, // Average miss latency for this key
|
|
1034
|
+
minMissLatency: 15.2, // Minimum miss latency for this key
|
|
1035
|
+
maxMissLatency: 45.8, // Maximum miss latency for this key
|
|
1036
|
+
avgReadThroughLatency: 3.8, // Average read-through latency for this key
|
|
1037
|
+
|
|
1038
|
+
// Read-through performance metrics
|
|
1039
|
+
throughput: 2.5, // Requests per second for this key
|
|
1040
|
+
errorRate: 0.0, // Error rate for this key (0%)
|
|
1041
|
+
|
|
1042
|
+
// Native operation metrics for this key
|
|
1043
|
+
nativeHits: 10, // Native hits for this key
|
|
1044
|
+
nativeMisses: 2, // Native misses for this key
|
|
1045
|
+
nativeSets: 5, // Native sets for this key
|
|
1046
|
+
nativeDeletes: 1, // Native deletes for this key
|
|
1047
|
+
nativeClears: 0, // Native clears for this key
|
|
1048
|
+
nativeDeleteByTags: 0, // Native delete-by-tags for this key
|
|
1049
|
+
nativeErrors: 0, // Native errors for this key
|
|
1050
|
+
totalNativeOperations: 18, // Total native operations for this key
|
|
1051
|
+
nativeThroughput: 0.5, // Native operations per second for this key
|
|
1052
|
+
nativeErrorRate: 0.0, // Native error rate for this key
|
|
1053
|
+
|
|
1054
|
+
// Context and metadata
|
|
1055
|
+
dataType: "request", // Type of data (query/request/function/custom)
|
|
1056
|
+
operation: "READ", // Cache operation type
|
|
1057
|
+
metadata: '{"ttl":3600}', // JSON string with additional metadata
|
|
1058
|
+
context: '{"user":"john.doe","tenant":"acme"}', // JSON string with context
|
|
1059
|
+
query: "SELECT * FROM UserPreferences WHERE userId = '123'", // CQL query text
|
|
1060
|
+
subject: '{"entity":"UserPreferences"}', // JSON string with subject info
|
|
1061
|
+
target: "UserService", // Target service name
|
|
1062
|
+
tenant: "acme", // Tenant information
|
|
1063
|
+
user: "john.doe", // User information
|
|
1064
|
+
locale: "en-US", // Locale information
|
|
1065
|
+
cacheOptions: '{"ttl":3600}', // JSON string with cache options
|
|
1066
|
+
timestamp: "2024-01-15T09:00:00Z" // When this key was first accessed
|
|
1067
|
+
}
|
|
1068
|
+
```
|
|
764
1069
|
|
|
765
|
-
|
|
1070
|
+
### Best Practices for Metrics
|
|
766
1071
|
|
|
767
|
-
|
|
1072
|
+
1. **Enable Selectively**: Only enable metrics when needed for monitoring or debugging
|
|
1073
|
+
2. **Monitor Memory Usage**: Key metrics can consume significant memory for large caches
|
|
1074
|
+
3. **Set Appropriate Intervals**: Balance persistence frequency with performance impact
|
|
1075
|
+
4. **Use Historical Data**: Analyze trends over time to optimize cache configuration
|
|
1076
|
+
5. **Monitor Error Rates**: High error rates may indicate configuration issues
|
|
1077
|
+
6. **Track Cache Efficiency**: Aim for high cache efficiency (miss latency >> hit latency)
|
|
768
1078
|
|
|
769
|
-
|
|
1079
|
+
## API Reference
|
|
770
1080
|
|
|
771
|
-
|
|
1081
|
+
The cds-caching plugin provides two APIs for managing cache operations:
|
|
772
1082
|
|
|
773
|
-
|
|
1083
|
+
- **Programmatic API** - JavaScript methods for use within your CAP application code
|
|
1084
|
+
- **OData API** - REST endpoints for external applications and monitoring tools
|
|
774
1085
|
|
|
775
|
-
|
|
1086
|
+
### Programmatic API
|
|
776
1087
|
|
|
777
|
-
The
|
|
1088
|
+
The programmatic API provides methods for direct cache operations within your CAP application:
|
|
778
1089
|
|
|
779
|
-
|
|
1090
|
+
```javascript
|
|
1091
|
+
// Connect to the caching service
|
|
1092
|
+
const cache = await cds.connect.to("caching")
|
|
780
1093
|
|
|
781
|
-
|
|
1094
|
+
// Basic operations
|
|
1095
|
+
await cache.set("key", "value")
|
|
1096
|
+
const value = await cache.get("key")
|
|
1097
|
+
await cache.delete("key")
|
|
782
1098
|
|
|
783
|
-
|
|
1099
|
+
// Read-through operations
|
|
1100
|
+
const result = await cache.rt.run(query, db)
|
|
1101
|
+
const result = await cache.rt.send(request, service)
|
|
784
1102
|
|
|
785
|
-
|
|
1103
|
+
// Metrics and statistics
|
|
1104
|
+
const stats = await cache.getCurrentStats()
|
|
1105
|
+
const keyMetrics = await cache.getCurrentKeyMetrics()
|
|
1106
|
+
```
|
|
786
1107
|
|
|
787
|
-
|
|
1108
|
+
[See the full Programmatic API Reference →](docs/programmatic-api.md)
|
|
788
1109
|
|
|
789
|
-
|
|
1110
|
+
### OData API
|
|
790
1111
|
|
|
791
|
-
The
|
|
1112
|
+
The OData API provides REST endpoints for external applications, monitoring tools, and administrative interfaces:
|
|
792
1113
|
|
|
793
|
-
|
|
1114
|
+
```http
|
|
1115
|
+
### Get cache statistics
|
|
1116
|
+
GET /odata/v4/caching-api/Metrics?$filter=cache eq 'mycache'
|
|
794
1117
|
|
|
795
|
-
|
|
1118
|
+
### Get cache entries
|
|
1119
|
+
GET /odata/v4/caching-api/Caches('mycache')/getEntries()
|
|
796
1120
|
|
|
797
|
-
|
|
798
|
-
-
|
|
1121
|
+
### Clear cache
|
|
1122
|
+
POST /odata/v4/caching-api/Caches('mycache')/clear()
|
|
1123
|
+
```
|
|
799
1124
|
|
|
800
|
-
|
|
1125
|
+
[See the full OData API Reference →](docs/odata-api.md)
|
|
801
1126
|
|
|
802
1127
|
### Contributing
|
|
803
1128
|
|
|
@@ -805,4 +1130,4 @@ Contributions are welcome! Please read our contributing guidelines and submit pu
|
|
|
805
1130
|
|
|
806
1131
|
### License
|
|
807
1132
|
|
|
808
|
-
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
1133
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|