5 Jun
duccio

duccio il 5 June 2008 parla di Rails Snippet

Rails 2.1.0 - Migliorate le performance di Active Record con l’uso della condizione IN di SQL

Con l’uscita di Rails 2.1.0 sono stati introdotti sostanziali miglioramenti all’active record riguardanti le performance nell’uso dell’eager loading, adesso funzionante anche con associazioni polimorfiche.

I miglioramenti

Fino alla penultima versione di rails (2.0.2) non era possibile includere un’associazione polimorfica:

    1 MyModel.find(:all, :include => :my_polymorphic_association, :order =>  "created_at DESC")

restituiva come errore:

    1 ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :my_polymorphic_association

Questo problema non sussiste più! Inoltre il comportamento dell’include in termini di query SQL è cambiato in meglio, infatti anzichè fare una join vengono eseguite due query più leggere, ottenendo un sostanziale miglioramento in termini di performance.

Una query come questa:

    1 Person.find(:all, :include => [:friends])

passa da:

    1 SELECT `people`.`id` AS t0_r0, `people`.`name` AS t0_r1,
    2 `people`.`surname` AS t0_r2, `friends`.`id` AS t1_r0,
    3 `friends`.`nickname` AS t1_r1 FROM `people`
    4 LEFT OUTER JOIN `friends` ON friends.person_id = friends.id

a queste due query più veloci:

    1 SELECT `people`.`id`, `people`.`name`, `people`.`surname` FROM `people`
    2 SELECT `friends`.`id`, `friends`.`nickname` FROM `friends` WHERE (`friends`.person_id IN (5,8,27,42))

E per le vecchie versioni di Rails?

Chiaramente sarebbe utile migrare al rails 2.1.0, ma non sempre il passaggio è veloce. Nei nostri vecchi progetti abbiamo adottato la stessa soluzione con una piccola variante che risolveva il problema degli ordinamenti. Magari con un esempio vi spiego meglio!!

Supponiamo di fare una query e di voler recuperare gli oggetti associati, per esempio:

    1 Table.find(:all, :include => [:avatar], :conditions => "created_at DESC")

Questa query mi restituisce i record della tabella “tables” e in eager load gli Avatar associati nell’ordine in cui li desideravo, ma è dispendiosa poichè afflitta dal problema 1+N di Active Record.

myrledobject.jpg

Sarebbe interessante riuscire a prendere in un colpo solo tutti gli Avatar associati magari con una query in più (come fa l’active record 2.1):

    1 ids = Table.find(:all, :conditions => "created_at DESC").map{|item| item.myrlable_id}
    2 Avatar.find(:all, :conditions => ["id IN(?)",ids])

Così facendo si ottengono gli Avatar associati ma anzichè essere restituiti nell’ordine voluto vengono restitituiti come trovati sul db. E’ Chiaro che questo non va bene perchè deve essere mantenuto il primo ordinamento richiesto; per ovviare al problema potete aggiungere l’ordinamento “FIELD(id,#{ids.join(”,”)})” nella seconda query:

    1 ids = Table.find(:all, :conditions => "created_at DESC").map{|item| item.myrlable_id}
    2 Avatar.find(:all, :conditions => ["id IN(?)",ids], :order => "FIELD(id,#{ids.join(",")})")

In questo modo gli oggetti Avatar vengono restituiti nell’ordine corretto.

4 Commenti a “Rails 2.1.0 - Migliorate le performance di Active Record con l’uso della condizione IN di SQL”

  1. Lorenzo il 16 June 2008 alle 17:52 dice:

    Ma non dovrebbe esssere più veloce mysql nel gestire il join piuttosto (visto che è stato concepito per quello) piuttosto che fare 2 query e poi creare il risultato facendolo parsare allo script server side?

  2. duccio il 16 June 2008 alle 19:43 dice:

    Ciao Lorenzo,
    quando nelle tabelle interessate cominciano ad esserci centinaia di migliaia di record ti assicuro che la seconda soluzione, quella che è stata ottimizzata in rails 2.1, è veramente più veloce!!

  3. Tex il 25 June 2008 alle 10:24 dice:

    Ciao Duccio,

    appena ho un po’ di tempo lo proverò ma ho come l’impressione che si corra il rischio che il numero di elementi ritornati dalla prima query, da mettere poi come parametro nella clausola in della seconda query, possano essere troppi (mi sembra infatti che ci siano dei limiti sul numero di elementi assegnabili come parametro nella clausola in)… cosa ne pensi ? (es. prima tabella 20K record e seconda tabella con 2M record, se il filtro sulla prima tabella mi ritorna ad esempio 5000 id diversi ce la fa sql a passarli nella clausola in senza scoppiare ?)

  4. tom il 9 July 2008 alle 20:58 dice:

    Dividere un join in query più piccole è notoriamente migliore sotto l’aspetto prestazionale, soprattutto con MySql

Scrivi un commento