Test de performance Python ORM à l'aide de la méthode de référence TPC-C

Lors de l'écriture d'applications en Python, les mappeurs objet-relationnels (ORM) sont souvent utilisés pour travailler avec des bases de données. SQLALchemy, PonyORM et le mappeur relationnel-objet inclus avec Django sont des exemples d'ORM. Lors du choix de l'ORM, ses performances jouent un rÎle assez important.


Sur Habr, et sur Internet dans son ensemble, il n'est pas possible de trouver un seul test de performance. À titre d'exemple de rĂ©fĂ©rence ORM python de qualitĂ©, vous pouvez utiliser la rĂ©fĂ©rence ORM Tortoise ( lien vers le rĂ©fĂ©rentiel ). Cette rĂ©fĂ©rence analyse la vitesse de six ORM pour onze types diffĂ©rents de requĂȘtes SQL.


En gĂ©nĂ©ral, le benchmark de tortue permet d'Ă©valuer la vitesse d'exĂ©cution des requĂȘtes Ă  l'aide de diffĂ©rents ORM, mais je vois un problĂšme avec cette approche de test. Les ORM sont souvent utilisĂ©s dans les applications Web oĂč plusieurs utilisateurs peuvent envoyer des demandes diffĂ©rentes en mĂȘme temps, mais je n'ai pas trouvĂ© de rĂ©fĂ©rence unique qui Ă©value les performances d'ORM dans de telles conditions. À la suite de cela, j'ai dĂ©cidĂ© d'Ă©crire mon benchmark et de comparer PonyORM et SQLAlchemy avec lui. Comme base, j'ai pris le benchmark TPC-C.


La société TPC depuis 1988, développe des tests, destinés au traitement des données. Ils sont depuis longtemps devenus un standard de l'industrie et sont utilisés par presque tous les fournisseurs d'équipement sur divers échantillons de matériel et de logiciels. La principale caractéristique de ces tests est qu'ils visent à tester sous une charge énorme dans des conditions aussi proches que possible des conditions réelles.


TPC-C simule un réseau d'entrepÎt. Il comprend une combinaison de cinq transactions exécutées simultanément de types et de complexité différents. La base de données comprend neuf tables avec un grand nombre d'enregistrements. Les performances du test TPC-C sont mesurées en transactions par minute.


J'ai dĂ©cidĂ© de tester deux ORM Python (SQLALchemy et PonyORM) en utilisant la mĂ©thode de test TPC-C adaptĂ©e Ă  cette tĂąche. Le but du test est d'Ă©valuer la vitesse de traitement des transactions lorsque plusieurs utilisateurs virtuels accĂšdent Ă  la base de donnĂ©es en mĂȘme temps.


Description du test


Dans le test que j'ai écrit, une base de données est d'abord créée et remplie, qui est une base de données d'un réseau d'entrepÎts. Le schéma de la base de données ressemble à ceci :


image


La base de données comprend huit relations:


  1. EntrepĂŽt - entrepĂŽt
  2. Quartier - zone d'entrepĂŽt
  3. Commande - Commande
  4. OrderLine - ligne de commande (poste de commande)
  5. Stock - quantité d'un certain produit dans un entrepÎt spécifique
  6. Article - article
  7. Client - client
  8. Historique - Historique des paiements clients

, e . . , :


  1. new_order ( ) — 45%
  2. payment ( ) — 43%
  3. order_status ( ) — 4%
  4. delivery ( ) — 4%
  5. stock_level ( ) — 4%

, TPC-C.


TPC-C , , ORM, . 64+ , .


:


  1. ,
  2. . : Stock 100 000 * W, W — , : 100 * W
  3. 5 . Payment ID, . ID,
  4. NewOrder. , , Order, NewOrder. , NewOrder. , , , , , . Order bool “is_o_delivered”, False, ,

, .


New Order


  1. : id id
  2. id
  3. ()
  4. . Item.
  5. , .

Payment


  1. : id id
  2. id
  3. .
  4. 1
  5. , ,
  6. .

Statut de la commande


  1. Transactions servies par l'ID client
  2. Le client et sa derniÚre commande sont extraits de la base de données
  3. Le statut est repris de la commande (livrée ou non) et des articles commandés

