46 Performance Tuning

Performance Tuning in OpenSearch ist vergleichbar mit dem Dirigieren eines Orchesters - alle Komponenten müssen harmonisch zusammenarbeiten, um optimale Ergebnisse zu erzielen. In diesem Kapitel erkunden wir, wie Performance-Engpässe identifiziert, analysiert und behoben werden können, während wir die Prinzipien hinter jeder Optimierungstechnik verstehen lernen.

46.1 OpenSearch Performance verstehen

Bevor wir in spezifische Optimierungen eintauchen, müssen wir verstehen, wie OpenSearch Operationen verarbeitet. Jede Such- oder Indexierungsanfrage durchläuft mehrere Phasen, jede mit eigenen Performance-Charakteristiken und Optimierungsmöglichkeiten.

46.1.1 Anatomie der Suchperformance

Wenn Sie eine Suchanfrage stellen, verarbeitet OpenSearch diese in mehreren Phasen:

  1. Query-Phase: OpenSearch verteilt die Anfrage an relevante Shards
  2. Fetch-Phase: Ruft die eigentlichen Dokumente ab
  3. Merge-Phase: Kombiniert und sortiert die Ergebnisse

Das Verständnis dieser Phasen hilft uns, unsere Optimierungen effektiv zu gestalten. Sehen wir uns an, wie jede Phase optimiert werden kann.

46.2 Query-Optimierungstechniken

46.2.1 Query-Struktur optimieren

Die Art und Weise, wie wir unsere Queries strukturieren, hat erheblichen Einfluss auf die Performance. Betrachten Sie diese zwei Ansätze für die gleiche Suche:

// Weniger effiziente Query
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "description": "wasserdichte kamera"
          }
        },
        {
          "range": {
            "price": {
              "gte": 100,
              "lte": 500
            }
          }
        }
      ]
    }
  }
}

// Effizientere Query
GET /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "price": {
              "gte": 100,
              "lte": 500
            }
          }
        }
      ],
      "should": [
        {
          "match": {
            "description": {
              "query": "wasserdichte kamera",
              "operator": "and",
              "minimum_should_match": "2"
            }
          }
        }
      ]
    }
  }
}

Die zweite Query ist effizienter, weil sie:

46.2.2 Query-Caching-Strategien

OpenSearch bietet verschiedene Caching-Mechanismen, die wir optimieren können:

PUT /products/_settings
{
  "index": {
    "queries": {
      "cache": {
        "enabled": true
      }
    },
    "requests": {
      "cache": {
        "enable": true
      }
    }
  }
}

Für häufig verwendete Aggregationen können wir Request-Caching implementieren:

GET /products/_search?request_cache=true
{
  "size": 0,
  "aggs": {
    "beliebte_kategorien": {
      "terms": {
        "field": "category",
        "size": 10
      }
    }
  }
}

46.3 Index-Optimierung

46.3.1 Index-Einstellungen optimieren

Index-Einstellungen haben einen tiefgreifenden Einfluss auf die Performance. Hier ist eine für suchintensive Workloads optimierte Konfiguration:

PUT /products
{
  "settings": {
    "index": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "30s",
      "translog": {
        "durability": "async",
        "sync_interval": "5s",
        "flush_threshold_size": "512mb"
      },
      "merge": {
        "scheduler": {
          "max_thread_count": 1,
          "max_merge_count": 2
        },
        "policy": {
          "floor_segment": "2mb",
          "max_merged_segment": "5gb",
          "segments_per_tier": 10
        }
      }
    }
  }
}

Verstehen wir jede Einstellung:

46.3.2 Field Data Optimierung

Field Data kann erheblichen Speicher verbrauchen. So optimieren Sie es:

PUT /products/_mapping
{
  "properties": {
    "product_name": {
      "type": "text",
      "fielddata": false,
      "fields": {
        "keyword": {
          "type": "keyword",
          "eager_global_ordinals": true
        }
      }
    },
    "description": {
      "type": "text",
      "index_options": "freqs"
    }
  }
}

Diese Konfiguration:

46.4 Memory Management

46.4.1 JVM Heap Optimierung

Der JVM Heap ist entscheidend für die Performance. Hier ist eine optimale Konfiguration:

# jvm.options
-Xms31g
-Xmx31g
-XX:+UseG1GC
-XX:G1ReservePercent=25
-XX:InitiatingHeapOccupancyPercent=75
-XX:+ParallelRefProcEnabled
-XX:MaxGCPauseMillis=200
-XX:+DisableExplicitGC

Diese Einstellungen:

46.4.2 Circuit Breaker Konfiguration

Circuit Breaker verhindern Out-of-Memory-Fehler:

PUT /_cluster/settings
{
  "persistent": {
    "indices.breaker.total.limit": "70%",
    "indices.breaker.request.limit": "60%",
    "indices.breaker.fielddata.limit": "40%"
  }
}

46.5 Bulk Processing Optimierung

46.5.1 Bulk-Indexierung optimieren

Für optimale Bulk-Indexierungs-Performance:

PUT /_cluster/settings
{
  "persistent": {
    "indices.memory.index_buffer_size": "30%"
  }
}

Hier ein Beispiel für optimierten Bulk-Indexierungs-Code:

def optimize_bulk_requests(documents, batch_size=5000):
    """
    Optimiert die Bulk-Indexierung von Dokumenten mit Fehlerbehandlung und Wiederholungsversuchen.
    
    Args:
        documents: Iterator der zu indexierenden Dokumente
        batch_size: Anzahl der Dokumente pro Bulk-Request
    """
    actions = []
    total_docs = 0
    
    for doc in documents:
        action = {
            "_index": "products",
            "_id": doc["id"],
            "_source": doc
        }
        actions.append(action)
        total_docs += 1
        
        if len(actions) >= batch_size:
            response = bulk_index_with_retries(actions)
            actions = []
            
            print(f"{total_docs} Dokumente indexiert")
    
    if actions:
        bulk_index_with_retries(actions)

def bulk_index_with_retries(actions, max_retries=3):
    """
    Führt Bulk-Indexierung mit Wiederholungslogik für fehlgeschlagene Dokumente durch.
    
    Args:
        actions: Liste der auszuführenden Bulk-Aktionen
        max_retries: Maximale Anzahl von Wiederholungsversuchen
    """
    retries = 0
    success = False
    
    while not success and retries < max_retries:
        try:
            response = client.bulk(body=actions)
            
            if response["errors"]:
                failed_docs = []
                for item in response["items"]:
                    if "error" in item["index"]:
                        failed_docs.append(actions[item["index"]["_id"]])
                
                if retries == max_retries - 1:
                    log_failed_documents(failed_docs)
                else:
                    actions = failed_docs
                    retries += 1
                    continue
            
            success = True
            
        except Exception as e:
            if retries == max_retries - 1:
                raise e
            retries += 1
            time.sleep(2 ** retries)
    
    return success

46.6 Monitoring und Profiling

46.6.1 Performance Monitoring

Richten Sie ein umfassendes Monitoring ein:

PUT _cluster/settings
{
  "persistent": {
    "opensearch.performance_analyzer.enabled": true,
    "opensearch.performance_analyzer.metrics": [
      "CPU_Utilization",
      "IO_TotThroughput",
      "IO_TotalSyscallRate",
      "Thread_Blocked_Time",
      "GC_Collection_Time"
    ]
  }
}

46.6.2 Query Profiling

Verwenden Sie die Profile-API zur Analyse der Query-Performance:

GET /products/_search
{
  "profile": true,
  "query": {
    "match": {
      "description": "wasserdichte kamera"
    }
  }
}

46.7 Fortgeschrittene Optimierungstechniken

46.7.1 Such-Performance-Optimierung

Für komplexe Suchszenarien:

PUT /products/_settings
{
  "index": {
    "max_result_window": 10000,
    "max_rescore_window": 500,
    "search": {
      "slowlog": {
        "threshold": {
          "query": {
            "warn": "10s",
            "info": "5s",
            "debug": "2s"
          },
          "fetch": {
            "warn": "1s",
            "info": "800ms",
            "debug": "500ms"
          }
        }
      }
    }
  }
}

46.7.2 Caching-Strategie

Implementieren Sie eine umfassende Caching-Strategie:

PUT /_cluster/settings
{
  "persistent": {
    "indices.queries.cache.size": "10%",
    "indices.fielddata.cache.size": "20%",
    "indices.request.cache.size": "2%"
  }
}

46.8 Performance-Test-Framework

Hier ist ein Framework für Performance-Tests:

class PerformanceTest:
    def __init__(self, client, index_name):
        self.client = client
        self.index_name = index_name
        self.results = []
    
    def measure_query_performance(self, query, iterations=100):
        """
        Misst die Query-Performance über mehrere Iterationen.
        
        Args:
            query: Zu testende Query
            iterations: Anzahl der Testiterationen
        """
        timings = []
        
        for i in range(iterations):
            start_time = time.time()
            response = self.client.search(
                index=self.index_name,
                body=query
            )
            end_time = time.time()
            
            timings.append({
                'took': response['took'],
                'total_time': (end_time - start_time) * 1000,
                'hits': response['hits']['total']['value']
            })
        
        return self.analyze_results(timings)
    
    def analyze_results(self, timings):
        """
        Analysiert Testergebnisse und liefert statistische Einblicke.
        
        Args:
            timings: Liste von Zeitmessungen
        """
        df = pd.DataFrame(timings)
        
        return {
            'mean_query_time': df['took'].mean(),
            'p95_query_time': df['took'].quantile(0.95),
            'max_query_time': df['took'].max(),
            'min_query_time': df['took'].min(),
            'std_dev': df['took'].std()
        }

46.9 Best Practices und Richtlinien

Bei der Optimierung der OpenSearch-Performance sollten Sie diese Prinzipien befolgen:

Indexierungs-Optimierung:

Query-Optimierung:

Ressourcen-Management: