Zum Hauptinhalt springen

Leistung und Optimierung in TypeORM

Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

1. Einführung in die Leistungsoptimierung

  • In Anwendungen, die ORM wie TypeORM verwenden, ist Leistungsoptimierung entscheidend, um einen reibungslosen Systembetrieb zu gewährleisten, Latenzzeiten zu minimieren und Ressourcen effizient zu nutzen.

  • Häufige Herausforderungen bei der ORM-Nutzung sind unnötige Datenabfragen, N+1-Query-Probleme sowie die Nichtnutzung von Optimierungswerkzeugen wie Indizierung oder Caching.

  • Die Hauptziele der Optimierung umfassen:

    • Reduzierung der an die Datenbank gesendeten SQL-Abfragen.
    • Optimierung komplexer Abfragen für schnellere Ausführung.
    • Nutzung von Caching und Indizes zur Beschleunigung des Datenabrufs.
    • Effizienter Datenabruf durch geeignete Lademethoden (Lazy vs. Eager Loading).

2. Effiziente Nutzung des Query Builders

2.1. Vermeidung des N+1-Query-Problems

  • Das N+1-Query-Problem tritt auf, wenn das System zu viele Unterabfragen für jede abgerufene Datenzeile ausführt.

  • Zur Vermeidung können leftJoinAndSelect oder innerJoinAndSelect verwendet werden, um Tabellen in einer einzigen Abfrage zu kombinieren statt mehrere Abfragen auszuführen.

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.getMany()
  • Hier ermöglicht leftJoinAndSelect den Abruf aller Benutzerbeiträge in einer einzigen Abfrage statt vieler kleiner Abfragen.

2.2. getRawMany() bei reinen Rohdaten nutzen

  • Falls keine vollständigen Objekte benötigt werden, kann getRawMany() verwendet werden, um Rohdaten abzurufen und die Verarbeitung überflüssiger Informationen durch TypeORM zu vermeiden.
const rawPosts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.select("post.title, post.createdAt")
.getRawMany()

2.3. Feldbegrenzung mit select

  • Zur Optimierung des Speicherverbrauchs und Reduzierung unnötiger Daten sollten nur benötigte Felder mittels select ausgewählt werden.
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.name", "user.email"])
.getMany()

3. Nutzung von Indizes

  • Indizes beschleunigen die Abfrageperformance in der Datenbank, indem sie die gescannte Datenmenge reduzieren. TypeORM unterstützt die Indexerstellung auf Tabellenspalten mittels @Index-Decorator.

3.1. Indexerstellung

  • Indizes können direkt in Entitäten über den @Index-Decorator angelegt werden.
import { Entity, Column, Index } from "typeorm"

@Entity()
@Index(["firstName", "lastName"]) // Composite index
export class User {
@Column()
firstName: string

@Column()
lastName: string
}

3.2. Eindeutiger Index

  • Eindeutige Indizes können erstellt werden, um doppelte Werte in einer Spalte zu verhindern.
@Index(["email"], { unique: true })

4. Lazy Loading und Eager Loading

TypeORM bietet zwei Hauptmethoden zum Laden von Datenbeziehungen: Lazy Loading und Eager Loading. Jede hat unterschiedliche Auswirkungen auf die Anwendungsleistung.

4.1. Lazy Loading

  • Lazy Loading lädt Beziehungsdaten nur bei Bedarf nach und reduziert so die Datenbanklast, wenn nicht immer alle verknüpften Daten benötigt werden.
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { lazy: true })
posts: Promise<Post[]>
}
  • Bei Bedarf kann die Daten einfach durch Aufruf abgerufen werden.