Livraison


  1. Transactions servies par l'identifiant de l'entrepĂŽt
  2. L'entrepÎt est demandé à la base de données par id et toutes ses sections
  3. Pour chaque site, la plus ancienne des commandes non livrées est prise. Dans chacun d'eux, le statut de livraison devient True
  4. De la base de données sont extraits les utilisateurs dont les commandes ont été livrées lors de cette transaction, et chacun augmente le compteur de livraison

Niveau de stock


  1. Transactions servies par l'identifiant de l'entrepĂŽt
  2. L'entrepÎt est demandé à la base de données par id
  3. Les 20 derniÚres commandes de cet entrepÎt sont demandées à partir de la base de données
  4. Pour chaque article de ces commandes de la base de données, la quantité des marchandises restantes dans l'entrepÎt est demandée

RĂ©sultats de test


Deux ORM participent aux tests:


  1. SQLAlchemy Les graphiques sont représentés par une ligne bleue.
  2. PonyORM. Les graphiques sont représentés par la ligne jaune.

10 2 , . multiprocessing.


—
—


PostgreSQL



, TPC-C. Pony .


image


:
Pony — 2543 /
SQLAlchemy — 1353.4 /


ORM . .


“New Order”


image


Vitesse moyenne:
Pony - 3349,2 trans / min
SQLAlchemy - 1415,3 trans / min


Transaction «Paiement»


image


Vitesse moyenne:
Pony - 7175,3 trans / min
SQLAlchemy - 4110,6 trans / min


Transaction «État de la commande»


image


Vitesse moyenne:
Pony - 16645,6 trans / min
SQLAlchemy - 4820,8 trans / min


Transaction «Livraison»


image


Vitesse moyenne:
SQLAlchemy - 716,9 trans / min
Pony - 323,5 trans / min


Transaction «Niveau de stock»


image


Vitesse moyenne:
Pony - 677,3 trans / min
SQLAlchemy - 167,9 trans / min


Analyse des résultats des tests


AprÚs avoir reçu les résultats, j'ai analysé pourquoi, dans diverses situations, un ORM est plus rapide qu'un autre et suis arrivé aux conclusions suivantes:


  1. 4 5 PonyORM , , SQL PonyORM Python SQL, , SQLALchemy SQL . PonyORM:


    stocks = select(stock for stock in Stock
    if stock.warehouse == whouse
    and stock.item in items).order_by(Stock.id).for_update()

    SQLAlchemy:


    stocks = session.query(Stock).filter(
    Stock.warehouse == whouse, Stock.item.in_(items)).order_by(text("id")).with_for_update()

  2. SQLAlchemy Delivery , UPDATE, , .



, SQLAlchemy:


INFO:sqlalchemy.engine.base.Engine:UPDATE order_line SET delivery_d=%(delivery_d)s WHERE order_line.id = %(order_line_id)s
INFO:sqlalchemy.engine.base.Engine:(
{'delivery_d': datetime.datetime(2020, 4, 6, 14, 33, 6, 922281), 'order_line_id': 316},
{'delivery_d': datetime.datetime(2020, 4, 6, 14, 33, 6, 922272), 'order_line_id': 317},
{'delivery_d': datetime.datetime(2020, 4, 6, 14, 33, 6, 922261))

Pony Update:


SELECT "id", "delivery_d", "item", "amount", "order"
FROM "orderline"
WHERE "order" = %(p1)s
{'p1':911}

UPDATE "orderline"
SET "delivery_d" = %(p1)s
WHERE "id" = %(p2)s
  AND "order" = %(p3)s
{'p1':datetime.datetime(2020, 4, 7, 17, 48, 58, 585932), 'p2':5047, 'p3':911}

UPDATE "orderline"
SET "delivery_d" = %(p1)s
WHERE "id" = %(p2)s
  AND "order" = %(p3)s
{'p1':datetime.datetime(2020, 4, 7, 17, 48, 58, 585990), 'p2':5048, 'p3':911}


Sur la base des rĂ©sultats de ces tests, je peux dire que Pony est beaucoup plus rapide lors de la rĂ©cupĂ©ration Ă  partir de la base de donnĂ©es, et SQLAlchemy peut dans certains cas produire des requĂȘtes de mise Ă  jour beaucoup plus rapides.


À l'avenir, je prĂ©vois de tester d'autres ORM (Peewee, Django) de cette maniĂšre.


Références


Code de test: lien vers le référentiel
SQLAlchemy: documentation , communauté
Pony: documentation , communauté


All Articles