fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,399 @@
/**
* Cache Manager Module
*
* Provides intelligent caching for API responses and computed values.
* Features:
* - Memory cache
* - IndexedDB cache
* - Cache invalidation strategies
* - Cache warming
* - Cache analytics
* - Integration with RequestDeduplicator
*/
import { Logger } from '../../core/logger.js';
/**
* Cache strategies
*/
export const CacheStrategy = {
NO_CACHE: 'no-cache',
CACHE_FIRST: 'cache-first',
NETWORK_FIRST: 'network-first',
STALE_WHILE_REVALIDATE: 'stale-while-revalidate',
NETWORK_ONLY: 'network-only',
CACHE_ONLY: 'cache-only'
};
/**
* CacheManager - Intelligent caching system
*/
export class CacheManager {
constructor(config = {}) {
this.config = {
defaultStrategy: config.defaultStrategy || CacheStrategy.STALE_WHILE_REVALIDATE,
defaultTTL: config.defaultTTL || 3600000, // 1 hour
maxMemorySize: config.maxMemorySize || 50, // Max 50 items in memory
enableIndexedDB: config.enableIndexedDB ?? true,
indexedDBName: config.indexedDBName || 'app-cache',
indexedDBVersion: config.indexedDBVersion || 1,
enableAnalytics: config.enableAnalytics ?? false,
...config
};
this.memoryCache = new Map(); // Map<key, CacheEntry>
this.indexedDBCache = null;
this.analytics = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0
};
// Initialize
this.init();
}
/**
* Create a new CacheManager instance
*/
static create(config = {}) {
return new CacheManager(config);
}
/**
* Initialize cache manager
*/
async init() {
// Initialize IndexedDB if enabled
if (this.config.enableIndexedDB && 'indexedDB' in window) {
try {
await this.initIndexedDB();
} catch (error) {
Logger.error('[CacheManager] Failed to initialize IndexedDB', error);
this.config.enableIndexedDB = false;
}
}
Logger.info('[CacheManager] Initialized', {
strategy: this.config.defaultStrategy,
memoryCache: true,
indexedDB: this.config.enableIndexedDB
});
}
/**
* Initialize IndexedDB
*/
async initIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.config.indexedDBName, this.config.indexedDBVersion);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.indexedDBCache = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('cache')) {
db.createObjectStore('cache', { keyPath: 'key' });
}
};
});
}
/**
* Get value from cache
*/
async get(key, options = {}) {
const strategy = options.strategy || this.config.defaultStrategy;
const ttl = options.ttl || this.config.defaultTTL;
// Check memory cache first
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry && !this.isExpired(memoryEntry, ttl)) {
this.analytics.hits++;
Logger.debug('[CacheManager] Cache hit (memory)', key);
return memoryEntry.value;
}
// Check IndexedDB if enabled
if (this.config.enableIndexedDB && this.indexedDBCache) {
try {
const indexedDBEntry = await this.getFromIndexedDB(key);
if (indexedDBEntry && !this.isExpired(indexedDBEntry, ttl)) {
// Promote to memory cache
this.memoryCache.set(key, indexedDBEntry);
this.analytics.hits++;
Logger.debug('[CacheManager] Cache hit (IndexedDB)', key);
return indexedDBEntry.value;
}
} catch (error) {
Logger.error('[CacheManager] IndexedDB get error', error);
}
}
this.analytics.misses++;
Logger.debug('[CacheManager] Cache miss', key);
return null;
}
/**
* Set value in cache
*/
async set(key, value, options = {}) {
const ttl = options.ttl || this.config.defaultTTL;
const strategy = options.strategy || this.config.defaultStrategy;
const entry = {
key,
value,
timestamp: Date.now(),
ttl,
strategy
};
// Store in memory cache
this.memoryCache.set(key, entry);
// Limit memory cache size
if (this.memoryCache.size > this.config.maxMemorySize) {
const firstKey = this.memoryCache.keys().next().value;
this.memoryCache.delete(firstKey);
}
// Store in IndexedDB if enabled
if (this.config.enableIndexedDB && this.indexedDBCache) {
try {
await this.setInIndexedDB(entry);
} catch (error) {
Logger.error('[CacheManager] IndexedDB set error', error);
}
}
this.analytics.sets++;
Logger.debug('[CacheManager] Cache set', key);
}
/**
* Get or compute value (cache-aside pattern)
*/
async getOrSet(key, computeFn, options = {}) {
const strategy = options.strategy || this.config.defaultStrategy;
// Try cache first (unless network-only)
if (strategy !== CacheStrategy.NETWORK_ONLY) {
const cached = await this.get(key, options);
if (cached !== null) {
return cached;
}
}
// Compute value
const value = await computeFn();
// Store in cache (unless no-cache)
if (strategy !== CacheStrategy.NO_CACHE) {
await this.set(key, value, options);
}
return value;
}
/**
* Delete value from cache
*/
async delete(key) {
// Delete from memory
this.memoryCache.delete(key);
// Delete from IndexedDB
if (this.config.enableIndexedDB && this.indexedDBCache) {
try {
await this.deleteFromIndexedDB(key);
} catch (error) {
Logger.error('[CacheManager] IndexedDB delete error', error);
}
}
this.analytics.deletes++;
Logger.debug('[CacheManager] Cache delete', key);
}
/**
* Clear all cache
*/
async clear() {
this.memoryCache.clear();
if (this.config.enableIndexedDB && this.indexedDBCache) {
try {
await this.clearIndexedDB();
} catch (error) {
Logger.error('[CacheManager] IndexedDB clear error', error);
}
}
Logger.info('[CacheManager] Cache cleared');
}
/**
* Invalidate cache by pattern
*/
async invalidate(pattern) {
const keysToDelete = [];
// Find matching keys in memory
for (const key of this.memoryCache.keys()) {
if (this.matchesPattern(key, pattern)) {
keysToDelete.push(key);
}
}
// Delete matching keys
for (const key of keysToDelete) {
await this.delete(key);
}
Logger.info('[CacheManager] Invalidated cache', { pattern, count: keysToDelete.length });
}
/**
* Check if key matches pattern
*/
matchesPattern(key, pattern) {
if (typeof pattern === 'string') {
return key.includes(pattern);
}
if (pattern instanceof RegExp) {
return pattern.test(key);
}
if (typeof pattern === 'function') {
return pattern(key);
}
return false;
}
/**
* Check if cache entry is expired
*/
isExpired(entry, ttl) {
if (!entry || !entry.timestamp) {
return true;
}
const age = Date.now() - entry.timestamp;
return age > (entry.ttl || ttl);
}
/**
* Get from IndexedDB
*/
async getFromIndexedDB(key) {
return new Promise((resolve, reject) => {
const transaction = this.indexedDBCache.transaction(['cache'], 'readonly');
const store = transaction.objectStore('cache');
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
/**
* Set in IndexedDB
*/
async setInIndexedDB(entry) {
return new Promise((resolve, reject) => {
const transaction = this.indexedDBCache.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const request = store.put(entry);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
/**
* Delete from IndexedDB
*/
async deleteFromIndexedDB(key) {
return new Promise((resolve, reject) => {
const transaction = this.indexedDBCache.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
/**
* Clear IndexedDB
*/
async clearIndexedDB() {
return new Promise((resolve, reject) => {
const transaction = this.indexedDBCache.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
/**
* Warm cache (preload values)
*/
async warm(keys, computeFn) {
Logger.info('[CacheManager] Warming cache', { count: keys.length });
for (const key of keys) {
try {
await this.getOrSet(key, () => computeFn(key));
} catch (error) {
Logger.error(`[CacheManager] Failed to warm cache for ${key}`, error);
}
}
}
/**
* Get cache analytics
*/
getAnalytics() {
const total = this.analytics.hits + this.analytics.misses;
const hitRate = total > 0 ? (this.analytics.hits / total) * 100 : 0;
return {
...this.analytics,
total,
hitRate: Math.round(hitRate * 100) / 100,
memorySize: this.memoryCache.size,
memoryMaxSize: this.config.maxMemorySize
};
}
/**
* Reset analytics
*/
resetAnalytics() {
this.analytics = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0
};
}
/**
* Destroy cache manager
*/
destroy() {
this.memoryCache.clear();
this.indexedDBCache = null;
Logger.info('[CacheManager] Destroyed');
}
}