Performance Optimization

Master performance optimization techniques to build fast, responsive Qlik applications that scale efficiently.

Performance Optimization Overview

Performance optimization in Qlik applications involves efficient data loading, smart caching strategies, optimized queries, and proper resource management to deliver excellent user experiences.

Data Loading
Efficient data fetching and pagination strategies
Caching
Intelligent caching of objects and data
Query Optimization
Optimized hypercube and selection strategies
Monitoring
Performance monitoring and profiling

Data Loading Optimization

Smart Pagination Strategy

Implement efficient pagination for large datasets

typescript
class SmartPagination {
  private sessionObject: any;
  private pageSize: number = 100;
  private cache = new Map<number, any[]>();
  private totalRows: number = 0;
  private prefetchPages = 2; // Prefetch next 2 pages

  constructor(sessionObject: any, pageSize: number = 100) {
    this.sessionObject = sessionObject;
    this.pageSize = pageSize;
  }

  async initialize(): Promise<void> {
    const layout = await this.sessionObject.getLayout();
    this.totalRows = layout.qHyperCube.qSize.qcy;
    console.log(`Initialized pagination for ${this.totalRows} rows`);
  }

  async getPage(pageNumber: number): Promise<{
    data: any[];
    page: number;
    totalPages: number;
    totalRows: number;
    hasNext: boolean;
    hasPrevious: boolean;
  }> {
    // Check cache first
    if (this.cache.has(pageNumber)) {
      console.log(`πŸ“‹ Page ${pageNumber} loaded from cache`);
      return this.buildPageResponse(pageNumber, this.cache.get(pageNumber)!);
    }

    try {
      // Fetch current page
      const pageData = await this.fetchPageData(pageNumber);
      this.cache.set(pageNumber, pageData);

      // Prefetch nearby pages in background
      this.prefetchNearbyPages(pageNumber);

      console.log(`πŸ“„ Page ${pageNumber} loaded from server`);
      return this.buildPageResponse(pageNumber, pageData);

    } catch (error) {
      console.error(`Failed to load page ${pageNumber}:`, error);
      throw error;
    }
  }

  private async fetchPageData(pageNumber: number): Promise<any[]> {
    const startRow = pageNumber * this.pageSize;
    const layout = await this.sessionObject.getLayout();
    const hypercube = layout.qHyperCube;

    const dataPage = await this.sessionObject.getHyperCubeData('/qHyperCubeDef', [{
      qLeft: 0,
      qTop: startRow,
      qWidth: hypercube.qSize.qcx,
      qHeight: Math.min(this.pageSize, this.totalRows - startRow)
    }]);

    return dataPage[0].qMatrix;
  }

  private async prefetchNearbyPages(currentPage: number): Promise<void> {
    const totalPages = Math.ceil(this.totalRows / this.pageSize);
    const pagesToPrefetch: number[] = [];

    // Prefetch next pages
    for (let i = 1; i <= this.prefetchPages; i++) {
      const nextPage = currentPage + i;
      if (nextPage < totalPages && !this.cache.has(nextPage)) {
        pagesToPrefetch.push(nextPage);
      }
    }

    // Prefetch previous pages
    for (let i = 1; i <= this.prefetchPages; i++) {
      const prevPage = currentPage - i;
      if (prevPage >= 0 && !this.cache.has(prevPage)) {
        pagesToPrefetch.push(prevPage);
      }
    }

    // Fetch in background without blocking
    pagesToPrefetch.forEach(pageNum => {
      this.fetchPageData(pageNum)
        .then(data => {
          this.cache.set(pageNum, data);
          console.log(`πŸ”„ Prefetched page ${pageNum}`);
        })
        .catch(error => {
          console.warn(`Prefetch failed for page ${pageNum}:`, error);
        });
    });
  }

  private buildPageResponse(pageNumber: number, data: any[]): any {
    const totalPages = Math.ceil(this.totalRows / this.pageSize);
    
    return {
      data,
      page: pageNumber,
      totalPages,
      totalRows: this.totalRows,
      hasNext: pageNumber < totalPages - 1,
      hasPrevious: pageNumber > 0
    };
  }

  // Cache management
  clearCache(): void {
    this.cache.clear();
    console.log('πŸ—‘οΈ Pagination cache cleared');
  }

  getCacheStats(): any {
    return {
      cachedPages: this.cache.size,
      totalPages: Math.ceil(this.totalRows / this.pageSize),
      cacheHitRatio: this.cache.size / Math.ceil(this.totalRows / this.pageSize),
      memoryUsageEstimate: this.cache.size * this.pageSize * 8 // Rough estimate
    };
  }
}

// Usage
async function implementSmartPagination(app: any) {
  // Create large dataset hypercube
  const largeDataObject = await app.createSessionObject({
    qInfo: { qType: 'large-dataset' },
    qHyperCubeDef: {
      qDimensions: [
        { qDef: { qFieldDefs: ['Product'] } },
        { qDef: { qFieldDefs: ['Customer'] } },
        { qDef: { qFieldDefs: ['Date'] } }
      ],
      qMeasures: [
        { qDef: { qDef: 'Sum(Sales)' } },
        { qDef: { qDef: 'Count(OrderID)' } }
      ],
      qInitialDataFetch: [] // Don't fetch initially
    }
  });

  const pagination = new SmartPagination(largeDataObject, 50);
  await pagination.initialize();

  // Load first page
  const firstPage = await pagination.getPage(0);
  console.log('First page:', firstPage);

  // Navigation example
  const nextPage = await pagination.getPage(1);
  console.log('Next page loaded');

  // Check cache performance
  const cacheStats = pagination.getCacheStats();
  console.log('Cache stats:', cacheStats);
}

Caching Strategies

Multi-Level Caching

Implement sophisticated caching for optimal performance

typescript
class MultiLevelCache {
  private memoryCache = new Map<string, any>();
  private sessionCache = new Map<string, any>();
  private persistentCache = new Map<string, any>();
  private cacheMetrics = new Map<string, any>();

  constructor(
    private maxMemoryItems: number = 100,
    private maxSessionItems: number = 500,
    private maxPersistentItems: number = 1000
  ) {}

  // Get data with automatic cache level selection
  async get(key: string, fetchFunction?: () => Promise<any>): Promise<any> {
    const startTime = performance.now();
    let cacheLevel = 'miss';

    try {
      // Level 1: Memory cache (fastest)
      if (this.memoryCache.has(key)) {
        cacheLevel = 'memory';
        const cached = this.memoryCache.get(key);
        
        if (this.isValid(cached)) {
          this.updateMetrics(key, 'memory', performance.now() - startTime);
          return cached.data;
        } else {
          this.memoryCache.delete(key);
        }
      }

      // Level 2: Session cache
      if (this.sessionCache.has(key)) {
        cacheLevel = 'session';
        const cached = this.sessionCache.get(key);
        
        if (this.isValid(cached)) {
          // Promote to memory cache
          this.set(key, cached.data, 'memory', cached.ttl);
          this.updateMetrics(key, 'session', performance.now() - startTime);
          return cached.data;
        } else {
          this.sessionCache.delete(key);
        }
      }

      // Level 3: Persistent cache (localStorage)
      const persistentData = this.getPersistent(key);
      if (persistentData && this.isValid(persistentData)) {
        cacheLevel = 'persistent';
        
        // Promote to higher levels
        this.set(key, persistentData.data, 'session', persistentData.ttl);
        this.set(key, persistentData.data, 'memory', persistentData.ttl);
        
        this.updateMetrics(key, 'persistent', performance.now() - startTime);
        return persistentData.data;
      }

      // Cache miss - fetch data
      if (fetchFunction) {
        cacheLevel = 'miss';
        const data = await fetchFunction();
        
        // Store in all levels with appropriate TTL
        this.set(key, data, 'memory', 5 * 60 * 1000); // 5 minutes
        this.set(key, data, 'session', 30 * 60 * 1000); // 30 minutes
        this.set(key, data, 'persistent', 24 * 60 * 60 * 1000); // 24 hours
        
        this.updateMetrics(key, 'miss', performance.now() - startTime);
        return data;
      }

      return null;

    } finally {
      console.log(`🎯 Cache ${cacheLevel} for key '${key}' in ${(performance.now() - startTime).toFixed(2)}ms`);
    }
  }

  // Set data in specific cache level
  set(key: string, data: any, level: 'memory' | 'session' | 'persistent', ttl: number = 5 * 60 * 1000): void {
    const cacheEntry = {
      data,
      timestamp: Date.now(),
      ttl,
      expires: Date.now() + ttl
    };

    switch (level) {
      case 'memory':
        this.enforceLimit(this.memoryCache, this.maxMemoryItems);
        this.memoryCache.set(key, cacheEntry);
        break;
        
      case 'session':
        this.enforceLimit(this.sessionCache, this.maxSessionItems);
        this.sessionCache.set(key, cacheEntry);
        break;
        
      case 'persistent':
        this.setPersistent(key, cacheEntry);
        break;
    }
  }

