Precaricamento dei dati e gestione dei problemi N+1 Query

Quando si lavora con l’applicazione rails di Discourse, sia che si stia creando un plugin o che si stia apportando una pull request a discourse/discourse, ci sono una serie di contesti in cui si incontreranno problemi di query N+1. Questo argomento spiega come gestire il precaricamento dei dati per risolvere tali problemi e mantenere Discourse performante.

Problemi di query N+1 in Rails

Innanzitutto, se non hai già familiarità con i problemi di query N+1 e con il modo in cui Rails li gestisce, consulta questa sezione delle guide.

Argomenti

I problemi N+1 da tenere d’occhio quando si carica un argomento si verificano quando si caricano tabelle associate a un post. Ora, i dati serializzati per un singolo argomento vengono preparati e precaricati nella classe TopicView. Tale classe dispone di un hook on_preload che consente di precaricare le tabelle associate prima che venga eseguita la query principale. Esiste un buon esempio di utilizzo di TopicView.on_preload nel plugin ActivityPub

Questo utilizzo dell’hook precarica sia i campi personalizzati, che tratteremo di seguito, sia carica associazioni aggiuntive per i post nell’argomento. Noterai che utilizza ActiveRecord::Associations::Preloader per farlo. Puoi leggere di questa classe nella documentazione:

Elenchi di argomenti

I problemi N+1 da tenere d’occhio quando si carica un elenco di argomenti si verificano quando si caricano tabelle associate a un argomento. Similmente alla classe TopicView per i singoli argomenti, esiste una classe TopicList per gli elenchi di argomenti. E proprio come TopicView, TopicList dispone anche di un metodo on_preload che puoi utilizzare per agganciarti alla query principale dell’elenco di argomenti per caricare le tabelle associate prima che la query raggiunga il database. Esiste un buon esempio di ciò nel plugin Discourse Assign:

register_topic_preloader_associations

La classe TopicList dispone di un metodo API del plugin server register_topic_preloader_associations, che essenzialmente applica lo stesso schema che abbiamo visto nel plugin ActivityPub, utilizzando ``ActiveRecord::Associations::Preloader`. Farà quel lavoro per te se gli passi un array di associazioni.

register_topic_list_preload_user_ids

Oltre a eseguire una grande query per ottenere tutti gli argomenti necessari, la classe TopicList esegue anche una grande query per ottenere tutti gli utenti necessari per l’elenco, inclusi ciascuno:

  • utente dell’argomento
  • ultimo utente del post
  • utente in primo piano dell’argomento
  • utenti consentiti dell’argomento

Se stai caricando altri utenti per ciascun argomento, questo metodo API ti consente di includere i loro ID utente nella grande query degli utenti in modo che i loro dati vengano precaricati insieme al resto.

Categorie

Esiste un elenco principale di categorie utilizzate in vari punti del sito che viene caricato e memorizzato nella cache nel modello Site. Sebbene tecnicamente non si tratti di precaricamento in senso stretto, fa qualcosa di simile, quindi ho pensato di includerlo qui.

site_all_categories_cache_query modifier

Il metodo all_categories_cache dispone di un modificatore API del plugin server che consente di modificare la grande query delle categorie: site_all_categories_cache_query. Utilizzi questo modificatore in questo modo nel tuo plugin.rb:

register_modifier(:site_all_categories_cache_query) do |query|
   query.where("categories.name LIKE 'Cool%'")
end

Ricerca

Anche il precaricamento della ricerca non è tecnicamente descritto come tale nel codice, ma fa anche qualcosa di simile con le sue query, ovvero il caricamento anticipato.

register_search_topic_eager_load

Il metodo API del plugin server register_search_topic_eager_load consente di caricare anticipatamente tabelle aggiuntive nella ricerca. Ad esempio:

register_search_topic_eager_load do |opts|
    %i(example_table)
end

Campi personalizzati

I campi personalizzati sono alimentati dal concern HasCustomFields. Tale concern è incluso nei modelli che hanno campi personalizzati associati, ovvero Post, Topic, Category e Group. Il modulo HasCustomFields dispone di un proprio sistema di precaricamento che può essere utilizzato in vari modi. Il modo migliore per utilizzare il precaricamento di HasCustomFields è tramite i metodi API del plugin server, che dovrebbero essere relativamente autoesplicativi dato tutto ciò che ho detto finora.

register_preloaded_category_custom_fields

add_preloaded_group_custom_field

add_preloaded_topic_list_custom_field

Puoi anche utilizzare alcuni dei metodi di livello inferiore in HasCustomFields per eseguire il tuo precaricamento quando necessario, come abbiamo visto nell’esempio del plugin ActivityPub. In quell’esempio, il concern HasCustomFields preload_custom_fields viene utilizzato per precaricare i campi personalizzati dei post in TopicView.on_preload.

TopicView.on_preload do |topic_view|
  ##
  Post.preload_custom_fields(topic_view.posts, activity_pub_post_custom_field_names)
  ##
end
4 Mi Piace