Projektvorstellung
- NewCodes ist ein Curated-Service für technische Unternehmensblogs
- Spring Boot + PostgreSQL-Architektur
- Implementierung einer Suchbegriff-Autovervollständigung: termbasierte Empfehlungen, Suche mit Zerlegung in Jamo, Initialkonsonanten-Suche, Empfehlungen für Unternehmensseiten
Entdeckung des Performance-Problems
- In der Tabelle
Term hatten sich 110.000 Datensätze angesammelt
- Die API-Antwortzeit stieg auf über 1000 ms
- Ziel: Antwort innerhalb von 100 ms
1. Versuch: Indizes hinzufügen (1000 ms → 700 ms)
- Erstellung eines Optimierungsindex für LIKE-Präfixsuchen mit
varchar_pattern_ops
- Indexerstellung mit der Option
CONCURRENTLY ohne Service-Unterbrechung
- Jeweils Indizes auf die Spalten term, decomposed_term und chosung angewendet
2. Versuch: LOWER-Funktionsindex (700 ms → 110 ms)
- Problem eines Full Scans durch die Verwendung der Funktion
LOWER() erkannt
- Erstellung eines funktionsbasierten Indexes (Functional Index)
- Neuaufbau des Indexes in der Form
LOWER(Spaltenname) varchar_pattern_ops
3. Versuch: JOIN → EXISTS (110 ms → 100 ms)
- INNER JOIN zwischen Corporation und Article war ein Performance-Flaschenhals
- Wechsel zu einer EXISTS-Subquery zur Reduzierung des Scan-Bereichs
- Optimierung darauf, nur die „Existenz von Daten“ zu prüfen
4. Versuch: Denormalisierung & Covering Index (100 ms → 90 ms)
- Hinzufügen der Spalte
total_frequency, um Aggregationsoperationen zu entfernen
- Ersetzung von GROUP BY- und SUM-Operationen durch vorab berechnete Werte
- Reduzierung der I/O-Anzahl durch einen Covering Index
- Einbeziehung von term und total_frequency in den Index mit der
INCLUDE-Klausel
5. Versuch: JDBC Template (90 ms → 80 ms)
- Entfernung des JPA/Hibernate-Overheads
- Direkte Ausführung von Queries mit JDBC Template
- Bei einfachen Leseabfragen ist das Überspringen der ORM-Schicht effektiv
Lösung des Problems mit Nginx Rate Limiting
- Ursprüngliche Einstellung: Begrenzung auf 2 Anfragen pro Sekunde, burst 10
- Fehlgeschlagene Anfragen durch 100-ms-Debouncing
- Verbesserung: Änderung auf 10 Anfragen pro Sekunde, burst 20
- Änderung des Statuscodes von 444 → 429
Verkleinerung der Antwortdatengröße
- Entfernen von JSON-Feldnamen, Umstellung auf arraybasierte Antworten
- Kennzeichnung des Typs per Zahl (0: Corporation, 1: Theme, 2: Term)
- Reduzierung der Netzwerkübertragungszeit
Parallele Verarbeitung mit CompletableFuture
- Gleichzeitige, voneinander unabhängige Ausführung der Abfragen für Corporation, Theme und Term
- Im Vergleich zur sequenziellen Ausführung fällt nur die maximale Antwortzeit an
- Hinzufügen von ExecutorService und Exception-Handling
Endergebnis
- Anfangs 1000 ms → final 80 ms (Entwicklungsserver), 40 ms (Produktivserver)
- Performance-Verbesserung von rund 90 % oder mehr
Wichtige Erkenntnisse
- Bedeutung einer klaren Problemdefinition und Richtungsfestlegung
- Balance zwischen dem Einsatz von KI und der Prüfung durch Entwickler
- Notwendigkeit von Design aus Sicht der Gesamtarchitektur
- Auswahl von Indextypen: einfache/zusammengesetzte/Covering Indizes
- Vorsicht vor der Deaktivierung von Indizes bei Funktionsverwendung
- Verständnis der internen Funktionsweise von JPA
- Analyse von Query-Ausführungsplänen mit EXPLAIN
Zukünftige Verbesserungsrichtungen
- Einsatz der Trie-Datenstruktur
- Caching häufig gesuchter Begriffe
- Nutzung eines CDN (bei globalem Service)
Noch keine Kommentare.