  // Intelligent cache invalidation
  invalidate(pattern: string | RegExp): number {
    let invalidatedCount = 0;
    const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;

    // Invalidate across all levels
    [this.memoryCache, this.sessionCache].forEach(cache => {
      const keysToDelete = Array.from(cache.keys()).filter(key => regex.test(key));
      keysToDelete.forEach(key => {
        cache.delete(key);
        invalidatedCount++;
      });
    });

    // Invalidate persistent cache
    try {
      const persistentKeys = Object.keys(localStorage).filter(key => 
        key.startsWith('qlik-cache-') && regex.test(key.substring(11))
      );
      persistentKeys.forEach(key => {
        localStorage.removeItem(key);
        invalidatedCount++;
      });
    } catch (error) {
      console.warn('Failed to invalidate persistent cache:', error);
    }

    console.log(`πŸ—‘οΈ Invalidated ${invalidatedCount} cache entries matching pattern: ${pattern}`);
    return invalidatedCount;
  }

  // Cache warming for frequently accessed data
  async warmCache(warmingPlan: Array<{
    key: string;
    fetchFunction: () => Promise<any>;
    priority: 'high' | 'normal' | 'low';
    levels: ('memory' | 'session' | 'persistent')[];
  }>): Promise<void> {
    
    console.log(`πŸ”₯ Starting cache warming for ${warmingPlan.length} items`);

    // Sort by priority
    const sortedPlan = warmingPlan.sort((a, b) => {
      const priorities = { high: 3, normal: 2, low: 1 };
      return priorities[b.priority] - priorities[a.priority];
    });

    for (const item of sortedPlan) {
      try {
        const data = await item.fetchFunction();
        
        // Store in requested levels
        item.levels.forEach(level => {
          const ttl = this.getTTLForLevel(level);
          this.set(item.key, data, level, ttl);
        });

        console.log(`βœ… Warmed cache for '${item.key}' in levels: ${item.levels.join(', ')}`);

        // Small delay for low priority items
        if (item.priority === 'low') {
          await new Promise(resolve => setTimeout(resolve, 10));
        }

      } catch (error) {
        console.error(`❌ Cache warming failed for '${item.key}':`, error);
      }
    }

    console.log('🎯 Cache warming completed');
  }

  // Get comprehensive cache statistics
  getStatistics(): any {
    const memoryStats = this.getCacheStats(this.memoryCache, 'Memory');
    const sessionStats = this.getCacheStats(this.sessionCache, 'Session');
    const persistentStats = this.getPersistentStats();

    const hitRateStats = Array.from(this.cacheMetrics.entries()).reduce((acc, [key, metrics]) => {
      acc.totalRequests += metrics.requests;
      acc.totalHits += metrics.hits;
      return acc;
    }, { totalRequests: 0, totalHits: 0 });

    return {
      levels: {
        memory: memoryStats,
        session: sessionStats,
        persistent: persistentStats
      },
      hitRate: hitRateStats.totalRequests > 0 ? 
        (hitRateStats.totalHits / hitRateStats.totalRequests) * 100 : 0,
      totalRequests: hitRateStats.totalRequests,
      totalHits: hitRateStats.totalHits
    };
  }

  private isValid(cacheEntry: any): boolean {
    return cacheEntry && Date.now() < cacheEntry.expires;
  }

  private enforceLimit(cache: Map<string, any>, maxItems: number): void {
    while (cache.size >= maxItems) {
      const firstKey = cache.keys().next().value;
      cache.delete(firstKey);
    }
  }

  private getPersistent(key: string): any {
    try {
      const stored = localStorage.getItem(`qlik-cache-${key}`);
      return stored ? JSON.parse(stored) : null;
    } catch {
      return null;
    }
  }

  private setPersistent(key: string, cacheEntry: any): void {
    try {
      // Enforce persistent cache limit
      const keys = Object.keys(localStorage).filter(k => k.startsWith('qlik-cache-'));
      while (keys.length >= this.maxPersistentItems) {
        localStorage.removeItem(keys.shift()!);
      }

      localStorage.setItem(`qlik-cache-${key}`, JSON.stringify(cacheEntry));
    } catch (error) {
      console.warn(`Failed to set persistent cache for ${key}:`, error);
    }
  }

