Performance driver pg e mysql2

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:

  1. Con Ruby ci sono dei vantaggi di performance a lavorare su
    PostgreSQL.

  2. 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

  3. 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

  1. Con Ruby ci sono dei vantaggi di performance a lavorare su
    PostgreSQL.

Ottimo post!

Aggiungerei al performance la cosa pi critica: Postgres tende ad
essere “corretto”. Per esempio, transazionale con la DDL: metti
CREATE TABLE, UPDATE, ecc… dentro una transaction e o esegue tutto,
o niente. Quando avevo scoperto che Mysql con InnoDB non faceva
questo, sono rimasto male.


David N. Welton

http://www.welton.it/davidw/

http://www.dedasys.com/

Complimenti per la ricerca, molto utile.

2015-05-21 9:51 GMT+02:00 Paolo M. [email protected]:

Poiché il destino raggruppa sempre eventi simili,

dott. Sartorius, ora credi nella sincronicità (Carl Gustav Jung) ?! :wink:

@David io sono rimasto un po’ male ieri quando ho scoperto che le stored
procedure di MySQL ritornano record solo se finiscono con una SELECT,
altrimenti si deve creare una temporary table (engine=memory altrimenti
ci mette tanto quanto un emanuense, non come le table normali), usare un
cursor per scandire la select, fare una insert into la tabella
temporanea e poi fare una select * da quest’ultima. Due p…e.

Comunque, per completare il mio post iniziale, è andata a finire così.

Qualche ora dopo il post ho scoperto di non aver accorpato tutte le
INSERT con la activerecord-import. Ottimizzando meglio, i tempi sono
scesi a 46, 47, 53 secondi (PostgreSQL, MySQL, MariaDB). Per questo
scenario il problema è risolto, al prezzo di una complicazione nel
codice dello script di seeding.

In generale resta il maggior tempo speso da mysql2 nel codice che
comunica con il database. Non tutto si può accorpare. Ad esempio le
INSERT in un normale workflow da web server sono tutte separate tra di
loro e qui dovrebbe farsi sentire il vantaggio di performance del driver
pg. Andrebbero però fatti benchmark. Fino ad allora i miei rimarranno
solo ragionevoli sospetti.

Di nuovo, per una volta questo non sembra colpa di MySQL ma tant’è.

Poiché il destino raggruppa sempre eventi simili, cercate pmontra in
Can anyone provide a sound argument for choosing MySQL over Postgres? | Hacker News e troverete qualche
considerazione in più sulla vicenda (ieri era partita una thread MySQL
pro e contro su HN).

Paolo

Gli Cappa a flusso laminare[/url] sono utilizzati nelle più svariate
realtà, dalle
… dalla progettazione fino al montaggio e collaudo sono innumerevoli
tra cui cappe …

Approfittando del lavoro descritto poco fa in
Codici nazione in codice fiscale - IT - Ruby-Forum ho fatto qualche prova di
performance con mysql2 e i suoi nuovi prepared statement.

Premesso che forse sto comparando mele con arance, perché ho un
PostgreSQL moderno e un MySQL vecchiotto, ecco i risultati. Ho messo
dentro anche MongoDB perché era parte del progetto su cui lavoravo.

Dieci run dello script a GitHub - pmontrasio/codici-stati: Dati stati con nomi in inglese e italiano, codice fiscale, ISTAT, Ministero dell'Interno, ISO3361 a 2 e 3 lettere

PostgreSQL, MySQL, MongoDB
0.033463352, 0.033122892, 0.227229571
0.041719425, 0.041613518, 0.235716736
0.016899566, 0.058329496, 0.2415223
0.033444892, 0.04158105, 0.23954115
0.033343821, 0.041789415, 0.234358035
0.025210378, 0.100504027, 0.22563008
0.025150277, 0.058183244, 0.237807435
0.033553869, 0.041665126, 0.242589368
0.016842638, 0.033267797, 0.190134758
0.016789275, 0.049795993, 0.249314104

Medie:

0.0276417493, 0.0499852558, 0.2323843537

Attenti a notare gli zeri subito dopo la virgola: gli ordini di
grandezza non sono gli stessi ma nel caso di MongoDB ho usato mongoid
senza accedere direttamente al driver. Che sia più lento ci sta.

Versioni:

PostgreSQL 9.4.5, pg-0.18.4
MySQL 5.5.46, mysql2-0.4.2
MongoDB 2.6.11, mongo-2.2.0 e mongoid-5.0.1

No, credo che sia Clustering illusion - Wikipedia