optimierungen auth-Modul und cache-Modul
This commit is contained in:
+81
-462
@@ -1,495 +1,114 @@
|
||||
# Infrastructure/Cache Module - Comprehensive Documentation
|
||||
# Infrastructure/Cache – Modulbeschreibung und Implementierungsleitfaden
|
||||
|
||||
*Letzte Aktualisierung: 15. August 2025*
|
||||
Letzte Aktualisierung: 03. September 2025
|
||||
|
||||
## Überblick
|
||||
## Zweck und Aufgaben des Moduls
|
||||
Das Infrastructure/Cache-Modul stellt eine einheitliche, technologie‑neutrale Cache‑Schnittstelle für alle Services bereit und liefert mit einer Redis‑basierten Adapter‑Implementierung die produktionsreife Ausführung. Ziele:
|
||||
- Antwortzeiten reduzieren und Primärdatenbanken entlasten.
|
||||
- Einheitliche API für Lesen/Schreiben, Batch‑Operationen und TTLs.
|
||||
- Resilienz bei Redis‑Ausfällen durch lokalen Fallback.
|
||||
- Operative Transparenz durch einfache Metriken, Health‑Informationen und periodische Wartungsaufgaben.
|
||||
|
||||
Das **Cache-Modul** stellt eine zentrale, hochverfügbare und produktionsbereite Caching-Infrastruktur für alle Microservices bereit. Es dient der Verbesserung der Anwendungsperformance, der Reduzierung von Latenzen und der Entlastung der primären PostgreSQL-Datenbank.
|
||||
## Architektur (Port‑Adapter)
|
||||
- cache‑api: enthält die öffentlichen Verträge und Basistypen
|
||||
- DistributedCache: zentrale Port‑Schnittstelle für Cache‑Operationen
|
||||
- CacheEntry, CacheConfiguration, CacheSerializer
|
||||
- ConnectionStatusTracker/ConnectionStateListener zur Verbindungsüberwachung
|
||||
- redis‑cache: Adapter, der die Port‑Schnittstelle mit Spring Data Redis umsetzt
|
||||
- RedisDistributedCache: konkrete Implementierung inkl. Offline‑Fallback, Dirty‑Sync, Batchs, Key‑Prefixing, TTL‑Handling und einfachen Metriken
|
||||
- JacksonCacheSerializer: serialisiert Werte und CacheEntry per Jackson
|
||||
|
||||
**Status: ✅ PRODUKTIONSBEREIT** - Vollständig getestet mit 39 Tests (94.7% Success Rate)
|
||||
## Öffentliche API (Auszug)
|
||||
DistributedCache
|
||||
- get(key, clazz)/set(key, value, ttl?)
|
||||
- delete(key), exists(key)
|
||||
- multiGet(keys, clazz), multiSet(map, ttl?)
|
||||
- multiDelete(keys)
|
||||
- synchronize(keys?), markDirty(key), getDirtyKeys(), clear()
|
||||
|
||||
## Architektur: Port-Adapter-Muster
|
||||
Idiomatic Kotlin Extensions
|
||||
- cache.get<T>(key)
|
||||
- cache.multiGet<T>(keys)
|
||||
|
||||
Das Modul folgt streng dem **Port-Adapter-Muster** (Hexagonale Architektur), um eine saubere Trennung zwischen der Caching-Schnittstelle (dem "Port") und der konkreten Implementierung (dem "Adapter") zu gewährleisten.
|
||||
CacheConfiguration (DefaultCacheConfiguration vorhanden)
|
||||
- defaultTtl?, localCacheMaxSize?, offlineModeEnabled, synchronizationInterval, offlineEntryMaxAge?, keyPrefix, compressionEnabled, compressionThreshold
|
||||
|
||||
### Module-Struktur
|
||||
Hinweis: Die Kompression wird aktuell durch den Serializer bereitgestellt; Schwellwerte/Flags sind für zukünftiges Tuning vorgesehen.
|
||||
|
||||
* **`:infrastructure:cache:cache-api`**: Definiert den abstrakten "Vertrag" für das Caching (`DistributedCache`-Interface), ohne sich um die zugrunde liegende Technologie zu kümmern. Die Fach-Services programmieren ausschließlich gegen dieses Interface.
|
||||
* **`:infrastructure:cache:redis-cache`**: Die konkrete Implementierung des Vertrags, die **Redis** als hochperformantes Caching-Backend verwendet. Kapselt die gesamte Redis-spezifische Logik.
|
||||
## Implementierungsdetails (RedisDistributedCache)
|
||||
- Lokaler Fallback: ConcurrentHashMap als lokaler Cache speichert CacheEntry inkl. expiresAt. Bei Redis‑Ausfall werden Schreibvorgänge lokal gehalten und als „dirty“ markiert.
|
||||
- Dirty‑Synchronisation: Sobald die Verbindung wieder ONLINE ist, werden geänderte Schlüssel zu Redis synchronisiert (synchronize()).
|
||||
- Key‑Prefixing: Alle externen Keys werden mittels keyPrefix gekapselt, um Mandanten/Services zu isolieren.
|
||||
- TTL/Expiration: TTL wird einheitlich über kotlin.time.Duration angegeben und für Redis in java.time.Duration konvertiert. Lokale Einträge enthalten expiresAt und werden periodisch bereinigt.
|
||||
- Batch‑Operationen: multiGet/multiSet/multiDelete nutzen Redis‑Batching/Pipelining, lokal wird konsistent gespiegelt.
|
||||
- Größenbegrenzung Local Cache (neu): Wenn localCacheMaxSize gesetzt ist, werden bei Überschreitung die am längsten nicht mehr modifizierten Einträge aus dem lokalen Cache entfernt (LRM – least recently modified). Dadurch bleibt der lokale Fallback speichereffizient.
|
||||
- Periodische Aufgaben (@Scheduled):
|
||||
- Verbindungsprüfung: fixedDelayString = "${redis.connection-check-interval:10000}"
|
||||
- Lokale Bereinigung: fixedDelayString = "${redis.local-cache-cleanup-interval:60000}"
|
||||
- Dirty‑Sync: fixedDelayString = "${redis.sync-interval:300000}"
|
||||
- Metriken‑Log: fixedDelayString = "${redis.metrics-log-interval:300000}"
|
||||
|
||||
## Schlüsselfunktionen
|
||||
Wichtige Robustheitsdetails
|
||||
- Alle Redis‑Operationen fangen RedisConnectionFailureException ab und schalten den ConnectionState auf DISCONNECTED. Beim nächsten erfolgreichen Zugriff wird CONNECTED gesetzt und eine Synchronisation der dirty keys ausgelöst.
|
||||
- multiSet setzt TTLs bei Bedarf per Pipeline nach (pExpire); einzelne set‑Operationen nutzen expire via Duration.
|
||||
|
||||
### Core Features
|
||||
* **Offline-Fähigkeit & Resilienz:** Das Modul verfügt über einen In-Memory-Cache, der bei einem Ausfall der Redis-Verbindung als Fallback dient. Schreib-Operationen werden lokal als "dirty" markiert und automatisch mit Redis synchronisiert, sobald die Verbindung wiederhergestellt ist.
|
||||
* **Idiomatische Kotlin-API:** Bietet neben der Standard-API auch ergonomische Erweiterungsfunktionen mit `reified`-Typen für eine saubere und typsichere Verwendung in Kotlin-Code (`cache.get<User>("key")`).
|
||||
* **Projekweite Konsistenz:** Verwendet `kotlin.time.Duration` und `kotlin.time.Instant` für eine einheitliche Handhabung von Zeit- und Dauer-Angaben im gesamten Projekt.
|
||||
* **Automatisierte Verbindungsüberwachung:** Überprüft periodisch den Zustand der Redis-Verbindung und informiert Listener über Statusänderungen (`CONNECTED`, `DISCONNECTED`).
|
||||
|
||||
### Enterprise Features
|
||||
* **Multi-Tenant-Fähigkeit:** Key-Prefixes ermöglichen vollständige Isolation zwischen verschiedenen Anwendungen
|
||||
* **Konfigurierbare Kompression:** Automatische Kompression für große Datenstrukturen (konfigurierbar ab 1KB)
|
||||
* **Performance-Optimierung:** 5.000+ gleichzeitige Operationen mit >95% Erfolgsrate
|
||||
* **Unicode-Vollunterstützung:** Internationale Deployment-fähig mit Emojis, Umlauten, Chinesisch, Arabisch
|
||||
* **10MB+ Objektgrößen:** Automatische Kompression und Übertragung sehr großer Objekte
|
||||
|
||||
### Enhanced Monitoring & Operations (NEW)
|
||||
* **Real-time Performance Metrics:** Automatisches Tracking aller Cache-Operationen mit Erfolgsraten
|
||||
* **Strukturierte Metrics-Logging:** Periodische Performance-Reports mit detaillierten Metriken
|
||||
* **Cache Warming Utilities:** Produktions-bereite Warming-Strategien für optimale Performance
|
||||
* **Health Status Monitoring:** Umfassende Gesundheitschecks mit automatischer Status-Bewertung
|
||||
* **Advanced Connection Tracking:** Erweiterte Verbindungsüberwachung mit detaillierten Zustandsinformationen
|
||||
|
||||
## Verwendung
|
||||
|
||||
Ein Microservice bindet `:infrastructure:cache:redis-cache` als Abhängigkeit ein und lässt sich das `DistributedCache`-Interface per Dependency Injection geben.
|
||||
|
||||
### Grundlegende Verwendung
|
||||
## Verwendung (Beispiele)
|
||||
Einbinden: Projekte hängen gegen :infrastructure:cache:redis-cache und injizieren DistributedCache.
|
||||
|
||||
Lesen/Schreiben mit TTL
|
||||
```kotlin
|
||||
@Service
|
||||
class MasterdataService(
|
||||
private val cache: DistributedCache // Nur das Interface wird verwendet!
|
||||
) {
|
||||
fun findCountryById(id: String): Country? {
|
||||
val cacheKey = "country:$id"
|
||||
|
||||
// 1. Versuche, aus dem Cache zu lesen (typsicher und sauber)
|
||||
val cachedCountry = cache.get<Country>(cacheKey)
|
||||
if (cachedCountry != null) {
|
||||
return cachedCountry
|
||||
}
|
||||
|
||||
// 2. Wenn nicht im Cache, aus der DB lesen
|
||||
val dbCountry = countryRepository.findById(id)
|
||||
|
||||
// 3. Ergebnis in den Cache schreiben für zukünftige Anfragen
|
||||
dbCountry?.let {
|
||||
cache.set(cacheKey, it, ttl = 1.hours) // Cache für 1 Stunde
|
||||
}
|
||||
return dbCountry
|
||||
}
|
||||
val user = cache.get<User>("user:42")
|
||||
if (user == null) {
|
||||
val loaded = userRepository.findById("42") ?: return null
|
||||
cache.set("user:42", loaded, ttl = 1.hours)
|
||||
}
|
||||
```
|
||||
|
||||
### Erweiterte Verwendung
|
||||
|
||||
Batch‑Lesezugriff
|
||||
```kotlin
|
||||
// Batch-Operationen für bessere Performance
|
||||
val userIds = listOf("user:1", "user:2", "user:3")
|
||||
val cachedUsers = cache.multiGet<User>(userIds)
|
||||
|
||||
// Bulk-Updates
|
||||
val newUsers = mapOf(
|
||||
"user:4" to User("Alice"),
|
||||
"user:5" to User("Bob")
|
||||
)
|
||||
cache.multiSet(newUsers, ttl = 30.minutes)
|
||||
|
||||
// Connection-State-Monitoring
|
||||
cache.registerConnectionListener(object : ConnectionStateListener {
|
||||
override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) {
|
||||
logger.info { "Cache connection state changed to: $newState" }
|
||||
}
|
||||
})
|
||||
|
||||
// Performance Monitoring (NEW)
|
||||
val metrics = cache.getPerformanceMetrics()
|
||||
logger.info { "Current performance: ${metrics["successRate"]} success rate, ${metrics["totalOperations"]} operations" }
|
||||
|
||||
// Health Status Checking (NEW)
|
||||
val health = cache.getHealthStatus()
|
||||
if (health["healthy"] as Boolean) {
|
||||
logger.info { "Cache is healthy with ${health["successRate"]} success rate" }
|
||||
} else {
|
||||
logger.warn { "Cache health issue detected: ${health["connectionState"]}" }
|
||||
}
|
||||
|
||||
// Cache Warming (NEW)
|
||||
// Individual key warming with data loader
|
||||
cache.warmCache(listOf("user:1", "user:2", "user:3")) { key ->
|
||||
userService.loadUser(key.substringAfter(":"))
|
||||
}
|
||||
|
||||
// Bulk cache warming
|
||||
val preloadData = mapOf(
|
||||
"config:app" to applicationConfig,
|
||||
"config:features" to featureFlags
|
||||
)
|
||||
cache.warmCacheBulk(preloadData, ttl = 1.hours)
|
||||
val ids = listOf("user:1", "user:2", "user:3")
|
||||
val map = cache.multiGet<User>(ids)
|
||||
```
|
||||
|
||||
## Test-Suite: Vollständige Produktionsabdeckung
|
||||
|
||||
### Test-Übersicht
|
||||
- ✅ **39 Tests total** (12 Basis + 27 erweiterte Tests)
|
||||
- ✅ **6 Test-Klassen** vollständig optimiert
|
||||
- ✅ **94.7% Success Rate** (36/38 erfolgreich)
|
||||
- ✅ **Professionelles SLF4J/kotlin-logging** durchgängig
|
||||
|
||||
### Test-Kategorien
|
||||
|
||||
| Kategorie | Tests | Zweck | Status |
|
||||
|-----------|-------|-------|---------|
|
||||
| **Basis-Funktionalität** | 12 | Core Cache Operations | ✅ Stabil |
|
||||
| **Performance & Load** | 3 | Gleichzeitige Zugriffe, Speicherdruck, Bulk-Ops | ✅ Optimiert |
|
||||
| **Edge Cases** | 6 | Serialisierung, große Daten, Unicode, null-Werte | ✅ Robust |
|
||||
| **Resilience** | 6 | Timeouts, Verbindungsausfälle, Wiederverbindung | ✅ Resilient |
|
||||
| **Configuration** | 6 | TTL, Kompression, Prefixes, Cache-Größen | ✅ Flexibel |
|
||||
| **Integration** | 6 | Cross-Instance, Monitoring, Produktions-Szenarien | ✅ Produktionsready |
|
||||
|
||||
### Detaillierte Test-Abdeckung
|
||||
|
||||
#### Performance & Load Tests
|
||||
- **`test cache performance with high concurrent access`**: 100 Coroutines mit je 50 Operationen (5.000 gleichzeitige Ops)
|
||||
- **`test cache behavior under memory pressure`**: 500 Einträge mit kleinem Local-Cache (100)
|
||||
- **`test bulk operations performance`**: 1000 Einträge mit multiSet/multiGet (1000+ Einträge/Sekunde)
|
||||
|
||||
#### Edge Cases & Error Handling
|
||||
- **`test serialization with problematic objects`**: Zirkuläre Referenzen, tiefe Verschachtelung (50 Ebenen)
|
||||
- **`test cache with extremely large values`**: 10MB Strings mit automatischer Kompression
|
||||
- **`test special characters and unicode`**: Emojis, Umlaute, Chinesisch, Arabisch, gemischte Inhalte
|
||||
- **`test cache with null and empty values`**: Leere Strings, null-Felder, leere Collections
|
||||
- **`test complex nested objects`**: Verschachtelte Maps mit Listen und Metadaten
|
||||
- **`test malformed data scenarios`**: Nicht-existierende Keys, gemischte Batch-Operationen
|
||||
|
||||
#### Resilience & Timeout Tests
|
||||
- **`test connection timeout scenarios`**: 5-Sekunden-Delays simuliert, max. 10s Timeout
|
||||
- **`test partial Redis failures`**: Intermittierende Ausfälle alle 3 Operationen
|
||||
- **`test network partitioning simulation`**: Komplette Netzwerktrennung mit Offline-Mode
|
||||
- **`test reconnection and synchronization`**: Automatische Wiederverbindung mit Dirty-Key-Sync
|
||||
- **`test connection state listener notifications`**: Listener-Management und State-Tracking
|
||||
- **`test Redis restart simulation`**: Neustart-Szenarien mit lokaler Pufferung
|
||||
|
||||
#### Configuration Tests
|
||||
- **`test different cache configurations`**: Performance-, Storage- und Minimal-Configs
|
||||
- **`test compression threshold behavior`**: 50-Byte-Schwelle konfigurierbar getestet
|
||||
- **`test key prefix functionality`**: Vollständige Isolation zwischen "app1", "app2", ""
|
||||
- **`test TTL configuration variations`**: null, 100ms, 30min TTLs flexibel konfigurierbar
|
||||
- **`test offline mode configuration`**: Ein/Ausschalten des Offline-Modus
|
||||
- **`test local cache size limits`**: 3 vs. unlimited vs. 1000 Einträge mit Redis-Fallback
|
||||
|
||||
#### Integration & Monitoring Tests
|
||||
- **`test connection state listener functionality`**: Professionelles Listener-Management
|
||||
- **`test different Redis configurations`**: Multi-Config-Isolation und Cross-Compatibility
|
||||
- **`test cache warming scenarios`**: Bulk- (1000 Einträge <100ms), graduelle und selektive Vorwärmung
|
||||
- **`test metrics and monitoring integration`**: State-Tracking, Dirty-Keys-Monitoring, Performance-Metriken
|
||||
- **`test cross-instance synchronization`**: Multi-Instance-Datenaustausch mit kleinen Delays
|
||||
- **`test production-like scenarios`**: User-Sessions (1000), Config-Caching, API-Responses (100)
|
||||
|
||||
### Produktionstauglichkeits-Validierung
|
||||
|
||||
#### ✅ **Performance-Benchmarks bestanden:**
|
||||
- **5.000+ gleichzeitige Operationen** mit >95% Erfolgsrate
|
||||
- **Sub-100ms Performance** für Standard-Operationen
|
||||
- **1000+ Einträge/Sekunde** bei Bulk-Operationen
|
||||
- **Cache-Warming: 1000 Einträge in <100ms** möglich
|
||||
|
||||
#### ✅ **Robustheit validiert:**
|
||||
- **Graceful Degradation** bei allen Fehlersituationen
|
||||
- **Automatische Wiederverbindung** mit Dirty-Key-Synchronisation
|
||||
- **Speicher-effiziente** Local-Cache-Verwaltung mit Redis-Fallback
|
||||
- **Cross-Instance-Synchronisation** zwischen Services funktionsfähig
|
||||
|
||||
#### ✅ **Enterprise-Features getestet:**
|
||||
- **10MB+ Objektgrößen** mit automatischer Kompression
|
||||
- **Unicode-Vollunterstützung** für internationale Deployments
|
||||
- **Multi-Tenant-Fähigkeit** durch Key-Prefixes mit perfekter Isolation
|
||||
- **Vollständige Offline-Fähigkeit** bei Redis-Ausfällen
|
||||
|
||||
## Logging-Architektur: Professionelle Standards
|
||||
|
||||
### Implementierte Standards
|
||||
Das gesamte Modul verwendet professionelle SLF4J/kotlin-logging Standards:
|
||||
|
||||
Bulk‑Schreiben
|
||||
```kotlin
|
||||
// Konsistentes Pattern in allen Klassen:
|
||||
companion.object {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
}
|
||||
|
||||
// Strukturierte Logging-Calls:
|
||||
logger.info { "Cache operation completed with metrics: $metrics" }
|
||||
logger.warn { "Connection state changed: $oldState -> $newState" }
|
||||
logger.debug { "Processing batch of $size entries with config: $config" }
|
||||
cache.multiSet(mapOf(
|
||||
"cfg:app" to appConfig,
|
||||
"cfg:features" to features
|
||||
), ttl = 30.minutes)
|
||||
```
|
||||
|
||||
### Log-Level-Richtlinien
|
||||
|
||||
| Level | Verwendung | Beispiel |
|
||||
|-------|------------|----------|
|
||||
| **INFO** | Cache-Operationen, State-Changes, Metriken | `logger.info { "Performance test completed: $metrics" }` |
|
||||
| **DEBUG** | Detaillierte Ablaufinformationen | `logger.debug { "Processing batch of $size entries" }` |
|
||||
| **WARN** | Verbindungsprobleme, Performance-Issues | `logger.warn { "Success rate below threshold: $rate" }` |
|
||||
| **ERROR** | Kritische Fehler, Serialisierungsprobleme | `logger.error { "Unexpected exception in cache operation" }` |
|
||||
|
||||
### Logback-Konfiguration
|
||||
```xml
|
||||
<!-- Strukturierte Console-Ausgaben -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Cache-spezifische Logger -->
|
||||
<logger name="at.mocode.infrastructure.cache" level="DEBUG" />
|
||||
<logger name="RedisDistributedCachePerformanceTest" level="INFO" />
|
||||
|
||||
<!-- Reduzierte Verbosity für externe Komponenten -->
|
||||
<logger name="org.testcontainers" level="WARN" />
|
||||
<logger name="io.lettuce" level="WARN" />
|
||||
```
|
||||
|
||||
## Dependency-Management: Single Source of Truth
|
||||
|
||||
### Vollständige SINGLE SOURCE OF TRUTH Konformität
|
||||
Alle Dependencies verwenden jetzt zentrale `libs.versions.toml` Verwaltung:
|
||||
|
||||
```toml
|
||||
# Zentrale Versionen
|
||||
[versions]
|
||||
logback = "1.5.13"
|
||||
kotlinLogging = "3.0.5"
|
||||
|
||||
[libraries]
|
||||
kotlin-logging-jvm = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlinLogging" }
|
||||
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||
logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
|
||||
|
||||
[bundles]
|
||||
redis-cache = ["spring-boot-starter-data-redis", "lettuce-core", "jackson-module-kotlin", "jackson-datatype-jsr310"]
|
||||
testing-jvm = ["junit-jupiter-api", "junit-jupiter-engine", "mockk", "assertj-core", "kotlinx-coroutines-test"]
|
||||
```
|
||||
|
||||
### Build-Konfiguration
|
||||
```kotlin
|
||||
// redis-cache/build.gradle.kts - VOLLSTÄNDIG OPTIMIERT
|
||||
dependencies {
|
||||
// Alle Dependencies über libs-Referenzen
|
||||
implementation(libs.bundles.redis.cache)
|
||||
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
testImplementation(libs.bundles.testing.jvm)
|
||||
testImplementation(libs.kotlin.test)
|
||||
testImplementation(libs.kotlin.logging.jvm)
|
||||
testImplementation(libs.logback.classic)
|
||||
testImplementation(libs.logback.core)
|
||||
}
|
||||
```
|
||||
|
||||
## Konfiguration & Deployment
|
||||
|
||||
### Cache-Konfigurationen für verschiedene Umgebungen
|
||||
|
||||
#### Performance-optimiert (High-Throughput)
|
||||
```kotlin
|
||||
val performanceConfig = DefaultCacheConfiguration(
|
||||
keyPrefix = "perf",
|
||||
defaultTtl = 5.minutes,
|
||||
localCacheMaxSize = 50000,
|
||||
compressionEnabled = false, // Für maximale Geschwindigkeit
|
||||
compressionThreshold = Int.MAX_VALUE
|
||||
)
|
||||
```
|
||||
|
||||
#### Storage-optimiert (Kompression)
|
||||
```kotlin
|
||||
val storageConfig = DefaultCacheConfiguration(
|
||||
keyPrefix = "storage",
|
||||
defaultTtl = 7.days,
|
||||
localCacheMaxSize = 1000,
|
||||
compressionEnabled = true,
|
||||
compressionThreshold = 100 // Kompression ab 100 Bytes
|
||||
)
|
||||
```
|
||||
|
||||
#### Minimal (Entwicklung)
|
||||
```kotlin
|
||||
val minimalConfig = DefaultCacheConfiguration(
|
||||
keyPrefix = "dev",
|
||||
defaultTtl = null, // Kein TTL
|
||||
localCacheMaxSize = null, // Unbegrenzt
|
||||
offlineModeEnabled = false // Für Entwicklung optional
|
||||
)
|
||||
```
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Connection-State-Monitoring
|
||||
Verbindungsstatus überwachen
|
||||
```kotlin
|
||||
cache.registerConnectionListener(object : ConnectionStateListener {
|
||||
override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) {
|
||||
when (newState) {
|
||||
ConnectionState.CONNECTED -> {
|
||||
logger.info { "Cache reconnected at $timestamp" }
|
||||
metricsCollector.increment("cache.reconnects")
|
||||
}
|
||||
ConnectionState.DISCONNECTED -> {
|
||||
logger.warn { "Cache disconnected at $timestamp" }
|
||||
alerting.sendAlert("Cache offline", "Redis connection lost")
|
||||
}
|
||||
ConnectionState.RECONNECTING -> {
|
||||
logger.info { "Cache attempting reconnection at $timestamp" }
|
||||
}
|
||||
}
|
||||
logger.info("Cache connection state: $newState at $timestamp")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Performance-Metriken
|
||||
```kotlin
|
||||
// Beispiel für strukturierte Metriken-Sammlung
|
||||
val metrics = mapOf(
|
||||
"totalOperations" to totalOperations,
|
||||
"successRate" to successRate,
|
||||
"averageLatency" to averageLatency,
|
||||
"operationsPerSecond" to opsPerSec,
|
||||
"dirtyKeysCount" to cache.getDirtyKeys().size,
|
||||
"connectionState" to cache.getConnectionState()
|
||||
)
|
||||
logger.info { "Cache performance metrics: $metrics" }
|
||||
```
|
||||
## Konfiguration
|
||||
DefaultCacheConfiguration bietet sinnvolle Defaults. Relevante Properties (optional via Spring @Scheduled Platzhalter):
|
||||
- redis.connection-check-interval: ms für Verbindungsprüfung (Default 10000)
|
||||
- redis.local-cache-cleanup-interval: ms für lokale Bereinigung (Default 60000)
|
||||
- redis.sync-interval: ms für Synchronisationsläufe (Default 300000)
|
||||
- redis.metrics-log-interval: ms für periodisches Metriken‑Logging (Default 300000)
|
||||
|
||||
### CI/CD Integration
|
||||
```yaml
|
||||
# Beispiel für GitHub Actions
|
||||
- name: Run Cache Tests with Structured Logging
|
||||
run: |
|
||||
./gradlew :infrastructure:cache:redis-cache:test --info
|
||||
Hinweise
|
||||
- keyPrefix sollte pro Service gesetzt werden (z. B. "masterdata"), um Kollisionen zu vermeiden.
|
||||
- localCacheMaxSize begrenzt die Größe des lokalen Fallback‑Caches. Bei null ist die Größe unbegrenzt.
|
||||
|
||||
# Log-Level für verschiedene Umgebungen:
|
||||
# Development: DEBUG (alle Details)
|
||||
# CI/CD: INFO (wichtige Ereignisse)
|
||||
# Production: WARN (nur Probleme)
|
||||
```
|
||||
## Betrieb & Monitoring
|
||||
- Health‑Infos: getHealthStatus() liefert eine einfache Einschätzung basierend auf ConnectionState und Erfolgsrate der Operationen.
|
||||
- Metriken: getPerformanceMetrics() liefert einfache Kennzahlen (Operations, Success‑Rate, Größe lokaler Cache, Anzahl dirty Keys). Periodisches Logging per @Scheduled möglich.
|
||||
- Cache Warming: warmCache(keys, loader) und warmCacheBulk(map) helfen, Hot‑Keys/gefragte Konfigurationen beim Start vorzuwärmen.
|
||||
|
||||
## Best Practices & Empfehlungen
|
||||
## Grenzen & bekannte Punkte
|
||||
- Kompression ist im Serializer implementiert; die konfigurierbaren Flags/Schwellenwerte sind derzeit nicht dynamisch an/aus‑geschaltet.
|
||||
- Offline‑Modus: Die Konfiguration offlineModeEnabled ist vorhanden; die Implementierung betreibt den lokalen Fallback standardmäßig bei Verbindungsproblemen. Eine harte Deaktivierung dieses Verhaltens ist aktuell nicht verdrahtet.
|
||||
|
||||
### Produktionseinsatz-Empfehlungen
|
||||
## Changelog (Kurz)
|
||||
- 2025‑09‑03: Fehlerbehebungen für @Scheduled‑Platzhalter, korrektes Logging im Cache‑Warming, lokale Cache‑Größenbegrenzung (LRM‑Eviction) hinzugefügt. Dokumentation aktualisiert (diese Datei).
|
||||
|
||||
#### **Priorität HOCH (sofort umsetzbar):**
|
||||
1. **Performance-Monitoring:** Strukturierte Logs für Produktions-Metriken nutzen
|
||||
2. **Connection-State-Überwachung:** Listener für Alerting bei Redis-Ausfällen einrichten
|
||||
3. **Cache-Warming:** Graduelle Warming-Strategien beim Service-Start implementieren
|
||||
|
||||
#### **Priorität MITTEL (mittelfristig):**
|
||||
1. **Kompression-Tuning:** Threshold je nach Datenanforderungen anpassen (Standard: 1KB)
|
||||
2. **Local-Cache-Größen:** Je nach verfügbarem RAM pro Service optimieren
|
||||
3. **TTL-Strategien:** Spezifische TTLs für verschiedene Datentypen definieren
|
||||
|
||||
#### **Priorität NIEDRIG (langfristig):**
|
||||
1. **Advanced Monitoring:** Integration mit Micrometer/Prometheus für detaillierte Metriken
|
||||
2. **Multi-Redis-Cluster:** Unterstützung für Redis-Cluster-Konfigurationen
|
||||
3. **Erweiterte Kompression:** Alternative Algorithmen (LZ4, Snappy) evaluieren
|
||||
|
||||
### Entwickler-Guidelines
|
||||
|
||||
#### **DO's ✅**
|
||||
- Verwende `cache.get<Type>(key)` für typsichere Operationen
|
||||
- Implementiere Connection-State-Listener für kritische Services
|
||||
- Nutze Batch-Operationen (`multiGet`, `multiSet`) für bessere Performance
|
||||
- Verwende aussagekräftige Key-Prefixes für Multi-Tenant-Szenarien
|
||||
- Teste Cache-Warming-Strategien in Integration-Tests
|
||||
|
||||
#### **DON'Ts ❌**
|
||||
- Niemals sensible Daten ohne Verschlüsselung cachen
|
||||
- Vermeide sehr große TTLs ohne Begründung (>24h)
|
||||
- Keine Hard-coded Cache-Keys - verwende Key-Factories
|
||||
- Vermeide Blocking-Operations in Connection-State-Listeners
|
||||
- Keine println() in Cache-bezogenem Code - verwende Logger
|
||||
|
||||
### Typische Anwendungsszenarien
|
||||
|
||||
#### User-Session-Caching
|
||||
```kotlin
|
||||
// TTL = Session-Timeout
|
||||
cache.set("user:session:${sessionId}", userSession, ttl = 30.minutes)
|
||||
```
|
||||
|
||||
#### API-Response-Caching
|
||||
```kotlin
|
||||
// Kurze TTL für häufig ändernde Daten
|
||||
cache.set("api:response:${endpoint}", response, ttl = 5.minutes)
|
||||
```
|
||||
|
||||
#### Configuration-Caching
|
||||
```kotlin
|
||||
// Lange TTL für stabile Konfiguration
|
||||
cache.set("config:${service}", config, ttl = 1.hours)
|
||||
```
|
||||
|
||||
#### Database-Result-Caching
|
||||
```kotlin
|
||||
// Mittlere TTL für Datenbankabfragen
|
||||
cache.set("db:${query.hash()}", results, ttl = 15.minutes)
|
||||
```
|
||||
|
||||
## Migration & Upgrade-Pfad
|
||||
|
||||
### Von Version < 1.0
|
||||
1. **Dependencies aktualisieren:** Umstellung auf libs.versions.toml
|
||||
2. **Logging modernisieren:** println() → SLF4J/kotlin-logging
|
||||
3. **Test-Suite erweitern:** Neue Test-Kategorien hinzufügen
|
||||
4. **Konfiguration migrieren:** Neue DefaultCacheConfiguration verwenden
|
||||
|
||||
### Backwards Compatibility
|
||||
- ✅ Alle bestehenden API-Calls funktionieren weiterhin
|
||||
- ✅ Bestehende Konfigurationen sind kompatibel
|
||||
- ✅ Migration kann schrittweise erfolgen
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2025-08-15 - Enhanced Monitoring & Operations Update v2.1
|
||||
- ✅ **Real-time Performance Metrics:** Automatisches Tracking aller Cache-Operationen mit detaillierter Erfolgsraten-Überwachung
|
||||
- ✅ **Strukturierte Metrics-Logging:** Periodische Performance-Reports alle 5 Minuten mit umfassenden Metriken
|
||||
- ✅ **Cache Warming Utilities:** Produktions-bereite Warming-Strategien mit Individual- und Bulk-Operations
|
||||
- ✅ **Health Status Monitoring:** Umfassende Gesundheitschecks mit automatischer Status-Bewertung (>90% Erfolgsrate)
|
||||
- ✅ **Enhanced Connection Tracking:** Erweiterte Verbindungsüberwachung mit detaillierten Zustandsinformationen
|
||||
- ✅ **Production-Ready Monitoring:** Integration hooks für Enterprise-Monitoring-Systeme
|
||||
- ✅ **Performance Optimization:** Verbesserte Metriken-Sammlung ohne Performance-Impact
|
||||
|
||||
### 2025-08-14 - Major Update v2.0
|
||||
- ✅ **Vollständige Test-Suite-Erweiterung:** Von 12 auf 39 Tests (94.7% Success Rate)
|
||||
- ✅ **Professionelle Logging-Architektur:** Komplette Umstellung auf SLF4J/kotlin-logging
|
||||
- ✅ **SINGLE SOURCE OF TRUTH:** Alle Dependencies über libs.versions.toml
|
||||
- ✅ **Edge-Cases-Korrekturen:** Serialisierungstests von 71.4% auf 100% Success Rate
|
||||
- ✅ **Enterprise-Features validiert:** 5.000+ concurrent operations, 10MB+ objects
|
||||
- ✅ **Produktionstauglichkeit erreicht:** Vollständige Performance-, Resilience- und Integration-Tests
|
||||
- ✅ **Erweiterte Konfigurierbarkeit:** Performance-, Storage- und Development-Presets
|
||||
- ✅ **Advanced Monitoring:** Connection-State-Listener und strukturierte Metriken
|
||||
|
||||
### 2025-08-14 - Previous
|
||||
- **Bug Fix:** Compiler-Warnungen in `JacksonCacheSerializer` bezüglich identity-sensitiver Operationen behoben
|
||||
- **Verbesserung:** Objects.equals() für sichere nullable Instant-Vergleiche
|
||||
|
||||
## Testing-Strategie: Zweistufig & Umfassend
|
||||
|
||||
### Integrationstests mit Testcontainers
|
||||
Die Kernfunktionalität wird gegen eine echte Redis-Datenbank getestet, die zur Laufzeit in einem Docker-Container gestartet wird. Dies garantiert 100%ige Kompatibilität und realistische Performance-Messungen.
|
||||
|
||||
### Unit-Tests mit MockK
|
||||
Die komplexe Logik der Offline-Fähigkeit und Synchronisation wird durch das Mocking des RedisTemplate getestet. So können Verbindungsausfälle, Timeouts und Netzwerkpartitionierung zuverlässig simuliert werden.
|
||||
|
||||
### End-to-End Produktionstests
|
||||
Production-like Scenarios testen realistische Anwendungsfälle:
|
||||
- User-Session-Management (1000 Sessions)
|
||||
- Configuration-Caching mit verschiedenen TTLs
|
||||
- API-Response-Caching (100 Endpoints)
|
||||
- Cross-Service-Kommunikation
|
||||
|
||||
## Fazit & Status
|
||||
|
||||
Das **Infrastructure/Cache-Modul** ist **vollständig produktionsbereit** und erfüllt alle Enterprise-Anforderungen:
|
||||
|
||||
- ✅ **94.7% Test Success Rate** mit 39 umfassenden Tests
|
||||
- ✅ **Professionelle Logging-Architektur** durchgängig etabliert
|
||||
- ✅ **Enterprise-Performance** validiert (5.000+ concurrent ops)
|
||||
- ✅ **Vollständige Resilience** bei Netzwerk- und Redis-Ausfällen
|
||||
- ✅ **SINGLE SOURCE OF TRUTH** für alle Dependencies
|
||||
- ✅ **Internationale Deployment-Fähigkeit** mit Unicode-Support
|
||||
- ✅ **Advanced Monitoring** mit Connection-State-Tracking
|
||||
- ✅ **Multi-Tenant-Capable** durch Key-Prefix-Isolation
|
||||
|
||||
**Empfehlung: ✅ BEREIT FÜR PRODUKTIONSEINSATZ**
|
||||
|
||||
Das Modul kann sofort in produktiven Umgebungen eingesetzt werden. Die umfassende Test-Suite und professionelle Architektur gewährleisten höchste Zuverlässigkeit und Performance.
|
||||
## Fazit
|
||||
Das Cache‑Modul bietet eine klare, wiederverwendbare Cache‑Schnittstelle mit einer robusten Redis‑Implementierung. Es unterstützt TTLs, Batch‑Operationen, lokalen Fallback bei Ausfällen und liefert einfache, praxistaugliche Betriebsinformationen. Mit keyPrefix und lokalen Limits ist der Einsatz in Multi‑Service‑Umgebungen unkompliziert und stabil.
|
||||
|
||||
+13
-3
@@ -5,6 +5,12 @@ plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
// Erlaubt die Verwendung der kotlin.time API im gesamten Modul
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
@@ -12,11 +18,15 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
|
||||
implementation(platform(projects.platform.platformBom))
|
||||
// Stellt gemeinsame Abhängigkeiten wie Logging bereit.
|
||||
implementation(projects.platform.platformDependencies)
|
||||
api(platform(projects.platform.platformBom))
|
||||
// Stellt gemeinsame Abhängigkeiten wie Logging bereit und exportiert sie für Konsumenten der API.
|
||||
api(projects.platform.platformDependencies)
|
||||
|
||||
// Stellt Test-Abhängigkeiten bereit.
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
|
||||
infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheSerializer.kt
Vendored
+21
-21
@@ -1,56 +1,56 @@
|
||||
package at.mocode.infrastructure.cache.api
|
||||
|
||||
/**
|
||||
* Interface for serializing and deserializing cache entries.
|
||||
* Schnittstelle zum Serialisieren und Deserialisieren von Cache-Einträgen.
|
||||
*/
|
||||
interface CacheSerializer {
|
||||
/**
|
||||
* Serializes a value to a byte array.
|
||||
* Serialisiert einen Wert zu einem Byte-Array.
|
||||
*
|
||||
* @param value The value to serialize
|
||||
* @return The serialized value as a byte array
|
||||
* @param value Der zu serialisierende Wert
|
||||
* @return Der serialisierte Wert als Byte-Array
|
||||
*/
|
||||
fun <T : Any> serialize(value: T): ByteArray
|
||||
|
||||
/**
|
||||
* Deserializes a byte array to a value.
|
||||
* Deserialisiert ein Byte-Array zu einem Wert.
|
||||
*
|
||||
* @param bytes The byte array to deserialize
|
||||
* @param clazz The class of the value to deserialize to
|
||||
* @return The deserialized value
|
||||
* @param bytes Das zu deserialisierende Byte-Array
|
||||
* @param clazz Die Zielklasse des zu deserialisierenden Werts
|
||||
* @return Der deserialisierte Wert
|
||||
*/
|
||||
fun <T : Any> deserialize(bytes: ByteArray, clazz: Class<T>): T
|
||||
|
||||
/**
|
||||
* Serializes a cache entry to a byte array.
|
||||
* Serialisiert einen Cache-Eintrag zu einem Byte-Array.
|
||||
*
|
||||
* @param entry The cache entry to serialize
|
||||
* @return The serialized cache entry as a byte array
|
||||
* @param entry Der zu serialisierende Cache-Eintrag
|
||||
* @return Der serialisierte Cache-Eintrag als Byte-Array
|
||||
*/
|
||||
fun <T : Any> serializeEntry(entry: CacheEntry<T>): ByteArray
|
||||
|
||||
/**
|
||||
* Deserializes a byte array to a cache entry.
|
||||
* Deserialisiert ein Byte-Array zu einem Cache-Eintrag.
|
||||
*
|
||||
* @param bytes The byte array to deserialize
|
||||
* @param valueClass The class of the value in the cache entry
|
||||
* @return The deserialized cache entry
|
||||
* @param bytes Das zu deserialisierende Byte-Array
|
||||
* @param valueClass Die Klasse des Werts im Cache-Eintrag
|
||||
* @return Der deserialisierte Cache-Eintrag
|
||||
*/
|
||||
fun <T : Any> deserializeEntry(bytes: ByteArray, valueClass: Class<T>): CacheEntry<T>
|
||||
|
||||
/**
|
||||
* Compresses a byte array.
|
||||
* Komprimiert ein Byte-Array.
|
||||
*
|
||||
* @param bytes The byte array to compress
|
||||
* @return The compressed byte array
|
||||
* @param bytes Das zu komprimierende Byte-Array
|
||||
* @return Das komprimierte Byte-Array
|
||||
*/
|
||||
fun compress(bytes: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* Decompresses a byte array.
|
||||
* Dekomprimiert ein Byte-Array.
|
||||
*
|
||||
* @param bytes The byte array to decompress
|
||||
* @return The decompressed byte array
|
||||
* @param bytes Das zu dekomprimierende Byte-Array
|
||||
* @return Das dekomprimierte Byte-Array
|
||||
*/
|
||||
fun decompress(bytes: ByteArray): ByteArray
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,20 +1,20 @@
|
||||
package at.mocode.infrastructure.cache.api
|
||||
|
||||
/**
|
||||
* Kotlin-idiomatic extension function to retrieve a value from the cache
|
||||
* using reified types.
|
||||
* Kotlin-idiomatische Extension-Funktion, um einen Wert aus dem Cache zu lesen
|
||||
* – mit reified Typen.
|
||||
*
|
||||
* Example: `val user = cache.get<User>("user:123")`
|
||||
* Beispiel: `val user = cache.get<User>("user:123")`
|
||||
*/
|
||||
inline fun <reified T : Any> DistributedCache.get(key: String): T? {
|
||||
return this.get(key, T::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin-idiomatic extension function to retrieve multiple values from the cache
|
||||
* using reified types.
|
||||
* Kotlin-idiomatische Extension-Funktion, um mehrere Werte aus dem Cache zu lesen
|
||||
* – mit reified Typen.
|
||||
*
|
||||
* Example: `val users = cache.multiGet<User>(listOf("user:123", "user:124"))`
|
||||
* Beispiel: `val users = cache.multiGet<User>(listOf("user:123", "user:124"))`
|
||||
*/
|
||||
inline fun <reified T : Any> DistributedCache.multiGet(keys: Collection<String>): Map<String, T> {
|
||||
return this.multiGet(keys, T::class.java)
|
||||
|
||||
+15
-4
@@ -3,13 +3,24 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
alias(libs.plugins.spring.boot)
|
||||
// Als Bibliothek benötigt dieses Modul das Spring Boot Plugin nicht.
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
}
|
||||
|
||||
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
|
||||
tasks.getByName("bootJar") {
|
||||
enabled = false
|
||||
// Stellt sicher, dass ein normales JAR gebaut wird (Bibliotheks-Modul).
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
+34
-14
@@ -82,6 +82,7 @@ class RedisDistributedCache(
|
||||
// Store in a local cache
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[prefixedKey] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
|
||||
trackOperation(true)
|
||||
return entry.value
|
||||
@@ -109,6 +110,7 @@ class RedisDistributedCache(
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[prefixedKey] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
|
||||
if (!isConnected()) {
|
||||
markDirty(key)
|
||||
@@ -231,6 +233,7 @@ class RedisDistributedCache(
|
||||
// Store in a local cache
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[key] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
|
||||
// Add to result
|
||||
result[removePrefix(key)] = entry.value
|
||||
@@ -263,6 +266,7 @@ class RedisDistributedCache(
|
||||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[prefixedKey] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
redisBatch[prefixedKey] = serializer.serializeEntry(entry)
|
||||
}
|
||||
|
||||
@@ -437,6 +441,22 @@ class RedisDistributedCache(
|
||||
return if (config.keyPrefix.isEmpty()) key else key.substring(config.keyPrefix.length + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzwingt die maximale Größe des lokalen Caches, indem die am längsten nicht
|
||||
* mehr modifizierten Einträge entfernt werden.
|
||||
*/
|
||||
private fun enforceLocalCacheSize() {
|
||||
val max = config.localCacheMaxSize ?: return
|
||||
val overflow = localCache.size - max
|
||||
if (overflow <= 0) return
|
||||
val toEvict = localCache.entries
|
||||
.sortedBy { it.value.lastModifiedAt }
|
||||
.take(overflow)
|
||||
.map { it.key }
|
||||
toEvict.forEach { localCache.remove(it) }
|
||||
logger.debug("Evicted ${toEvict.size} entries to enforce local cache size limit $max")
|
||||
}
|
||||
|
||||
private fun handleConnectionFailure(e: Exception) {
|
||||
logger.warn("Redis connection failure: ${e.message}")
|
||||
setConnectionState(ConnectionState.DISCONNECTED)
|
||||
@@ -468,9 +488,9 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically check the connection to Redis.
|
||||
* Prüft periodisch die Verbindung zu Redis.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.connection-check-interval:10000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.connection-check-interval:10000}")
|
||||
fun checkConnection() {
|
||||
try {
|
||||
redisTemplate.hasKey("connection-test")
|
||||
@@ -481,9 +501,9 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically clean up expired entries from the local cache.
|
||||
* Bereinigt periodisch abgelaufene Einträge aus dem lokalen Cache.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.local-cache-cleanup-interval:60000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.local-cache-cleanup-interval:60000}")
|
||||
fun cleanupLocalCache() {
|
||||
val now = Clock.System.now()
|
||||
val expiredKeys = localCache.entries
|
||||
@@ -498,9 +518,9 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically synchronize dirty keys when connected.
|
||||
* Synchronisiert periodisch schmutzige Schlüssel, sobald verbunden.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.sync-interval:300000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.sync-interval:300000}")
|
||||
fun scheduledSync() {
|
||||
if (isConnected() && dirtyKeys.isNotEmpty()) {
|
||||
synchronize(null)
|
||||
@@ -512,7 +532,7 @@ class RedisDistributedCache(
|
||||
//
|
||||
|
||||
/**
|
||||
* Track a cache operation for metrics
|
||||
* Zeichnet eine Cache-Operation für Metriken auf.
|
||||
*/
|
||||
private fun trackOperation(success: Boolean) {
|
||||
synchronized(this) {
|
||||
@@ -522,7 +542,7 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current performance metrics
|
||||
* Liefert aktuelle Performance-Metriken.
|
||||
*/
|
||||
fun getPerformanceMetrics(): Map<String, Any> {
|
||||
val now = Clock.System.now()
|
||||
@@ -543,9 +563,9 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Log performance metrics (called periodically)
|
||||
* Loggt Performance-Metriken (periodisch aufgerufen).
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.metrics-log-interval:300000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.metrics-log-interval:300000}")
|
||||
fun logPerformanceMetrics() {
|
||||
val metrics = getPerformanceMetrics()
|
||||
logger.info("Cache performance metrics: $metrics")
|
||||
@@ -553,7 +573,7 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache warming utility - preloads specified keys
|
||||
* Cache-Warming-Helfer – lädt angegebene Schlüssel vor.
|
||||
*/
|
||||
fun warmCache(keys: Collection<String>, dataLoader: (String) -> Any?) {
|
||||
logger.info("Starting cache warming for ${keys.size} keys")
|
||||
@@ -571,11 +591,11 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
val duration = Clock.System.now() - startTime
|
||||
logger.info("Cache warming completed: $warmedCount/$${keys.size} keys loaded in $duration")
|
||||
logger.info("Cache warming completed: $warmedCount/${keys.size} keys loaded in $duration")
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk cache warming with batch operations
|
||||
* Bulk-Cache-Warming mit Batch-Operationen.
|
||||
*/
|
||||
fun warmCacheBulk(keyDataMap: Map<String, Any>, ttl: Duration? = null) {
|
||||
logger.info("Starting bulk cache warming for ${keyDataMap.size} entries")
|
||||
@@ -588,7 +608,7 @@ class RedisDistributedCache(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache health status
|
||||
* Liefert den Cache-Gesundheitsstatus.
|
||||
*/
|
||||
fun getHealthStatus(): Map<String, Any> {
|
||||
val metrics = getPerformanceMetrics()
|
||||
|
||||
Reference in New Issue
Block a user