Ho dovuto portare uno script di seeding di database da PostgreSQL 9.4 a
MariaDB 10 (un cliente ha scelto così e con poco entusiasmo mi devo
adeguare) e questo ha dato il via ad una serie interessante di scoperte.
Una di queste riguarda i driver Ruby pg e mysql2.
A parte alcune amenità [1] [2] [3] [4] ho subito notato che lo script
con MariaDB girava 20 volte (venti) più lentamente che con PostgreSQL:
21 minuti contro 1 minuto e 3 secondi. Inutilizzabile.
Una differenza così grande non può essere dovuta al database, per cui mi
sono messo ad indagare la configurazione. Anche il MySQL della
distribuzione (Ubuntu 12.04) era ugualmente lento e mi posso aspettare
che quello sia configurato ragionevolmente bene. A questo punto il
sospetto diventa il driver.
Ho aperto questa issue Performance problems · Issue #623 · brianmario/mysql2 · GitHub e
mi hanno dato due preziosi consigli: usare un profiler
(GitHub - ruby-prof/ruby-prof: A ruby profiler. See https://ruby-prof.github.io for more information.) e la gemma activerecord-import
(GitHub - zdennis/activerecord-import: A library for bulk insertion of data into your database using ActiveRecord.).
L’uso del profiler ha evidenziato che il driver pg usa prepared
statements, che alla scala del numero di record creati dal mio script
(poco più di 32 mila) danno dei vantaggi. mysql2 invece non usa prepared
statement e pari numero di chiamate al db questo pare faccia la
differenza (x20!).
Accorpare le chiamate usando activerecord-import (fa una sola insert per
tutto un array di oggetti) ha significato riscrivere lo script in modo
un po’ innaturale, perché mi servivano gli id dei record creati per
passarli in quelli dei record associati, ma i tempi di esecuzione per
mysql2 sono scesi da 21 minuti a 1 minuto e 33". Ne è valsa la pena. Ci
sono solo 1045 chiamate al db e ciò nonostante è sempre più lento delle
32k chiamate fatte da pg, ma ora si ragiona di nuovo. Lo script con pg e
activerecord-import però è sceso a 47 secondi.
Pur con tutte le ottimizzazioni introdotte a activerecord-import le
chiamate a PostgreSQL del mio script sommano a 9.4 secondi contro i 49.8
delle chiamate a MariaDB (Ruby aggiunge una quarantina di secondi,
indipendentemente dal database usato).
Riassumendo:
-
Con Ruby ci sono dei vantaggi di performance a lavorare su
PostgreSQL. -
Pare che la versione 0.4.0 di mysql2 avrà i prepared statement e
quando sarà rilasciata vedrò di ricordarmi di rifare i test senza
activerecord-import -
Per i dettagli (tempi di profiling, etc) leggete
Performance problems · Issue #623 · brianmario/mysql2 · GitHub.
Le “amenità”:
[1]
[2] Per MariaDB installare la gemma mysql2 dopo aver dato bundle config
build.mysql2 --with-mysql-config=/path/to/mariadb/bin/mysql_config
(occhio che questa è globale, date un
–with-mysql-config=/usr/bin/mysql_config per quando vi servirà per
mysql)
[3] MySQL e MariaDB non hanno il TRUNCATE CASCADE, quindi
connection = ActiveRecord::Base.connection
connection.execute(“SET FOREIGN_KEY_CHECKS = 0;”)
[ tutti i modelli ].each do |model|
connection.execute(“TRUNCATE #{model.table_name}”)
end
connection.execute(“SET FOREIGN_KEY_CHECKS = 1;”)
[4] Ma neppure ActiveRecord ha il TRUNCATE, quindi o usate gemme che lo
aggiungono o anche per PostgreSQL vi serve un loop come quello, con
connection.execute(“TRUNCATE #{model.table_name} CASCADE”) e senza le
set foreign_key_checks.
Paolo