  private getTTLForLevel(level: string): number {
    switch (level) {
      case 'memory': return 5 * 60 * 1000; // 5 minutes
      case 'session': return 30 * 60 * 1000; // 30 minutes
      case 'persistent': return 24 * 60 * 60 * 1000; // 24 hours
      default: return 5 * 60 * 1000;
    }
  }

  private updateMetrics(key: string, level: string, time: number): void {
    if (!this.cacheMetrics.has(key)) {
      this.cacheMetrics.set(key, { requests: 0, hits: 0, avgTime: 0 });
    }

    const metrics = this.cacheMetrics.get(key);
    metrics.requests++;
    if (level !== 'miss') metrics.hits++;
    metrics.avgTime = (metrics.avgTime + time) / 2;
  }

  private getCacheStats(cache: Map<string, any>, name: string): any {
    const validEntries = Array.from(cache.values()).filter(entry => this.isValid(entry));
    
    return {
      name,
      size: cache.size,
      validEntries: validEntries.length,
      expiredEntries: cache.size - validEntries.length,
      utilizationPercent: (cache.size / this.getMaxForCache(name)) * 100
    };
  }

  private getMaxForCache(name: string): number {
    switch (name) {
      case 'Memory': return this.maxMemoryItems;
      case 'Session': return this.maxSessionItems;
      default: return 100;
    }
  }

  private getPersistentStats(): any {
    try {
      const keys = Object.keys(localStorage).filter(k => k.startsWith('qlik-cache-'));
      return {
        name: 'Persistent',
        size: keys.length,
        utilizationPercent: (keys.length / this.maxPersistentItems) * 100
      };
    } catch {
      return { name: 'Persistent', size: 0, utilizationPercent: 0 };
    }
  }
}

// Usage
async function implementAdvancedCaching(app: any) {
  const cache = new MultiLevelCache(50, 200, 500);

  // Warm frequently used data
  await cache.warmCache([
    {
      key: 'sales-summary',
      fetchFunction: async () => {
        const obj = await app.createSessionObject({
          qHyperCubeDef: {
            qMeasures: [{ qDef: { qDef: 'Sum(Sales)' } }],
            qInitialDataFetch: [{ qLeft: 0, qTop: 0, qWidth: 1, qHeight: 1 }]
          }
        });
        const layout = await obj.getLayout();
        return layout.qHyperCube.qDataPages[0].qMatrix[0][0];
      },
      priority: 'high',
      levels: ['memory', 'session', 'persistent']
    }
  ]);

  // Use cached data
  const salesData = await cache.get('sales-summary');
  console.log('Sales data from cache:', salesData);

  // Get cache performance stats
  const stats = cache.getStatistics();
  console.log('Cache performance:', stats);

  return cache;
}

Performance Monitoring

Performance Monitoring & Profiling

Monitor and analyze performance metrics in real-time

typescript
class PerformanceMonitor {
  private metrics = new Map<string, any>();
  private alerts: any[] = [];
  private isMonitoring = false;

  // Start comprehensive performance monitoring
  startMonitoring(): void {
    this.isMonitoring = true;
    console.log('πŸ“Š Performance monitoring started');

    // Monitor frame rate
    this.monitorFrameRate();
    
    // Monitor memory usage
    this.monitorMemoryUsage();
    
    // Monitor network performance
    this.monitorNetworkPerformance();
  }

  // Monitor Qlik operation performance
  async measureOperation<T>(
    operationName: string,
    operation: () => Promise<T>,
    thresholds?: { warning: number; critical: number }
  ): Promise<T> {
    const startTime = performance.now();
    const startMemory = this.getMemoryUsage();

    try {
      const result = await operation();
      const endTime = performance.now();
      const endMemory = this.getMemoryUsage();

      const metrics = {
        operationName,
        duration: endTime - startTime,
        memoryDelta: endMemory.usedJSHeapSize - startMemory.usedJSHeapSize,
        timestamp: new Date().toISOString(),
        status: 'success'
      };

      this.recordMetrics(operationName, metrics);

      // Check thresholds
      if (thresholds) {
        this.checkThresholds(operationName, metrics.duration, thresholds);
      }

      console.log(`⚑ ${operationName}: ${metrics.duration.toFixed(2)}ms, Memory: +${(metrics.memoryDelta / 1024 / 1024).toFixed(2)}MB`);
      
      return result;

    } catch (error) {
      const endTime = performance.now();
      const errorMetrics = {
        operationName,
        duration: endTime - startTime,
        timestamp: new Date().toISOString(),
        status: 'error',
        error: error.message
      };

      this.recordMetrics(operationName, errorMetrics);
      console.error(`❌ ${operationName} failed after ${(endTime - startTime).toFixed(2)}ms:`, error);
      
      throw error;
    }
  }

