Prestazioni e ottimizzazione in TypeORM
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
leftJoinAndSelectoinnerJoinAndSelectper 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,
leftJoinAndSelectaiuta 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
limiteoffsetper 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
}
}
});