21 Performance-Optimierung

Performance-Optimierung ist ein kritischer Aspekt bei der Entwicklung skalierbarer NestJS-Anwendungen. Dieses Kapitel behandelt systematische Ansätze zur Identifikation von Bottlenecks, Optimierung von Datenbankzugriffen, effizientes Memory Management und verschiedene Strategien zur Verbesserung der Gesamtperformance von NestJS-Anwendungen.

21.1 Profiling von NestJS-Anwendungen

Profiling ist der erste Schritt zur Performance-Optimierung und ermöglicht es, Bottlenecks präzise zu identifizieren.

21.1.1 Built-in Node.js Profiling

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { performance, PerformanceObserver } from 'perf_hooks';
import * as fs from 'fs';

@Injectable()
export class PerformanceProfiler implements OnModuleInit, OnModuleDestroy {
  private observer: PerformanceObserver;
  private metrics: Map<string, number[]> = new Map();

  onModuleInit() {
    // Performance Observer für automatisches Tracking
    this.observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        this.recordMetric(entry.name, entry.duration);
      });
    });

    this.observer.observe({ entryTypes: ['measure', 'function'] });
  }

  onModuleDestroy() {
    this.observer?.disconnect();
    this.exportMetrics();
  }

  // Manuelle Performance-Messungen
  startTiming(label: string): () => number {
    const startTime = performance.now();
    performance.mark(`${label}-start`);

    return () => {
      const endTime = performance.now();
      performance.mark(`${label}-end`);
      performance.measure(label, `${label}-start`, `${label}-end`);
      
      const duration = endTime - startTime;
      this.recordMetric(label, duration);
      return duration;
    };
  }

  // Decorator für automatisches Method Profiling
  profile(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    const className = target.constructor.name;

    descriptor.value = async function (...args: any[]) {
      const label = `${className}.${propertyKey}`;
      const endTiming = this.startTiming(label);

      try {
        const result = await originalMethod.apply(this, args);
        return result;
      } finally {
        endTiming();
      }
    };

    return descriptor;
  }

  private recordMetric(name: string, duration: number): void {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name)!.push(duration);
  }

  getMetrics(): Record<string, {
    count: number;
    average: number;
    min: number;
    max: number;
    p95: number;
    p99: number;
  }> {
    const result: Record<string, any> = {};

    this.metrics.forEach((durations, name) => {
      const sorted = durations.sort((a, b) => a - b);
      const count = durations.length;
      const sum = durations.reduce((a, b) => a + b, 0);

      result[name] = {
        count,
        average: sum / count,
        min: sorted[0],
        max: sorted[count - 1],
        p95: sorted[Math.floor(count * 0.95)],
        p99: sorted[Math.floor(count * 0.99)],
      };
    });

    return result;
  }

  private exportMetrics(): void {
    const metrics = this.getMetrics();
    const timestamp = new Date().toISOString();
    
    fs.writeFileSync(
      `performance-metrics-${timestamp}.json`,
      JSON.stringify(metrics, null, 2)
    );
  }

  // Memory Usage Tracking
  getMemoryUsage(): NodeJS.MemoryUsage & {
    external: number;
    arrayBuffers: number;
  } {
    return process.memoryUsage();
  }

  // CPU Usage (approximiert über Event Loop Lag)
  measureEventLoopLag(): Promise<number> {
    return new Promise((resolve) => {
      const start = process.hrtime.bigint();
      setImmediate(() => {
        const lag = Number(process.hrtime.bigint() - start) / 1e6; // Convert to ms
        resolve(lag);
      });
    });
  }
}

// Usage Example
@Injectable()
export class UserService {
  constructor(private profiler: PerformanceProfiler) {}

  @profile
  async findUsers(filters: any): Promise<User[]> {
    // Automatisches Profiling durch Decorator
    return await this.userRepository.find(filters);
  }

  async complexOperation(): Promise<any> {
    const endTiming = this.profiler.startTiming('complex-operation');
    
    try {
      // Komplexe Operation
      const result = await this.performComplexCalculation();
      return result;
    } finally {
      const duration = endTiming();
      if (duration > 1000) { // Warnung bei >1s
        console.warn(`Slow operation detected: ${duration}ms`);
      }
    }
  }

  private async performComplexCalculation(): Promise<any> {
    // Simulierte komplexe Berechnung
    await new Promise(resolve => setTimeout(resolve, 500));
    return { result: 'computed' };
  }
}

21.1.2 Advanced Profiling mit Clinic.js Integration

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AdvancedProfiler {
  private isProfilingEnabled: boolean;

  constructor(private configService: ConfigService) {
    this.isProfilingEnabled = this.configService.get('PROFILING_ENABLED', false);
  }

  // Heap Snapshot für Memory Analysis
  async createHeapSnapshot(): Promise<string> {
    const v8 = require('v8');
    const fs = require('fs').promises;
    
    const heapSnapshot = v8.getHeapSnapshot();
    const fileName = `heap-${Date.now()}.heapsnapshot`;
    
    const chunks: Buffer[] = [];
    
    return new Promise((resolve, reject) => {
      heapSnapshot.on('data', (chunk) => chunks.push(chunk));
      heapSnapshot.on('end', async () => {
        try {
          await fs.writeFile(fileName, Buffer.concat(chunks));
          resolve(fileName);
        } catch (error) {
          reject(error);
        }
      });
      heapSnapshot.on('error', reject);
    });
  }

  // CPU Profiling
  startCpuProfiling(): void {
    if (!this.isProfilingEnabled) return;

    const inspector = require('inspector');
    const session = new inspector.Session();
    session.connect();

    session.post('Profiler.enable', () => {
      session.post('Profiler.start');
    });

    // Auto-stop nach 30 Sekunden
    setTimeout(() => {
      this.stopCpuProfiling(session);
    }, 30000);
  }

  private stopCpuProfiling(session: any): void {
    session.post('Profiler.stop', (err: any, { profile }: any) => {
      if (err) {
        console.error('CPU Profiling error:', err);
        return;
      }

      const fs = require('fs');
      const fileName = `cpu-profile-${Date.now()}.cpuprofile`;
      fs.writeFileSync(fileName, JSON.stringify(profile));
      
      session.disconnect();
      console.log(`CPU profile saved to ${fileName}`);
    });
  }

  // Real-time Performance Monitoring
  startRealTimeMonitoring(): void {
    const monitoringInterval = setInterval(() => {
      const metrics = {
        timestamp: new Date().toISOString(),
        memory: process.memoryUsage(),
        cpu: process.cpuUsage(),
        uptime: process.uptime(),
        activeHandles: process._getActiveHandles().length,
        activeRequests: process._getActiveRequests().length,
      };

      this.sendMetricsToMonitoring(metrics);
    }, 5000); // Alle 5 Sekunden

    // Cleanup bei Process-Ende
    process.on('SIGTERM', () => clearInterval(monitoringInterval));
    process.on('SIGINT', () => clearInterval(monitoringInterval));
  }

  private sendMetricsToMonitoring(metrics: any): void {
    // Integration mit Monitoring-System (z.B. Prometheus, DataDog)
    console.log('Performance Metrics:', metrics);
    
    // Beispiel: Prometheus Metrics
    // this.prometheusService.recordMetrics(metrics);
  }

  // Bottleneck Detection
  async detectBottlenecks(): Promise<{
    slowQueries: any[];
    memoryLeaks: any[];
    highCpuOperations: any[];
  }> {
    const slowQueries = await this.identifySlowQueries();
    const memoryLeaks = await this.detectMemoryLeaks();
    const highCpuOperations = await this.identifyHighCpuOperations();

    return {
      slowQueries,
      memoryLeaks,
      highCpuOperations,
    };
  }

  private async identifySlowQueries(): Promise<any[]> {
    // Query Performance Analysis
    // Integration mit Database-spezifischen Tools
    return [];
  }

  private async detectMemoryLeaks(): Promise<any[]> {
    // Memory Leak Detection
    const memUsage = process.memoryUsage();
    const threshold = 500 * 1024 * 1024; // 500MB
    
    if (memUsage.heapUsed > threshold) {
      return [{
        type: 'high-memory-usage',
        heapUsed: memUsage.heapUsed,
        threshold,
        timestamp: new Date(),
      }];
    }

    return [];
  }

  private async identifyHighCpuOperations(): Promise<any[]> {
    // CPU-intensive Operations Detection
    const cpuUsage = process.cpuUsage();
    const highCpuThreshold = 80; // 80% CPU

    // Vereinfachte CPU-Berechnung
    const cpuPercent = (cpuUsage.user + cpuUsage.system) / 1000000; // Convert to seconds

    if (cpuPercent > highCpuThreshold) {
      return [{
        type: 'high-cpu-usage',
        cpuPercent,
        threshold: highCpuThreshold,
        timestamp: new Date(),
      }];
    }

    return [];
  }
}