  // Get comprehensive performance report
  getPerformanceReport(): any {
    const report = {
      summary: this.getSummaryStats(),
      operations: this.getOperationStats(),
      alerts: this.alerts.slice(-10), // Last 10 alerts
      systemMetrics: this.getSystemMetrics(),
      recommendations: this.generateRecommendations()
    };

    return report;
  }

  private recordMetrics(operationName: string, metrics: any): void {
    if (!this.metrics.has(operationName)) {
      this.metrics.set(operationName, {
        calls: 0,
        totalDuration: 0,
        avgDuration: 0,
        minDuration: Infinity,
        maxDuration: 0,
        errors: 0,
        lastCall: null
      });
    }

    const operationMetrics = this.metrics.get(operationName);
    operationMetrics.calls++;
    operationMetrics.lastCall = metrics.timestamp;

    if (metrics.status === 'success') {
      operationMetrics.totalDuration += metrics.duration;
      operationMetrics.avgDuration = operationMetrics.totalDuration / operationMetrics.calls;
      operationMetrics.minDuration = Math.min(operationMetrics.minDuration, metrics.duration);
      operationMetrics.maxDuration = Math.max(operationMetrics.maxDuration, metrics.duration);
    } else {
      operationMetrics.errors++;
    }
  }

  private checkThresholds(operationName: string, duration: number, thresholds: any): void {
    if (duration > thresholds.critical) {
      this.addAlert('critical', `${operationName} took ${duration.toFixed(2)}ms (critical threshold: ${thresholds.critical}ms)`);
    } else if (duration > thresholds.warning) {
      this.addAlert('warning', `${operationName} took ${duration.toFixed(2)}ms (warning threshold: ${thresholds.warning}ms)`);
    }
  }

  private addAlert(level: string, message: string): void {
    const alert = {
      level,
      message,
      timestamp: new Date().toISOString()
    };

    this.alerts.push(alert);
    console.warn(`🚨 Performance Alert [${level.toUpperCase()}]: ${message}`);

    // Keep only last 50 alerts
    if (this.alerts.length > 50) {
      this.alerts.shift();
    }
  }

  private monitorFrameRate(): void {
    let lastFrame = performance.now();
    let frameCount = 0;
    let totalFrameTime = 0;

    const measureFrame = (currentFrame: number) => {
      if (!this.isMonitoring) return;

      const deltaTime = currentFrame - lastFrame;
      frameCount++;
      totalFrameTime += deltaTime;

      // Report every 60 frames
      if (frameCount === 60) {
        const avgFrameTime = totalFrameTime / frameCount;
        const fps = 1000 / avgFrameTime;

        if (fps < 30) {
          this.addAlert('warning', `Low frame rate detected: ${fps.toFixed(1)} FPS`);
        }

        frameCount = 0;
        totalFrameTime = 0;
      }

      lastFrame = currentFrame;
      requestAnimationFrame(measureFrame);
    };

    requestAnimationFrame(measureFrame);
  }

  private monitorMemoryUsage(): void {
    setInterval(() => {
      if (!this.isMonitoring) return;

      const memory = this.getMemoryUsage();
      const memoryUsageMB = memory.usedJSHeapSize / 1024 / 1024;

      if (memoryUsageMB > 100) { // 100MB threshold
        this.addAlert('warning', `High memory usage: ${memoryUsageMB.toFixed(2)}MB`);
      }

      if (memoryUsageMB > 200) { // 200MB threshold
        this.addAlert('critical', `Critical memory usage: ${memoryUsageMB.toFixed(2)}MB`);
      }
    }, 10000); // Check every 10 seconds
  }

