Vai al contenuto principale

Prestazioni e ottimizzazione in TypeORM

Traduzione Beta Non Ufficiale

Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →

1. Introduzione all'ottimizzazione delle prestazioni

  • Nelle applicazioni che utilizzano ORM come TypeORM, l'ottimizzazione delle prestazioni è cruciale per garantire che il sistema funzioni senza intoppi, minimizzi la latenza e utilizzi le risorse in modo efficiente.

  • Le sfide comuni quando si utilizza un ORM includono il recupero non necessario di dati, i problemi di query N+1 e il mancato utilizzo di strumenti di ottimizzazione come l'indicizzazione o la cache.

  • Gli obiettivi principali dell'ottimizzazione includono:

    • Ridurre il numero di query SQL inviate al database.
    • Ottimizzare query complesse per una esecuzione più rapida.
    • Utilizzare cache e indici per accelerare il recupero dei dati.
    • Garantire un recupero efficiente dei dati utilizzando metodi di caricamento appropriati (Lazy vs. Eager loading).

2. Uso efficiente del Query Builder

2.1. Evitare il problema delle query N+1

  • Il problema delle query N+1 si verifica quando il sistema esegue troppe sotto-query per ogni riga di dati recuperata.

  • Per evitarlo, puoi utilizzare leftJoinAndSelect o innerJoinAndSelect per combinare le tabelle in un'unica query invece di eseguire query multiple.

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.getMany()
  • In questo esempio, leftJoinAndSelect aiuta a recuperare tutti i post dell'utente in un'unica query anziché in molte piccole query.

2.2. Usare getRawMany() quando servono solo dati grezzi

  • Nei casi in cui non sono necessari oggetti completi, puoi usare getRawMany() per ottenere dati grezzi ed evitare che TypeORM elabori troppe informazioni.
const rawPosts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.select("post.title, post.createdAt")
.getRawMany()

2.3. Limitare i campi con select

  • Per ottimizzare l'utilizzo della memoria e ridurre dati non necessari, seleziona solo i campi richiesti utilizzando select.
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.name", "user.email"])
.getMany()

3. Utilizzo degli indici

  • Gli indici accelerano le prestazioni delle query nel database riducendo la quantità di dati scansionati. TypeORM supporta la creazione di indici sulle colonne delle tabelle utilizzando il decoratore @Index.

3.1. Creazione di un indice

  • Gli indici possono essere creati direttamente nelle entità utilizzando il decoratore @Index.
import { Entity, Column, Index } from "typeorm"

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

@Column()
lastName: string
}

3.2. Indice univoco

  • Puoi creare indici univoci per garantire l'assenza di valori duplicati in una colonna.
@Index(["email"], { unique: true })

4. Lazy loading e Eager Loading

TypeORM fornisce due metodi principali per caricare le relazioni dei dati: Lazy Loading e Eager Loading. Ognuno ha un impatto diverso sulle prestazioni della tua applicazione.

4.1. Lazy loading

  • Il lazy loading carica i dati delle relazioni solo quando necessario, riducendo il carico sul database quando tutti i dati correlati non sono sempre indispensabili.
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { lazy: true })
posts: Promise<Post[]>
}
  • Quando devi recuperare i dati, chiama semplicemente:
const user = await userRepository.findOne(userId)
const posts = await user.posts
  • Vantaggi:

    • Efficienza delle risorse: Carica solo i dati necessari quando effettivamente richiesti, riducendo i costi delle query e l'utilizzo della memoria.
    • Ideale per utilizzo selettivo dei dati: Adatto a scenari in cui non tutti i dati correlati sono necessari.
  • Svantaggi:

    • Complessità aumentata delle query: Ogni accesso ai dati correlati attiva una query aggiuntiva al database, che può aumentare la latenza se non gestita correttamente.
    • Difficoltà di tracciamento: Può portare al problema delle query n+1 se utilizzato con noncuranza.

4.2. Eager Loading

  • L'eager loading recupera automaticamente tutti i dati correlati quando viene eseguita la query principale. Questo può essere conveniente ma può causare problemi di prestazioni se ci sono troppe relazioni complesse.
@Entity()
export class User {
@OneToMany(() => Post, (post) => post.user, { eager: true })
posts: Post[]
}
  • In questo caso, i post verranno caricati non appena vengono recuperati i dati dell'utente.

  • Vantaggi:

    • Carica automaticamente i dati correlati, facilitando l'accesso alle relazioni senza query aggiuntive.
    • Evita il problema delle query n+1: Poiché tutti i dati vengono recuperati in un'unica query, non c'è rischio di generare query multiple non necessarie.
  • Svantaggi:

    • Il recupero di tutti i dati correlati in una volta può generare query di grandi dimensioni, anche quando non tutti i dati sono necessari.
    • Non adatto a scenari in cui è richiesto solo un sottoinsieme di dati correlati, poiché può portare a un utilizzo inefficiente dei dati.
  • Per esplorare maggiori dettagli ed esempi su come configurare e utilizzare relazioni lazy ed eager, visita la documentazione ufficiale di TypeORM: Relazioni Eager e Lazy

5. Ottimizzazioni avanzate

5.1. Utilizzo degli hint nelle query

  • Gli hint nelle query sono istruzioni inviate insieme alle query SQL, che aiutano il database a decidere strategie di esecuzione più efficienti.

  • Sistemi RDBMS diversi supportano diversi tipi di hint, come suggerire l'uso di indici o scegliere il tipo di JOIN appropriato.

await dataSource.query(`
SELECT /*+ MAX_EXECUTION_TIME(1000) */ *
FROM user
WHERE email = 'example@example.com'
`)
  • Nell'esempio precedente, MAX_EXECUTION_TIME(1000) istruisce MySQL a interrompere la query se richiede più di 1 secondo.

5.2. Paginazione

  • La paginazione è una tecnica cruciale per migliorare le prestazioni quando si recuperano grandi quantità di dati. Invece di recuperare tutti i dati in una volta, la paginazione li divide in pagine più piccole, riducendo il carico sul database e ottimizzando l'utilizzo della memoria.

  • In TypeORM, puoi utilizzare limit e offset per la paginazione.

const users = await userRepository
.createQueryBuilder("user")
.limit(10) // Number of records to fetch per page
.offset(20) // Skip the first 20 records
.getMany()
  • La paginazione previene il recupero di grandi quantità di dati in una sola volta, riducendo la latenza e ottimizzando l'utilizzo della memoria. Nell'implementare la paginazione, considera l'utilizzo di cursori per gestire in modo più efficiente i dati dinamici.

5.3. Caching

  • Il caching è la tecnica di memorizzare temporaneamente i risultati delle query o i dati per utilizzarli in richieste future senza interrogare il database ogni volta.

  • TypeORM include il supporto integrato per il caching ed è possibile personalizzarne l'utilizzo.

const users = await userRepository
.createQueryBuilder("user")
.cache(true) // Enable caching
.getMany()
  • Inoltre, è possibile configurare la durata della cache o utilizzare strumenti esterni di caching come Redis per una maggiore efficienza.
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
cache: {
type: "redis",
options: {
host: "localhost",
port: 6379
}
}
});