21.1.3 Distributed Tracing mit OpenTelemetry

import { Injectable, OnModuleInit } from '@nestjs/common';
import { NodeSDK } from '@opentelemetry/auto-instrumentations-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';

@Injectable()
export class TelemetryService implements OnModuleInit {
  private sdk: NodeSDK;

  onModuleInit() {
    // OpenTelemetry SDK Konfiguration
    this.sdk = new NodeSDK({
      instrumentations: [getNodeAutoInstrumentations({
        // HTTP-Instrumentierung aktivieren
        '@opentelemetry/instrumentation-http': {
          enabled: true,
          requestHook: (span, request) => {
            span.setAttributes({
              'http.route': this.extractRoute(request.url),
              'http.user_agent': request.headers['user-agent'],
            });
          },
        },
        // Database-Instrumentierung
        '@opentelemetry/instrumentation-mysql': { enabled: true },
        '@opentelemetry/instrumentation-postgres': { enabled: true },
        '@opentelemetry/instrumentation-redis': { enabled: true },
      })],
      metricReader: new PeriodicExportingMetricReader({
        exporter: new PrometheusExporter({
          port: 9464,
        }),
        exportIntervalMillis: 1000,
      }),
    });

    this.sdk.start();
  }

  private extractRoute(url: string): string {
    // Route-Extraktion für bessere Gruppierung
    return url.replace(/\/\d+/g, '/:id')
              .replace(/\?.*/, '');
  }

  // Custom Spans für Business Logic
  async traceOperation<T>(
    operationName: string,
    operation: () => Promise<T>,
    attributes?: Record<string, string | number>
  ): Promise<T> {
    const { trace } = require('@opentelemetry/api');
    const tracer = trace.getTracer('nestjs-app');

    return tracer.startActiveSpan(operationName, async (span) => {
      try {
        if (attributes) {
          span.setAttributes(attributes);
        }

        const result = await operation();
        span.setStatus({ code: 1 }); // SUCCESS
        return result;
      } catch (error) {
        span.setStatus({ 
          code: 2, // ERROR
          message: error.message 
        });
        span.recordException(error);
        throw error;
      } finally {
        span.end();
      }
    });
  }

  // Performance Decorator mit Tracing
  trace(operationName?: string) {
    return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
      const originalMethod = descriptor.value;
      const spanName = operationName || `${target.constructor.name}.${propertyKey}`;

      descriptor.value = async function (...args: any[]) {
        return this.telemetryService.traceOperation(
          spanName,
          () => originalMethod.apply(this, args),
          {
            'method.class': target.constructor.name,
            'method.name': propertyKey,
            'args.count': args.length,
          }
        );
      };

      return descriptor;
    };
  }
}

// Usage Example
@Injectable()
export class TracedUserService {
  constructor(private telemetryService: TelemetryService) {}

  @trace('user.find.complex')
  async findUsersWithComplexLogic(filters: any): Promise<User[]> {
    return await this.telemetryService.traceOperation(
      'database.query.users',
      async () => {
        // Database-Operation
        return await this.userRepository.find(filters);
      },
      {
        'query.type': 'find',
        'filters.count': Object.keys(filters).length,
      }
    );
  }
}

21.2 Database Query Optimization

Datenbankzugriffe sind oft der größte Performance-Bottleneck in Webanwendungen.

21.2.1 Query Analysis und Optimization

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class OptimizedQueryService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>
  ) {}

  // N+1 Problem vermeiden mit Eager Loading
  async findUsersWithPostsOptimized(): Promise<User[]> {
    return await this.userRepository
      .createQueryBuilder('user')
      .leftJoinAndSelect('user.posts', 'posts')
      .leftJoinAndSelect('posts.comments', 'comments')
      .getMany();
  }

  // Selective Field Loading
  async findUsersWithLimitedFields(): Promise<Partial<User>[]> {
    return await this.userRepository
      .createQueryBuilder('user')
      .select(['user.id', 'user.email', 'user.name'])
      .getMany();
  }

  // Pagination mit Cursor-based Navigation
  async findUsersWithCursorPagination(
    cursor?: string,
    limit: number = 20
  ): Promise<{
    users: User[];
    nextCursor?: string;
    hasMore: boolean;
  }> {
    const queryBuilder = this.userRepository
      .createQueryBuilder('user')
      .orderBy('user.createdAt', 'DESC')
      .limit(limit + 1); // +1 um hasMore zu bestimmen

    if (cursor) {
      const cursorDate = new Date(Buffer.from(cursor, 'base64').toString());
      queryBuilder.where('user.createdAt < :cursor', { cursor: cursorDate });
    }

    const users = await queryBuilder.getMany();
    const hasMore = users.length > limit;
    
    if (hasMore) {
      users.pop(); // Letzten Eintrag entfernen
    }

    const nextCursor = hasMore && users.length > 0
      ? Buffer.from(users[users.length - 1].createdAt.toISOString()).toString('base64')
      : undefined;

    return {
      users,
      nextCursor,
      hasMore,
    };
  }

  // Aggregation mit Caching
  async getUserStatistics(): Promise<{
    totalUsers: number;
    activeUsers: number;
    averagePostsPerUser: number;
  }> {
    const [totalUsers, activeUsers, avgPosts] = await Promise.all([
      this.userRepository.count(),
      this.userRepository.count({ where: { isActive: true } }),
      this.userRepository
        .createQueryBuilder('user')
        .leftJoin('user.posts', 'posts')
        .select('AVG(subquery.postCount)', 'average')
        .from(subQuery => {
          return subQuery
            .select('user.id', 'userId')
            .addSelect('COUNT(posts.id)', 'postCount')
            .from(User, 'user')
            .leftJoin('user.posts', 'posts')
            .groupBy('user.id');
        }, 'subquery')
        .getRawOne(),
    ]);

    return {
      totalUsers,
      activeUsers,
      averagePostsPerUser: parseFloat(avgPosts?.average || '0'),
    };
  }

  // Bulk Operations für bessere Performance
  async bulkUpdateUserStatus(userIds: string[], status: string): Promise<void> {
    await this.userRepository
      .createQueryBuilder()
      .update(User)
      .set({ status })
      .where('id IN (:...userIds)', { userIds })
      .execute();
  }

  async bulkInsertUsers(users: Partial<User>[]): Promise<void> {
    // Batch-Insert in Chunks für bessere Performance
    const chunkSize = 1000;
    
    for (let i = 0; i < users.length; i += chunkSize) {
      const chunk = users.slice(i, i + chunkSize);
      await this.userRepository
        .createQueryBuilder()
        .insert()
        .into(User)
        .values(chunk)
        .execute();
    }
  }

  // Query Performance Monitoring
  async executeWithMonitoring<T>(
    queryBuilder: SelectQueryBuilder<T>,
    queryName: string
  ): Promise<T[]> {
    const startTime = performance.now();
    
    try {
      const result = await queryBuilder.getMany();
      const duration = performance.now() - startTime;
      
      // Performance-Logging
      if (duration > 1000) { // Langsame Queries loggen
        console.warn(`Slow query detected: ${queryName}`, {
          duration: `${duration.toFixed(2)}ms`,
          sql: queryBuilder.getSql(),
          parameters: queryBuilder.getParameters(),
        });
      }

      return result;
    } catch (error) {
      const duration = performance.now() - startTime;
      console.error(`Query failed: ${queryName}`, {
        duration: `${duration.toFixed(2)}ms`,
        error: error.message,
        sql: queryBuilder.getSql(),
      });
      throw error;
    }
  }

  // Index Optimization Hints
  async analyzeQueryPerformance(): Promise<{
    recommendations: string[];
    slowQueries: Array<{
      query: string;
      avgDuration: number;
      executionCount: number;
    }>;
  }> {
    // Vereinfachte Analyse - in der Praxis würde man
    // Database-spezifische EXPLAIN-Befehle verwenden
    
    return {
      recommendations: [
        'Add index on user.email for faster lookups',
        'Consider partitioning large tables',
        'Use covering indexes for frequent SELECT queries',
      ],
      slowQueries: [
        {
          query: 'SELECT * FROM users WHERE status = ?',
          avgDuration: 1250,
          executionCount: 1500,
        },
      ],
    };
  }
}

21.2.2 Database Connection Optimization