  private monitorNetworkPerformance(): void {
    // Monitor fetch performance
    const originalFetch = window.fetch;
    window.fetch = async (...args) => {
      const startTime = performance.now();
      
      try {
        const response = await originalFetch(...args);
        const endTime = performance.now();
        const duration = endTime - startTime;

        if (duration > 5000) { // 5 second threshold
          this.addAlert('warning', `Slow network request: ${duration.toFixed(2)}ms to ${args[0]}`);
        }

        return response;
      } catch (error) {
        const endTime = performance.now();
        this.addAlert('error', `Network request failed after ${(endTime - startTime).toFixed(2)}ms: ${error.message}`);
        throw error;
      }
    };
  }

  private getMemoryUsage(): any {
    if ('memory' in performance) {
      return (performance as any).memory;
    }
    return { usedJSHeapSize: 0, totalJSHeapSize: 0 };
  }

  private getSummaryStats(): any {
    const totalCalls = Array.from(this.metrics.values()).reduce((sum, m) => sum + m.calls, 0);
    const totalErrors = Array.from(this.metrics.values()).reduce((sum, m) => sum + m.errors, 0);
    
    return {
      totalOperations: this.metrics.size,
      totalCalls,
      totalErrors,
      errorRate: totalCalls > 0 ? (totalErrors / totalCalls) * 100 : 0,
      monitoringDuration: this.isMonitoring ? 'Active' : 'Stopped'
    };
  }

  private getOperationStats(): any[] {
    return Array.from(this.metrics.entries()).map(([name, stats]) => ({
      operation: name,
      ...stats,
      successRate: stats.calls > 0 ? ((stats.calls - stats.errors) / stats.calls) * 100 : 0
    }));
  }

  private getSystemMetrics(): any {
    const memory = this.getMemoryUsage();
    
    return {
      memoryUsage: {
        used: Math.round(memory.usedJSHeapSize / 1024 / 1024),
        total: Math.round(memory.totalJSHeapSize / 1024 / 1024),
        limit: Math.round((memory as any).jsHeapSizeLimit / 1024 / 1024)
      },
      connection: navigator.onLine ? 'online' : 'offline',
      userAgent: navigator.userAgent
    };
  }

  private generateRecommendations(): string[] {
    const recommendations: string[] = [];
    const operationStats = this.getOperationStats();

    // Check for slow operations
    operationStats.forEach(stat => {
      if (stat.avgDuration > 2000) {
        recommendations.push(`Consider optimizing '${stat.operation}' - avg duration: ${stat.avgDuration.toFixed(2)}ms`);
      }
    });

    // Check error rates
    operationStats.forEach(stat => {
      if (stat.successRate < 95 && stat.calls > 10) {
        recommendations.push(`High error rate for '${stat.operation}': ${(100 - stat.successRate).toFixed(1)}%`);
      }
    });

    // Memory recommendations
    const memory = this.getMemoryUsage();
    const memoryUsageMB = memory.usedJSHeapSize / 1024 / 1024;
    if (memoryUsageMB > 50) {
      recommendations.push('Consider implementing more aggressive caching or object cleanup');
    }

    return recommendations.length > 0 ? recommendations : ['Performance looks good! πŸŽ‰'];
  }

  stopMonitoring(): void {
    this.isMonitoring = false;
    console.log('πŸ“Š Performance monitoring stopped');
  }
}

// Usage
async function implementPerformanceMonitoring(app: any) {
  const monitor = new PerformanceMonitor();
  monitor.startMonitoring();

  // Monitor Qlik operations
  const salesData = await monitor.measureOperation(
    'getSalesData',
    async () => {
      const obj = await app.createSessionObject({
        qHyperCubeDef: {
          qDimensions: [{ qDef: { qFieldDefs: ['Product'] } }],
          qMeasures: [{ qDef: { qDef: 'Sum(Sales)' } }],
          qInitialDataFetch: [{ qLeft: 0, qTop: 0, qWidth: 2, qHeight: 100 }]
        }
      });
      return await obj.getLayout();
    },
    { warning: 1000, critical: 3000 }
  );

  // Get performance report
  const report = monitor.getPerformanceReport();
  console.log('Performance Report:', report);

  return monitor;
}

πŸš€ Performance Best Practices

Data Loading: Use pagination and lazy loading for large datasets
Caching: Implement multi-level caching with appropriate TTL values
Object Management: Clean up session objects when no longer needed
Query Optimization: Limit initial data fetch sizes and use efficient expressions
Memory Management: Monitor memory usage and implement cleanup strategies
Network Optimization: Batch requests and use compression when possible
UI Performance: Debounce user interactions and use virtual scrolling
Monitoring: Continuously monitor performance metrics and set up alerts