const user = await userRepository.findOne(userId)
const posts = await user.posts
  • Vorteile:

    • Ressourceneffizienz: Lädt nur benötigte Daten bei tatsächlichem Bedarf, reduziert Abfragekosten und Speicherverbrauch.
    • Ideal für selektive Datennutzung: Geeignet für Szenarien, in denen nicht alle verknüpften Daten benötigt werden.
  • Nachteile:

    • Erhöhte Abfragekomplexität: Jeder Zugriff auf Beziehungsdaten löst eine zusätzliche Datenbankabfrage aus, was bei falscher Handhabung die Latenz erhöhen kann.
    • Schwierige Nachverfolgung: Kann bei unvorsichtiger Nutzung zum N+1-Query-Problem führen.

4.2. Eager Loading

  • Eager Loading ruft automatisch alle verknüpften Daten bei Ausführung der Hauptabfrage ab. Dies ist praktisch, kann jedoch bei zu vielen komplexen Beziehungen zu Performance-Problemen führen.
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { eager: true })
posts: Post[]
}
  • In diesem Fall werden Beiträge sofort geladen, sobald die Benutzerdaten abgerufen werden.

  • Vorteile:

    • Lädt Beziehungsdaten automatisch nach und erleichtert den Zugriff ohne zusätzliche Abfragen.
    • Vermeidet das N+1-Query-Problem: Da alle Daten in einer Abfrage abgerufen werden, entfällt das Risiko unnötiger Mehrfachabfragen.
  • Nachteile:

    • Das Abrufen aller verknüpften Daten auf einmal kann zu großen Abfragen führen, selbst wenn nicht alle Daten benötigt werden.
    • Ungeeignet für Szenarien, in denen nur ein Teil der Beziehungsdaten benötigt wird, da dies zu ineffizienter Datennutzung führen kann.
  • Weitere Details und Beispiele zur Konfiguration von Lazy- und Eager-Beziehungen finden Sie in der offiziellen TypeORM-Dokumentation: Eager und Lazy Relations

5. Erweiterte Optimierung

5.1. Verwendung von Query Hints

  • Query Hints sind Anweisungen, die zusammen mit SQL-Abfragen gesendet werden und der Datenbank helfen, effizientere Ausführungsstrategien zu wählen.

  • Unterschiedliche RDBMS-Systeme unterstützen verschiedene Hint-Typen, etwa Empfehlungen zur Indexnutzung oder Wahl geeigneter JOIN-Typen.

await dataSource.query(`
SELECT /*+ MAX_EXECUTION_TIME(1000) */ *
FROM user
WHERE email = 'example@example.com'
`)
  • Im obigen Beispiel weist MAX_EXECUTION_TIME(1000) MySQL an, die Abfrage nach mehr als 1 Sekunde abzubrechen.

5.2. Paginierung

  • Paginierung ist eine entscheidende Technik zur Leistungsverbesserung beim Abruf großer Datenmengen. Statt aller Daten auf einmal werden diese in kleinere Seiten unterteilt, was die Datenbanklast reduziert und den Speicherverbrauch optimiert.

  • In TypeORM können dafür limit und offset verwendet werden.

const users = await userRepository
.createQueryBuilder("user")
.limit(10) // Number of records to fetch per page
.offset(20) // Skip the first 20 records
.getMany()
  • Pagination verhindert das Abrufen großer Datenmengen auf einmal, minimiert Latenzzeiten und optimiert die Speichernutzung. Bei der Implementierung von Pagination sollten Pagination-Cursors für eine effizientere Handhabung dynamischer Daten in Betracht gezogen werden.

5.3. Caching

  • Caching ist eine Technik, bei der Abfrageergebnisse oder Daten vorübergehend gespeichert werden, um sie in zukünftigen Anfragen zu verwenden, ohne jedes Mal die Datenbank abfragen zu müssen.

  • TypeORM bietet integrierte Caching-Unterstützung, und Sie können anpassen, wie das Caching genutzt wird.

const users = await userRepository
.createQueryBuilder("user")
.cache(true) // Enable caching
.getMany()
  • Zusätzlich können Sie die Cache-Dauer konfigurieren oder externe Caching-Tools wie Redis für bessere Effizienz nutzen.
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
cache: {
type: "redis",
options: {
host: "localhost",
port: 6379
}
}
});