import { Injectable } from '@nestjs/common';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class DatabaseOptimizationService {
  constructor(private configService: ConfigService) {}

  // Optimierte Database Configuration
  getOptimizedDatabaseConfig(): TypeOrmModuleOptions {
    const isProduction = this.configService.get('NODE_ENV') === 'production';

    return {
      type: 'postgres',
      host: this.configService.get('DB_HOST'),
      port: this.configService.get('DB_PORT'),
      username: this.configService.get('DB_USERNAME'),
      password: this.configService.get('DB_PASSWORD'),
      database: this.configService.get('DB_NAME'),
      
      // Connection Pool Optimization
      extra: {
        // Maximale Anzahl Verbindungen
        max: isProduction ? 20 : 10,
        
        // Minimale Anzahl Verbindungen
        min: isProduction ? 5 : 2,
        
        // Connection Timeout
        connectionTimeoutMillis: 30000,
        
        // Idle Timeout
        idleTimeoutMillis: 30000,
        
        // Statement Timeout
        statement_timeout: 30000,
        
        // Query Timeout
        query_timeout: 30000,
        
        // SSL Configuration für Production
        ssl: isProduction ? { rejectUnauthorized: false } : false,
        
        // Application Name für besseres Monitoring
        application_name: 'nestjs-app',
      },
      
      // Query Optimization
      cache: {
        duration: 30000, // 30 Sekunden Query Cache
        type: 'redis',
        options: {
          host: this.configService.get('REDIS_HOST'),
          port: this.configService.get('REDIS_PORT'),
        },
      },
      
      // Logging für Development
      logging: !isProduction ? ['query', 'error', 'warn'] : ['error'],
      
      // Schema Synchronization nur in Development
      synchronize: !isProduction,
      
      // Migration Settings
      migrationsRun: isProduction,
      migrations: ['dist/migrations/*.js'],
      
      // Entity Loading
      entities: ['dist/**/*.entity.js'],
      
      // Retry Logic
      retryAttempts: isProduction ? 3 : 1,
      retryDelay: 3000,
    };
  }

  // Read Replicas für Read-Heavy Workloads
  getReadReplicaConfig(): TypeOrmModuleOptions {
    return {
      ...this.getOptimizedDatabaseConfig(),
      name: 'readReplica',
      host: this.configService.get('DB_READ_REPLICA_HOST'),
      
      // Nur Read-Operations
      extra: {
        ...this.getOptimizedDatabaseConfig().extra,
        // Read-Only Connection
        readonly: true,
      },
    };
  }

  // Connection Health Monitoring
  async monitorConnectionHealth(): Promise<{
    activeConnections: number;
    idleConnections: number;
    totalConnections: number;
    connectionErrors: number;
  }> {
    // Implementation würde Database-spezifische Monitoring-Queries verwenden
    // Hier ein vereinfachtes Beispiel
    
    return {
      activeConnections: 15,
      idleConnections: 5,
      totalConnections: 20,
      connectionErrors: 0,
    };
  }

  // Automatic Connection Pool Scaling
  async adjustConnectionPool(metrics: {
    avgResponseTime: number;
    activeConnections: number;
    queuedRequests: number;
  }): Promise<void> {
    const { avgResponseTime, activeConnections, queuedRequests } = metrics;
    
    // Scale up wenn:
    // - Response Time > 1s
    // - Queue > 10 Requests
    // - Connection Usage > 80%
    
    if (avgResponseTime > 1000 || queuedRequests > 10) {
      console.log('Scaling up database connections');
      // Implementation für Pool-Skalierung
    }
    
    // Scale down wenn alle Metriken niedrig sind
    if (avgResponseTime < 200 && queuedRequests === 0 && activeConnections < 5) {
      console.log('Scaling down database connections');
      // Implementation für Pool-Reduzierung
    }
  }
}

// Query Builder mit automatischer Optimization
@Injectable()
export class OptimizedQueryBuilder {
  constructor(
    @InjectRepository(User) private userRepo: Repository<User>
  ) {}

  createOptimizedQuery(): SelectQueryBuilder<User> {
    return this.userRepo
      .createQueryBuilder('user')
      .cache(30000) // 30s Cache
      .setQueryRunner(this.getOptimalQueryRunner());
  }

  private getOptimalQueryRunner(): any {
    // Read-Heavy Operations zu Read Replica routen
    const operationType = this.determineOperationType();
    
    if (operationType === 'read') {
      // Read Replica verwenden
      return this.getReadReplicaQueryRunner();
    }
    
    // Write Operations zur Master DB
    return this.getMasterQueryRunner();
  }

  private determineOperationType(): 'read' | 'write' {
    // Query-Typ-Detection basierend auf Call Stack
    const stack = new Error().stack;
    
    if (stack?.includes('find') || stack?.includes('get')) {
      return 'read';
    }
    
    return 'write';
  }

  private getReadReplicaQueryRunner(): any {
    // Read Replica Connection
    return null; // Implementation abhängig von Setup
  }

  private getMasterQueryRunner(): any {
    // Master DB Connection
    return null; // Implementation abhängig von Setup
  }
}

21.3 Memory Management

Effizientes Memory Management ist entscheidend für die Stabilität und Performance von Node.js-Anwendungen.

21.3.1 Memory Leak Detection und Prevention

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';

@Injectable()
export class MemoryManager implements OnModuleInit, OnModuleDestroy {
  private memoryBaseline: NodeJS.MemoryUsage;
  private memoryHistory: Array<{
    timestamp: Date;
    usage: NodeJS.MemoryUsage;
    gcCount: number;
  }> = [];
  private gcCount = 0;

  onModuleInit() {
    this.memoryBaseline = process.memoryUsage();
    this.setupGCMonitoring();
    this.setupMemoryMonitoring();
  }

  onModuleDestroy() {
    this.logMemoryReport();
  }

  private setupGCMonitoring(): void {
    // GC Event Monitoring
    const { PerformanceObserver } = require('perf_hooks');
    
    const gcObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === 'gc') {
          this.gcCount++;
          this.logGCEvent(entry);
        }
      });
    });

    gcObserver.observe({ entryTypes: ['gc'] });
  }

  @Interval(30000) // Alle 30 Sekunden
  private setupMemoryMonitoring(): void {
    const currentUsage = process.memoryUsage();
    
    this.memoryHistory.push({
      timestamp: new Date(),
      usage: currentUsage,
      gcCount: this.gcCount,
    });

    // Nur letzte 100 Einträge behalten
    if (this.memoryHistory.length > 100) {
      this.memoryHistory.shift();
    }

    // Memory Leak Detection
    this.detectMemoryLeaks(currentUsage);
  }

  private detectMemoryLeaks(currentUsage: NodeJS.MemoryUsage): void {
    const heapGrowth = currentUsage.heapUsed - this.memoryBaseline.heapUsed;
    const heapGrowthMB = heapGrowth / (1024 * 1024);

    // Warnung bei Heap-Wachstum > 100MB
    if (heapGrowthMB > 100) {
      console.warn('Potential memory leak detected', {
        heapGrowthMB: heapGrowthMB.toFixed(2),
        currentHeapMB: (currentUsage.heapUsed / (1024 * 1024)).toFixed(2),
        baselineHeapMB: (this.memoryBaseline.heapUsed / (1024 * 1024)).toFixed(2),
      });

      // Heap Snapshot für Analyse erstellen
      this.createHeapSnapshot();
    }

    // RSS Memory Check
    const rssGrowth = currentUsage.rss - this.memoryBaseline.rss;
    const rssGrowthMB = rssGrowth / (1024 * 1024);

    if (rssGrowthMB > 200) { // 200MB RSS Wachstum
      console.warn('High RSS memory usage detected', {
        rssGrowthMB: rssGrowthMB.toFixed(2),
        currentRssMB: (currentUsage.rss / (1024 * 1024)).toFixed(2),
      });
    }
  }

  private async createHeapSnapshot(): Promise<void> {
    try {
      const v8 = require('v8');
      const fs = require('fs').promises;
      
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const filename = `heap-snapshot-${timestamp}.heapsnapshot`;
      
      const heapSnapshot = v8.getHeapSnapshot();
      const chunks: Buffer[] = [];
      
      heapSnapshot.on('data', (chunk) => chunks.push(chunk));
      heapSnapshot.on('end', async () => {
        await fs.writeFile(filename, Buffer.concat(chunks));
        console.log(`Heap snapshot saved: ${filename}`);
      });
    } catch (error) {
      console.error('Failed to create heap snapshot:', error);
    }
  }

  private logGCEvent(entry: any): void {
    console.log('GC Event:', {
      kind: entry.detail?.kind,
      duration: `${entry.duration.toFixed(2)}ms`,
      timestamp: new Date(entry.startTime).toISOString(),
    });
  }

  // Memory-optimized Object Pool
  createObjectPool<T>(
    factory: () => T,
    reset: (obj: T) => void,
    maxSize: number = 100
  ): ObjectPool<T> {
    return new ObjectPool(factory, reset, maxSize);
  }

  // Memory-efficient Data Processing
  async processLargeDataset<T, R>(
    data: T[],
    processor: (item: T) => Promise<R>,
    batchSize: number = 1000
  ): Promise<R[]> {
    const results: R[] = [];
    
    for (let i = 0; i < data.length; i += batchSize) {
      const batch = data.slice(i, i + batchSize);
      const batchResults = await Promise.all(
        batch.map(item => processor(item))
      );
      
      results.push(...batchResults);
      
      // Garbage Collection nach jedem Batch
      if (global.gc) {
        global.gc();
      }
      
      // Kurze Pause für Event Loop
      await new Promise(resolve => setImmediate(resolve));
    }
    
    return results;
  }

  private logMemoryReport(): void {
    if (this.memoryHistory.length === 0) return;

    const latest = this.memoryHistory[this.memoryHistory.length - 1];
    const growth = {
      heapUsed: latest.usage.heapUsed - this.memoryBaseline.heapUsed,
      heapTotal: latest.usage.heapTotal - this.memoryBaseline.heapTotal,
      rss: latest.usage.rss - this.memoryBaseline.rss,
      external: latest.usage.external - this.memoryBaseline.external,
    };

    console.log('Memory Report:', {
      baseline: this.formatMemoryUsage(this.memoryBaseline),
      current: this.formatMemoryUsage(latest.usage),
      growth: this.formatMemoryUsage(growth as NodeJS.MemoryUsage),
      totalGCCount: this.gcCount,
    });
  }

  private formatMemoryUsage(usage: NodeJS.MemoryUsage): Record<string, string> {
    return {
      heapUsed: `${(usage.heapUsed / (1024 * 1024)).toFixed(2)} MB`,
      heapTotal: `${(usage.heapTotal / (1024 * 1024)).toFixed(2)} MB`,
      rss: `${(usage.rss / (1024 * 1024)).toFixed(2)} MB`,
      external: `${(usage.external / (1024 * 1024)).toFixed(2)} MB`,
    };
  }

  // Memory-aware Caching
  shouldCacheBasedOnMemory(): boolean {
    const usage = process.memoryUsage();
    const heapUsagePercent = (usage.heapUsed / usage.heapTotal) * 100;
    
    // Caching stoppen bei >85% Heap-Nutzung
    return heapUsagePercent < 85;
  }

  // Force Garbage Collection (nur für Development/Testing)
  forceGC(): boolean {
    if (global.gc) {
      global.gc();
      return true;
    }
    return false;
  }
}

// Object Pool Implementation
class ObjectPool<T> {
  private pool: T[] = [];
  private maxSize: number;

  constructor(
    private factory: () => T,
    private reset: (obj: T) => void,
    maxSize: number = 100
  ) {
    this.maxSize = maxSize;
  }

  acquire(): T {
    if (this.pool.length > 0) {
      return this.pool.pop()!;
    }
    return this.factory();
  }

  release(obj: T): void {
    if (this.pool.length < this.maxSize) {
      this.reset(obj);
      this.pool.push(obj);
    }
    // Objektreferenz entfernen falls Pool voll
  }

  size(): number {
    return this.pool.length;
  }

  clear(): void {
    this.pool.length = 0;
  }
}

// Memory-efficient Service Example
@Injectable()
export class MemoryEfficientService {
  private readonly objectPool: ObjectPool<Buffer>;

  constructor(private memoryManager: MemoryManager) {
    // Buffer Pool für häufige Allokationen
    this.objectPool = this.memoryManager.createObjectPool(
      () => Buffer.alloc(1024), // 1KB Buffer
      (buffer) => buffer.fill(0), // Reset Buffer
      50 // Max 50 Buffers im Pool
    );
  }

  async processData(data: any[]): Promise<any[]> {
    // Memory-aware Processing
    if (!this.memoryManager.shouldCacheBasedOnMemory()) {
      console.warn('Skipping cache due to high memory usage');
    }

    return await this.memoryManager.processLargeDataset(
      data,
      async (item) => this.processItem(item),
      500 // Kleinere Batch-Größe
    );
  }

  private async processItem(item: any): Promise<any> {
    const buffer = this.objectPool.acquire();
    
    try {
      // Buffer für Processing verwenden
      const result = this.performOperation(item, buffer);
      return result;
    } finally {
      // Buffer zurück zum Pool
      this.objectPool.release(buffer);
    }
  }

  private performOperation(item: any, buffer: Buffer): any {
    // Verarbeitung mit wiederverwendetem Buffer
    return { processed: item.id };
  }
}

21.4 Connection Pooling

Connection Pooling ist entscheidend für die Skalierbarkeit von Datenbankzugriffen und externen API-Calls.

21.4.1 Database Connection Pooling

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Pool, PoolClient, PoolConfig } from 'pg';

@Injectable()
export class DatabaseConnectionPool implements OnModuleInit, OnModuleDestroy {
  private pool: Pool;
  private connectionMetrics = {
    activeConnections: 0,
    totalConnections: 0,
    connectionErrors: 0,
    queryCount: 0,
    avgQueryTime: 0,
  };

  constructor(private configService: ConfigService) {}

  async onModuleInit(): Promise<void> {
    await this.initializePool();
    this.setupMonitoring();
  }

  async onModuleDestroy(): Promise<void> {
    await this.pool?.end();
  }

  private async initializePool(): Promise<void> {
    const poolConfig: PoolConfig = {
      host: this.configService.get('DB_HOST'),
      port: this.configService.get('DB_PORT'),
      database: this.configService.get('DB_NAME'),
      user: this.configService.get('DB_USER'),
      password: this.configService.get('DB_PASSWORD'),
      
      // Pool Configuration
      min: this.configService.get('DB_POOL_MIN', 2),
      max: this.configService.get('DB_POOL_MAX', 20),
      
      // Connection Timeouts
      connectionTimeoutMillis: 30000,
      idleTimeoutMillis: 30000,
      
      // Keep-alive
      keepAlive: true,
      keepAliveInitialDelayMillis: 10000,
      
      // SSL Configuration
      ssl: this.configService.get('NODE_ENV') === 'production' ? {
        rejectUnauthorized: false
      } : false,
      
      // Application Name für Monitoring
      application_name: 'nestjs-app',
      
      // Statement Timeout
      statement_timeout: 30000,
    };

    this.pool = new Pool(poolConfig);

    // Event Listeners für Monitoring
    this.pool.on('connect', (client) => {
      this.connectionMetrics.activeConnections++;
      this.connectionMetrics.totalConnections++;
      console.log('New database connection established');
    });

    this.pool.on('remove', (client) => {
      this.connectionMetrics.activeConnections--;
      console.log('Database connection removed from pool');
    });

    this.pool.on('error', (error, client) => {
      this.connectionMetrics.connectionErrors++;
      console.error('Database pool error:', error);
    });
  }

  async executeQuery<T = any>(
    query: string,
    params?: any[],
    options?: { timeout?: number }
  ): Promise<T[]> {
    const startTime = performance.now();
    let client: PoolClient | undefined;

    try {
      client = await this.pool.connect();
      
      if (options?.timeout) {
        client.query('SET statement_timeout = $1', [options.timeout]);
      }

      const result = await client.query(query, params);
      
      // Metrics aktualisieren
      const duration = performance.now() - startTime;
      this.updateQueryMetrics(duration);
      
      return result.rows;
    } catch (error) {
      this.connectionMetrics.connectionErrors++;
      console.error('Query execution failed:', {
        query,
        params,
        error: error.message,
        duration: performance.now() - startTime,
      });
      throw error;
    } finally {
      if (client) {
        client.release();
      }
    }
  }

  async executeTransaction<T>(
    operations: (client: PoolClient) => Promise<T>
  ): Promise<T> {
    const client = await this.pool.connect();
    
    try {
      await client.query('BEGIN');
      const result = await operations(client);
      await client.query('COMMIT');
      return result;
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  private updateQueryMetrics(duration: number): void {
    this.connectionMetrics.queryCount++;
    
    // Moving Average für Query Time
    const alpha = 0.1; // Gewichtung für exponentiellen Durchschnitt
    this.connectionMetrics.avgQueryTime = 
      (alpha * duration) + ((1 - alpha) * this.connectionMetrics.avgQueryTime);
  }

  private setupMonitoring(): void {
    setInterval(() => {
      this.logPoolStatistics();
      this.checkPoolHealth();
    }, 30000); // Alle 30 Sekunden
  }

  private logPoolStatistics(): void {
    const poolStats = {
      totalCount: this.pool.totalCount,
      idleCount: this.pool.idleCount,
      waitingCount: this.pool.waitingCount,
      ...this.connectionMetrics,
    };

    console.log('Database Pool Statistics:', poolStats);
  }

  private checkPoolHealth(): void {
    const { totalCount, idleCount, waitingCount } = this.pool;
    
    // Warnung bei hoher Queue
    if (waitingCount > 5) {
      console.warn('High connection queue detected', {
        waiting: waitingCount,
        total: totalCount,
        idle: idleCount,
      });
    }

    // Warnung bei niedriger Idle-Rate
    const idleRate = idleCount / totalCount;
    if (idleRate < 0.1 && totalCount > 5) {
      console.warn('Low idle connection rate', {
        idleRate: (idleRate * 100).toFixed(1) + '%',
        suggestion: 'Consider increasing pool size',
      });
    }
  }

  // Pool-Resize basierend auf Load
  async adjustPoolSize(metrics: {
    avgResponseTime: number;
    queueLength: number;
    cpuUsage: number;
  }): Promise<void> {
    const currentMax = this.pool.options.max || 20;
    
    // Scale up bei hoher Last
    if (metrics.avgResponseTime > 1000 && metrics.queueLength > 3) {
      const newMax = Math.min(currentMax + 5, 50);
      await this.resizePool(newMax);
      console.log(`Scaled up pool to ${newMax} connections`);
    }
    
    // Scale down bei niedriger Last
    if (metrics.avgResponseTime < 200 && metrics.queueLength === 0 && metrics.cpuUsage < 30) {
      const newMax = Math.max(currentMax - 2, 5);
      await this.resizePool(newMax);
      console.log(`Scaled down pool to ${newMax} connections`);
    }
  }

  private async resizePool(newMaxSize: number): Promise<void> {
    // Pool-Resize würde spezifische Implementation benötigen
    // da pg.Pool keine direkte Resize-Methode hat
    console.log(`Pool resize to ${newMaxSize} connections requested`);
  }

  getPoolMetrics(): any {
    return {
      pool: {
        totalCount: this.pool.totalCount,
        idleCount: this.pool.idleCount,
        waitingCount: this.pool.waitingCount,
      },
      metrics: this.connectionMetrics,
    };
  }
}

21.4.2 HTTP Connection Pooling

import { Injectable, OnModuleInit } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import * as https from 'https';
import * as http from 'http';

@Injectable()
export class HttpConnectionPool implements OnModuleInit {
  private httpAgent: http.Agent;
  private httpsAgent: https.Agent;

  constructor(
    private httpService: HttpService,
    private configService: ConfigService
  ) {}

  onModuleInit() {
    this.initializeAgents();
    this.configureHttpService();
  }

  private initializeAgents(): void {
    const agentConfig = {
      // Connection Pool Konfiguration
      keepAlive: true,
      keepAliveMsecs: 30000,
      maxSockets: this.configService.get('HTTP_MAX_SOCKETS', 50),
      maxFreeSockets: this.configService.get('HTTP_MAX_FREE_SOCKETS', 10),
      timeout: this.configService.get('HTTP_TIMEOUT', 30000),
      freeSocketTimeout: this.configService.get('HTTP_FREE_SOCKET_TIMEOUT', 15000),
    };

    this.httpAgent = new http.Agent(agentConfig);
    this.httpsAgent = new https.Agent({
      ...agentConfig,
      // SSL/TLS Konfiguration
      rejectUnauthorized: this.configService.get('NODE_ENV') === 'production',
      secureProtocol: 'TLSv1_2_method',
    });
  }

  private configureHttpService(): void {
    // Axios mit Connection Pooling konfigurieren
    this.httpService.axiosRef.defaults.httpAgent = this.httpAgent;
    this.httpService.axiosRef.defaults.httpsAgent = this.httpsAgent;
    
    // Request/Response Interceptors für Monitoring
    this.httpService.axiosRef.interceptors.request.use((config) => {
      config.metadata = { startTime: performance.now() };
      return config;
    });

    this.httpService.axiosRef.interceptors.response.use(
      (response) => {
        const duration = performance.now() - response.config.metadata.startTime;
        this.logHttpMetrics(response.config.url!, duration, 'success');
        return response;
      },
      (error) => {
        const duration = performance.now() - error.config?.metadata?.startTime || 0;
        this.logHttpMetrics(error.config?.url || 'unknown', duration, 'error');
        return Promise.reject(error);
      }
    );
  }

  private logHttpMetrics(url: string, duration: number, status: string): void {
    if (duration > 5000) { // Langsame Requests loggen
      console.warn('Slow HTTP request detected:', {
        url,
        duration: `${duration.toFixed(2)}ms`,
        status,
      });
    }
  }

  // Connection Pool Statistiken
  getConnectionStats(): {
    http: any;
    https: any;
  } {
    return {
      http: {
        sockets: Object.keys(this.httpAgent.sockets).length,
        freeSockets: Object.keys(this.httpAgent.freeSockets).length,
        requests: Object.keys(this.httpAgent.requests).length,
      },
      https: {
        sockets: Object.keys(this.httpsAgent.sockets).length,
        freeSockets: Object.keys(this.httpsAgent.freeSockets).length,
        requests: Object.keys(this.httpsAgent.requests).length,
      },
    };
  }

  // Circuit Breaker Pattern für externe Services
  async makeResilientRequest<T>(
    url: string,
    options: any = {},
    circuitBreakerConfig?: {
      failureThreshold?: number;
      resetTimeout?: number;
    }
  ): Promise<T> {
    const config = {
      failureThreshold: 5,
      resetTimeout: 60000,
      ...circuitBreakerConfig,
    };

    // Vereinfachte Circuit Breaker Implementation
    return await this.httpService.axiosRef({
      url,
      ...options,
      timeout: options.timeout || 10000,
      retry: 3,
      retryDelay: 1000,
    }).then(response => response.data);
  }

  // Batch HTTP Requests mit Connection Sharing
  async batchRequests<T>(
    requests: Array<{
      url: string;
      method?: string;
      data?: any;
      headers?: any;
    }>,
    options?: {
      batchSize?: number;
      delay?: number;
    }
  ): Promise<T[]> {
    const { batchSize = 10, delay = 100 } = options || {};
    const results: T[] = [];

    for (let i = 0; i < requests.length; i += batchSize) {
      const batch = requests.slice(i, i + batchSize);
      
      const batchPromises = batch.map(request =>
        this.httpService.axiosRef(request)
          .then(response => response.data)
          .catch(error => ({ error: error.message }))
      );

      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);

      // Kurze Pause zwischen Batches
      if (i + batchSize < requests.length && delay > 0) {
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }

    return results;
  }

  // Connection Pool Cleanup
  async cleanup(): Promise<void> {
    this.httpAgent.destroy();
    this.httpsAgent.destroy();
  }
}

21.5 Async/Await Optimierung

Asynchrone Operationen richtig zu optimieren ist entscheidend für die Performance von Node.js-Anwendungen.

21.5.1 Parallel vs Sequential Processing

import { Injectable } from '@nestjs/common';

@Injectable()
export class AsyncOptimizationService {
  // SCHLECHT: Sequential Processing
  async processUsersSequential(userIds: string[]): Promise<any[]> {
    const results = [];
    
    for (const userId of userIds) {
      const user = await this.fetchUser(userId);
      const profile = await this.fetchUserProfile(userId);
      const permissions = await this.fetchUserPermissions(userId);
      
      results.push({ user, profile, permissions });
    }
    
    return results; // Sehr langsam bei vielen Benutzern
  }

  // BESSER: Parallel Processing
  async processUsersParallel(userIds: string[]): Promise<any[]> {
    const results = await Promise.all(
      userIds.map(async (userId) => {
        // Parallele Requests für jeden Benutzer
        const [user, profile, permissions] = await Promise.all([
          this.fetchUser(userId),
          this.fetchUserProfile(userId),
          this.fetchUserPermissions(userId),
        ]);
        
        return { user, profile, permissions };
      })
    );
    
    return results;
  }

  // OPTIMAL: Batch + Parallel mit Concurrency Control
  async processUsersBatched(
    userIds: string[],
    batchSize: number = 10,
    concurrency: number = 5
  ): Promise<any[]> {
    const results: any[] = [];
    
    // Batches mit begrenzter Concurrency
    for (let i = 0; i < userIds.length; i += batchSize) {
      const batch = userIds.slice(i, i + batchSize);
      
      const batchResults = await this.processBatchWithConcurrency(
        batch,
        concurrency
      );
      
      results.push(...batchResults);
    }
    
    return results;
  }

  private async processBatchWithConcurrency(
    userIds: string[],
    concurrency: number
  ): Promise<any[]> {
    const semaphore = new Semaphore(concurrency);
    
    const promises = userIds.map(async (userId) => {
      await semaphore.acquire();
      
      try {
        const [user, profile, permissions] = await Promise.all([
          this.fetchUser(userId),
          this.fetchUserProfile(userId),
          this.fetchUserPermissions(userId),
        ]);
        
        return { user, profile, permissions };
      } finally {
        semaphore.release();
      }
    });
    
    return Promise.all(promises);
  }

  // Streaming für große Datenmengen
  async *processUsersStream(userIds: string[]): AsyncGenerator<any, void, unknown> {
    const concurrency = 5;
    const semaphore = new Semaphore(concurrency);
    
    const processingQueue = userIds.map(async (userId) => {
      await semaphore.acquire();
      
      try {
        const [user, profile, permissions] = await Promise.all([
          this.fetchUser(userId),
          this.fetchUserProfile(userId),
          this.fetchUserPermissions(userId),
        ]);
        
        return { userId, user, profile, permissions };
      } finally {
        semaphore.release();
      }
    });

    // Ergebnisse streamen sobald sie verfügbar sind
    for await (const result of this.processAsTheyComplete(processingQueue)) {
      yield result;
    }
  }

  private async *processAsTheyComplete<T>(
    promises: Promise<T>[]
  ): AsyncGenerator<T, void, unknown> {
    const pending = new Set(promises);
    
    while (pending.size > 0) {
      const race = Promise.race(
        Array.from(pending).map(async (promise) => {
          const result = await promise;
          pending.delete(promise);
          return result;
        })
      );
      
      yield await race;
    }
  }

  // Optimierte Error Handling
  async processWithErrorRecovery<T>(
    operations: (() => Promise<T>)[],
    options: {
      maxRetries?: number;
      retryDelay?: number;
      failFast?: boolean;
    } = {}
  ): Promise<Array<T | Error>> {
    const { maxRetries = 3, retryDelay = 1000, failFast = false } = options;
    
    const results = await Promise.allSettled(
      operations.map(async (operation) => {
        let lastError: Error;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
          try {
            return await operation();
          } catch (error) {
            lastError = error;
            
            if (failFast) {
              throw error;
            }
            
            if (attempt < maxRetries) {
              await this.delay(retryDelay * attempt); // Exponential backoff
            }
          }
        }
        
        throw lastError!;
      })
    );
    
    return results.map(result => 
      result.status === 'fulfilled' ? result.value : result.reason
    );
  }

  // Promise Timeout Utility
  withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
    return Promise.race([
      promise,
      new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs)
      ),
    ]);
  }

  // Memory-efficient async processing
  async processLargeDatasetAsync<T, R>(
    data: T[],
    processor: (item: T) => Promise<R>,
    options: {
      batchSize?: number;
      concurrency?: number;
      memoryThreshold?: number;
    } = {}
  ): Promise<R[]> {
    const { batchSize = 1000, concurrency = 10, memoryThreshold = 100 * 1024 * 1024 } = options;
    const results: R[] = [];
    
    for (let i = 0; i < data.length; i += batchSize) {
      // Memory Check
      const memUsage = process.memoryUsage();
      if (memUsage.heapUsed > memoryThreshold) {
        console.warn('High memory usage, forcing GC');
        if (global.gc) global.gc();
        await this.delay(100); // Kurze Pause
      }
      
      const batch = data.slice(i, i + batchSize);
      const semaphore = new Semaphore(concurrency);
      
      const batchResults = await Promise.all(
        batch.map(async (item) => {
          await semaphore.acquire();
          try {
            return await processor(item);
          } finally {
            semaphore.release();
          }
        })
      );
      
      results.push(...batchResults);
      
      // Progress Logging
      const progress = ((i + batch.length) / data.length * 100).toFixed(1);
      console.log(`Processing progress: ${progress}%`);
    }
    
    return results;
  }

  private async delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Mock-Methoden für Beispiele
  private async fetchUser(userId: string): Promise<any> {
    await this.delay(Math.random() * 100);
    return { id: userId, name: `User ${userId}` };
  }

  private async fetchUserProfile(userId: string): Promise<any> {
    await this.delay(Math.random() * 150);
    return { userId, profile: 'profile data' };
  }

  private async fetchUserPermissions(userId: string): Promise<any> {
    await this.delay(Math.random() * 80);
    return { userId, permissions: ['read', 'write'] };
  }
}

// Semaphore Implementation für Concurrency Control
class Semaphore {
  private permits: number;
  private waitQueue: Array<() => void> = [];

  constructor(permits: number) {
    this.permits = permits;
  }

  async acquire(): Promise<void> {
    return new Promise((resolve) => {
      if (this.permits > 0) {
        this.permits--;
        resolve();
      } else {
        this.waitQueue.push(resolve);
      }
    });
  }

  release(): void {
    if (this.waitQueue.length > 0) {
      const next = this.waitQueue.shift()!;
      next();
    } else {
      this.permits++;
    }
  }

  available(): number {
    return this.permits;
  }
}

21.6 Bundle Size Optimization

Die Optimierung der Bundle-Größe ist wichtig für schnelle Startup-Zeiten und reduzierten Memory-Footprint.

21.6.1 Dynamic Imports und Lazy Loading

import { Injectable, OnModuleInit } from '@nestjs/common';

@Injectable()
export class LazyLoadingService implements OnModuleInit {
  private heavyLibraries: Map<string, any> = new Map();

  async onModuleInit() {
    // Kritische Module vorladen
    await this.preloadCriticalModules();
  }

  private async preloadCriticalModules(): Promise<void> {
    // Nur wirklich kritische Module beim Start laden
    const criticalModules = ['lodash/pick', 'date-fns/format'];
    
    await Promise.all(
      criticalModules.map(async (moduleName) => {
        try {
          const module = await import(moduleName);
          this.heavyLibraries.set(moduleName, module);
        } catch (error) {
          console.warn(`Failed to preload ${moduleName}:`, error.message);
        }
      })
    );
  }

  // Lazy Loading für schwere Bibliotheken
  async getImageProcessingLibrary(): Promise<any> {
    const cacheKey = 'sharp';
    
    if (this.heavyLibraries.has(cacheKey)) {
      return this.heavyLibraries.get(cacheKey);
    }

    try {
      const sharp = await import('sharp');
      this.heavyLibraries.set(cacheKey, sharp);
      return sharp;
    } catch (error) {
      console.error('Failed to load image processing library:', error);
      throw new Error('Image processing not available');
    }
  }

  async getPdfLibrary(): Promise<any> {
    const cacheKey = 'pdf-lib';
    
    if (this.heavyLibraries.has(cacheKey)) {
      return this.heavyLibraries.get(cacheKey);
    }

    const pdfLib = await import('pdf-lib');
    this.heavyLibraries.set(cacheKey, pdfLib);
    return pdfLib;
  }

  // Feature-basiertes Lazy Loading
  async loadFeatureModule(featureName: string): Promise<any> {
    const moduleMap: Record<string, () => Promise<any>> = {
      'email': () => import('../features/email/email.module'),
      'analytics': () => import('../features/analytics/analytics.module'),
      'reporting': () => import('../features/reporting/reporting.module'),
      'export': () => import('../features/export/export.module'),
    };

    const loader = moduleMap[featureName];
    if (!loader) {
      throw new Error(`Unknown feature module: ${featureName}`);
    }

    return await loader();
  }

  // Conditional Loading basierend auf Environment
  async loadEnvironmentSpecificModules(): Promise<void> {
    const environment = process.env.NODE_ENV;

    if (environment === 'development') {
      // Development-spezifische Module
      await Promise.all([
        import('morgan').then(morgan => this.heavyLibraries.set('morgan', morgan)),
        import('@nestjs/swagger').then(swagger => this.heavyLibraries.set('swagger', swagger)),
      ]);
    }

    if (environment === 'production') {
      // Production-spezifische Module
      await Promise.all([
        import('compression').then(compression => this.heavyLibraries.set('compression', compression)),
        import('helmet').then(helmet => this.heavyLibraries.set('helmet', helmet)),
      ]);
    }
  }

  // Tree-shaking freundliche Utilities
  async getOptimizedUtilities(): Promise<{
    pick: Function;
    omit: Function;
    formatDate: Function;
  }> {
    // Nur spezifische Funktionen importieren, nicht die ganze Bibliothek
    const [pick, omit, formatDate] = await Promise.all([
      import('lodash/pick').then(m => m.default),
      import('lodash/omit').then(m => m.default),
      import('date-fns/format').then(m => m.default),
    ]);

    return { pick, omit, formatDate };
  }

  // Memory-optimiertes Module Loading
  async loadWithMemoryCheck(moduleName: string): Promise<any> {
    const beforeMemory = process.memoryUsage();
    
    const module = await import(moduleName);
    
    const afterMemory = process.memoryUsage();
    const memoryIncrease = afterMemory.heapUsed - beforeMemory.heapUsed;
    
    console.log(`Module ${moduleName} loaded, memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(2)} MB`);
    
    return module;
  }

  // Cleanup nicht genutzter Module
  cleanupUnusedModules(): void {
    const unusedThreshold = 5 * 60 * 1000; // 5 Minuten
    const now = Date.now();

    for (const [key, value] of this.heavyLibraries.entries()) {
      if (value.lastUsed && (now - value.lastUsed) > unusedThreshold) {
        console.log(`Cleaning up unused module: ${key}`);
        this.heavyLibraries.delete(key);
      }
    }
  }

  // Module Usage Tracking
  markModuleUsed(moduleName: string): void {
    const module = this.heavyLibraries.get(moduleName);
    if (module) {
      module.lastUsed = Date.now();
    }
  }
}

// Optimierte Service mit Lazy Loading
@Injectable()
export class OptimizedImageService {
  constructor(private lazyLoader: LazyLoadingService) {}

  async processImage(imageBuffer: Buffer): Promise<Buffer> {
    const sharp = await this.lazyLoader.getImageProcessingLibrary();
    this.lazyLoader.markModuleUsed('sharp');

    return await sharp.default(imageBuffer)
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toBuffer();
  }

  async generatePdf(data: any): Promise<Buffer> {
    const pdfLib = await this.lazyLoader.getPdfLibrary();
    this.lazyLoader.markModuleUsed('pdf-lib');

    const pdfDoc = await pdfLib.PDFDocument.create();
    // PDF-Generierung...
    
    return await pdfDoc.save();
  }
}

21.6.2 Webpack Bundle Analysis

import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';

@Injectable()
export class BundleAnalysisService {
  // Bundle-Größen analysieren
  async analyzeBundleSize(): Promise<{
    totalSize: number;
    moduleBreakdown: Array<{
      name: string;
      size: number;
      percentage: number;
    }>;
    recommendations: string[];
  }> {
    const packagePath = path.join(process.cwd(), 'package.json');
    const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
    
    const dependencies = {
      ...packageJson.dependencies,
      ...packageJson.devDependencies,
    };

    const moduleBreakdown = await this.getModuleSizes(dependencies);
    const totalSize = moduleBreakdown.reduce((sum, mod) => sum + mod.size, 0);

    const recommendations = this.generateOptimizationRecommendations(moduleBreakdown);

    return {
      totalSize,
      moduleBreakdown: moduleBreakdown.map(mod => ({
        ...mod,
        percentage: (mod.size / totalSize) * 100,
      })),
      recommendations,
    };
  }

  private async getModuleSizes(dependencies: Record<string, string>): Promise<Array<{
    name: string;
    size: number;
  }>> {
    const moduleSizes: Array<{ name: string; size: number }> = [];

    for (const [moduleName] of Object.entries(dependencies)) {
      try {
        const moduleSize = await this.calculateModuleSize(moduleName);
        moduleSizes.push({ name: moduleName, size: moduleSize });
      } catch (error) {
        console.warn(`Could not calculate size for ${moduleName}`);
      }
    }

    return moduleSizes.sort((a, b) => b.size - a.size);
  }

  private async calculateModuleSize(moduleName: string): Promise<number> {
    const modulePath = path.join(process.cwd(), 'node_modules', moduleName);
    
    if (!fs.existsSync(modulePath)) {
      return 0;
    }

    return this.getDirectorySize(modulePath);
  }

  private getDirectorySize(dirPath: string): number {
    let totalSize = 0;

    const items = fs.readdirSync(dirPath);
    
    for (const item of items) {
      const itemPath = path.join(dirPath, item);
      const stats = fs.statSync(itemPath);

      if (stats.isDirectory()) {
        totalSize += this.getDirectorySize(itemPath);
      } else {
        totalSize += stats.size;
      }
    }

    return totalSize;
  }

  private generateOptimizationRecommendations(modules: Array<{ name: string; size: number }>): string[] {
    const recommendations: string[] = [];
    
    // Große Module identifizieren
    const largeModules = modules.filter(mod => mod.size > 5 * 1024 * 1024); // > 5MB
    
    if (largeModules.length > 0) {
      recommendations.push(
        `Consider lazy loading these large modules: ${largeModules.map(m => m.name).join(', ')}`
      );
    }

    // Potentielle Alternativen vorschlagen
    const alternatives: Record<string, string> = {
      'moment': 'date-fns (smaller and tree-shakeable)',
      'lodash': 'individual lodash functions or native JS methods',
      'axios': 'native fetch API',
      'express': 'fastify (lighter alternative)',
    };

    for (const module of modules) {
      if (alternatives[module.name]) {
        recommendations.push(`Consider replacing ${module.name} with ${alternatives[module.name]}`);
      }
    }

    // Tree-shaking Empfehlungen
    recommendations.push('Use named imports instead of default imports for better tree-shaking');
    recommendations.push('Configure your bundler to eliminate dead code');
    
    return recommendations;
  }

  // Runtime Bundle Monitoring
  monitorRuntimeMemory(): {
    heapUsed: number;
    heapTotal: number;
    external: number;
    moduleCount: number;
  } {
    const memUsage = process.memoryUsage();
    
    // Geladene Module zählen
    const moduleCount = Object.keys(require.cache).length;

    return {
      heapUsed: memUsage.heapUsed,
      heapTotal: memUsage.heapTotal,
      external: memUsage.external,
      moduleCount,
    };
  }

  // Code Splitting Empfehlungen
  generateCodeSplittingStrategy(): {
    criticalModules: string[];
    lazyLoadableModules: string[];
    vendorModules: string[];
  } {
    return {
      criticalModules: [
        '@nestjs/core',
        '@nestjs/common',
        'express',
        'reflect-metadata',
      ],
      lazyLoadableModules: [
        'sharp',
        'pdf-lib',
        '@nestjs/swagger',
        'nodemailer',
      ],
      vendorModules: [
        'lodash',
        'date-fns',
        'axios',
        'typeorm',
      ],
    };
  }
}

21.7 Load Testing Strategien

Load Testing ist essentiell, um die Performance-Grenzen der Anwendung zu verstehen und zu optimieren.

21.7.1 Automatisiertes Load Testing

import { Injectable } from '@nestjs/common';
import axios from 'axios';

interface LoadTestConfig {
  baseUrl: string;
  endpoints: Array<{
    path: string;
    method: 'GET' | 'POST' | 'PUT' | 'DELETE';
    payload?: any;
    weight: number; // Gewichtung für Request-Verteilung
  }>;
  duration: number; // Testdauer in Sekunden
  concurrency: number; // Anzahl gleichzeitiger Verbindungen
  rampUp: number; // Ramp-up Zeit in Sekunden
}

interface LoadTestResult {
  totalRequests: number;
  successfulRequests: number;
  failedRequests: number;
  averageResponseTime: number;
  p95ResponseTime: number;
  p99ResponseTime: number;
  requestsPerSecond: number;
  errorRate: number;
  errors: Array<{
    endpoint: string;
    error: string;
    count: number;
  }>;
}

@Injectable()
export class LoadTestingService {
  async runLoadTest(config: LoadTestConfig): Promise<LoadTestResult> {
    console.log('Starting load test...', config);
    
    const startTime = Date.now();
    const endTime = startTime + (config.duration * 1000);
    const results: Array<{
      endpoint: string;
      responseTime: number;
      success: boolean;
      error?: string;
    }> = [];

    // Ramp-up Phase
    await this.rampUpPhase(config, results, startTime, endTime);
    
    // Haupt-Load-Phase
    await this.mainLoadPhase(config, results, endTime);

    return this.analyzeResults(results, config.duration);
  }

  private async rampUpPhase(
    config: LoadTestConfig,
    results: any[],
    startTime: number,
    endTime: number
  ): Promise<void> {
    const rampUpEnd = startTime + (config.rampUp * 1000);
    const concurrencyStep = config.concurrency / (config.rampUp * 10); // 10 Schritte pro Sekunde

    let currentConcurrency = 1;
    
    while (Date.now() < rampUpEnd && Date.now() < endTime) {
      const promises: Promise<void>[] = [];
      
      for (let i = 0; i < Math.floor(currentConcurrency); i++) {
        promises.push(this.executeRequest(config, results));
      }

      await Promise.all(promises);
      
      currentConcurrency = Math.min(
        currentConcurrency + concurrencyStep,
        config.concurrency
      );

      await this.delay(100); // 100ms zwischen Batches
    }
  }

  private async mainLoadPhase(
    config: LoadTestConfig,
    results: any[],
    endTime: number
  ): Promise<void> {
    while (Date.now() < endTime) {
      const promises: Promise<void>[] = [];
      
      for (let i = 0; i < config.concurrency; i++) {
        promises.push(this.executeRequest(config, results));
      }

      await Promise.all(promises);
      await this.delay(10); // Kurze Pause zwischen Batches
    }
  }

  private async executeRequest(
    config: LoadTestConfig,
    results: any[]
  ): Promise<void> {
    const endpoint = this.selectEndpoint(config.endpoints);
    const startTime = performance.now();

    try {
      await axios({
        method: endpoint.method,
        url: `${config.baseUrl}${endpoint.path}`,
        data: endpoint.payload,
        timeout: 30000,
      });

      const responseTime = performance.now() - startTime;
      results.push({
        endpoint: endpoint.path,
        responseTime,
        success: true,
      });
    } catch (error) {
      const responseTime = performance.now() - startTime;
      results.push({
        endpoint: endpoint.path,
        responseTime,
        success: false,
        error: error.message,
      });
    }
  }

  private selectEndpoint(endpoints: LoadTestConfig['endpoints']): LoadTestConfig['endpoints'][0] {
    // Gewichtete Zufallsauswahl
    const totalWeight = endpoints.reduce((sum, ep) => sum + ep.weight, 0);
    let random = Math.random() * totalWeight;

    for (const endpoint of endpoints) {
      random -= endpoint.weight;
      if (random <= 0) {
        return endpoint;
      }
    }

    return endpoints[0]; // Fallback
  }

  private analyzeResults(results: any[], duration: number): LoadTestResult {
    const totalRequests = results.length;
    const successfulRequests = results.filter(r => r.success).length;
    const failedRequests = totalRequests - successfulRequests;

    const responseTimes = results
      .filter(r => r.success)
      .map(r => r.responseTime)
      .sort((a, b) => a - b);

    const averageResponseTime = responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length;
    const p95ResponseTime = responseTimes[Math.floor(responseTimes.length * 0.95)];
    const p99ResponseTime = responseTimes[Math.floor(responseTimes.length * 0.99)];

    const requestsPerSecond = totalRequests / duration;
    const errorRate = (failedRequests / totalRequests) * 100;

    // Fehler gruppieren
    const errorGroups = new Map<string, { endpoint: string; error: string; count: number }>();
    
    results.filter(r => !r.success).forEach(r => {
      const key = `${r.endpoint}:${r.error}`;
      const existing = errorGroups.get(key);
      
      if (existing) {
        existing.count++;
      } else {
        errorGroups.set(key, {
          endpoint: r.endpoint,
          error: r.error,
          count: 1,
        });
      }
    });

    return {
      totalRequests,
      successfulRequests,
      failedRequests,
      averageResponseTime: averageResponseTime || 0,
      p95ResponseTime: p95ResponseTime || 0,
      p99ResponseTime: p99ResponseTime || 0,
      requestsPerSecond,
      errorRate,
      errors: Array.from(errorGroups.values()),
    };
  }

  // Stress Testing - Grenzen finden
  async runStressTest(baseConfig: LoadTestConfig): Promise<{
    maxConcurrency: number;
    breakingPoint: {
      concurrency: number;
      errorRate: number;
      avgResponseTime: number;
    };
    results: LoadTestResult[];
  }> {
    const results: LoadTestResult[] = [];
    let currentConcurrency = 10;
    let maxConcurrency = 0;
    let breakingPoint: any = null;

    while (currentConcurrency <= 500) { // Max 500 concurrent users
      const testConfig = {
        ...baseConfig,
        concurrency: currentConcurrency,
        duration: 60, // 1 Minute pro Test
      };

      console.log(`Testing with ${currentConcurrency} concurrent users...`);
      
      const result = await this.runLoadTest(testConfig);
      results.push(result);

      // Breaking Point Detection
      if (result.errorRate > 5 || result.averageResponseTime > 5000) {
        breakingPoint = {
          concurrency: currentConcurrency,
          errorRate: result.errorRate,
          avgResponseTime: result.averageResponseTime,
        };
        break;
      }

      maxConcurrency = currentConcurrency;
      currentConcurrency = Math.floor(currentConcurrency * 1.5); // 50% Steigerung
      
      // Pause zwischen Tests
      await this.delay(30000); // 30 Sekunden Erholung
    }

    return {
      maxConcurrency,
      breakingPoint,
      results,
    };
  }

  // Spike Testing - Plötzliche Lastspitzen
  async runSpikeTest(config: LoadTestConfig): Promise<LoadTestResult> {
    console.log('Starting spike test...');
    
    const results: any[] = [];
    const testDuration = 300000; // 5 Minuten
    const spikeDuration = 30000; // 30 Sekunden Spike
    const normalConcurrency = Math.floor(config.concurrency * 0.3);
    const spikeConcurrency = config.concurrency;

    const startTime = Date.now();
    const spikeStart = startTime + 120000; // Spike nach 2 Minuten
    const spikeEnd = spikeStart + spikeDuration;
    const endTime = startTime + testDuration;

    while (Date.now() < endTime) {
      const currentTime = Date.now();
      let currentConcurrency = normalConcurrency;

      // Spike-Phase
      if (currentTime >= spikeStart && currentTime < spikeEnd) {
        currentConcurrency = spikeConcurrency;
        console.log('SPIKE PHASE: High load');
      }

      const promises: Promise<void>[] = [];
      for (let i = 0; i < currentConcurrency; i++) {
        promises.push(this.executeRequest(config, results));
      }

      await Promise.all(promises);
      await this.delay(100);
    }

    return this.analyzeResults(results, testDuration / 1000);
  }

  // Performance Regression Detection
  async detectPerformanceRegression(
    config: LoadTestConfig,
    baseline: LoadTestResult
  ): Promise<{
    hasRegression: boolean;
    regressions: Array<{
      metric: string;
      baseline: number;
      current: number;
      degradation: number;
    }>;
  }> {
    const currentResult = await this.runLoadTest(config);
    const regressions: any[] = [];
    
    const metrics = [
      {
        name: 'averageResponseTime',
        threshold: 0.2, // 20% Verschlechterung
        baseline: baseline.averageResponseTime,
        current: currentResult.averageResponseTime,
      },
      {
        name: 'p95ResponseTime',
        threshold: 0.25, // 25% Verschlechterung
        baseline: baseline.p95ResponseTime,
        current: currentResult.p95ResponseTime,
      },
      {
        name: 'requestsPerSecond',
        threshold: -0.15, // 15% Abfall
        baseline: baseline.requestsPerSecond,
        current: currentResult.requestsPerSecond,
      },
      {
        name: 'errorRate',
        threshold: 2, // 2% absolute Steigerung
        baseline: baseline.errorRate,
        current: currentResult.errorRate,
      },
    ];

    for (const metric of metrics) {
      let degradation: number;
      
      if (metric.name === 'errorRate') {
        degradation = metric.current - metric.baseline; // Absolute Differenz
      } else if (metric.name === 'requestsPerSecond') {
        degradation = (metric.current - metric.baseline) / metric.baseline; // Negative für Verschlechterung
      } else {
        degradation = (metric.current - metric.baseline) / metric.baseline; // Relative Differenz
      }

      if (
        (metric.name === 'errorRate' && degradation > metric.threshold) ||
        (metric.name === 'requestsPerSecond' && degradation < metric.threshold) ||
        (metric.name !== 'errorRate' && metric.name !== 'requestsPerSecond' && degradation > metric.threshold)
      ) {
        regressions.push({
          metric: metric.name,
          baseline: metric.baseline,
          current: metric.current,
          degradation: degradation * 100, // Als Prozent
        });
      }
    }

    return {
      hasRegression: regressions.length > 0,
      regressions,
    };
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Load Test Beispiel-Konfiguration
  getDefaultLoadTestConfig(): LoadTestConfig {
    return {
      baseUrl: 'http://localhost:3000',
      endpoints: [
        { path: '/api/users', method: 'GET', weight: 40 },
        { path: '/api/users/1', method: 'GET', weight: 30 },
        { path: '/api/products', method: 'GET', weight: 20 },
        { path: '/api/auth/login', method: 'POST', payload: { email: 'test@test.com', password: 'password' }, weight: 10 },
      ],
      duration: 300, // 5 Minuten
      concurrency: 50,
      rampUp: 30, // 30 Sekunden
    };
  }
}

Performance-Optimierung ist ein kontinuierlicher Prozess, der systematisches Monitoring, Profiling und iterative Verbesserungen erfordert. Die in diesem Kapitel vorgestellten Techniken bilden eine solide Grundlage für die Entwicklung hochperformanter NestJS-Anwendungen. Wichtig ist, immer zu messen bevor optimiert wird, und die Optimierungen regelmäßig zu validieren, um sicherzustellen, dass sie den gewünschten Effekt haben.