Logo Wedge Digital
Drf Django Rest Framework Serialisation Python

Serpy : sérialisation d’objets Python ridiculement rapide

Serpy : sérialisation d’objets Python ridiculement rapide
serpy: ridiculously fast object serialization
Ok, ma traduction est hasardeuse mais cela annonce la couleur. D’ailleurs, l’auteur annonce des performances assez significatives en comparaison à d’autres solutions (voir le benchmark) qui semblent justifier cette description quelque peu provocante.

Je suis tombé sur cette librairie Serpy (ridiculously fast object serialization) un jour où je faisais face à de gros problèmes de performances sur la sérialisation d’une réponse API REST.

Alors Serpy c’est quoi ?

Serpy, késako ?

Rappelons pour ceux du fond qui dorment, que la sérialisation désigne l’opération transformant un objet mémoire en une structure compatible d’une I/O. Système de fichiers ou API le plus souvent. L’opération inverse est la désérialisation, qui consiste donc à passer d’un format texte à un objet mémoire.

Dans cet objectif, Serpy a été conçu pour sérialiser simplement et surtout très rapidement des données complexes vers des types natifs (dicts, lists, string…). Ainsi, les types natifs peuvent être convertis aisément en JSON, par exemple.

Génial non ? Oui, mais attention, il ne sait faire que ça. Je m’explique.

Le système de sérialisation fourni par Django Rest Framework permet de traiter à la fois les étapes de sérialisation et de désérialisation. Ce système est utilisé de manière analogue à un formulaire Django. Il inclue donc l’étape de validation des données entrantes, indispensable pour que la déserialisation fonctionne correctement. À ça vous ajoutez une once de magie Django et vous pouvez, à partir du modèle Django, écrire en très peu de lignes votre sérialiseur. Mais cette polyvalence et cette magie ont un prix : LES PERFORMANCES.

Tant qu’on a très peu de données à sérialiser, l’impact est très faible. Par contre, si on a une API qui doit retourner une liste de resources complexes, la magie et la polyvalence se “payent cash” par une chute vertigineuse des performances.

Mais je pose la question : que gagne-t-on à faire appel à une couche permettant de valider des données et désérialiser des données entrantes, quand on a juste besoin de les sérialiser ?

La réponse est simple : RIEN !

Serpy entre donc en jeu en se concentrant sur la sérialisation uniquement. En s’affranchissant des étapes de validation nécessaires à la désérialisation, on récupère beaucoup de perfs. La doc annonce un ordre de grandeur (x10). C’est au minimum le gain qui sera constaté. Cela conduit en revanche à avoir des couches de sérialisation et de désérialisation hétérogènes, mais quand tout cela est testé automatiquement, ce n’est pas très impactant au niveau maintenance.

Serpy, à quoi ça ressemble ?

Serpy est un intermédiaire intéressant entre la sérialisation entièrement manuelle et une sérialisation magique à la sauce DRF. Il va falloir décrire explicitement à Serpy la nature des champs à sérialiser. Autrement dit, Serpy ne pourra/saura pas déduire les données à sérialiser à partir du modèle Django.

Prenons, par exemple, ces modèles Django :

from django.db import models

class ProductionCompany(models.Model):
name = models.CharField()

class Singer(models.Model):
name = models.CharField()
production_company = ProductionCompany()

class Album(models.Model):
name = models.CharField()
singer = models.ForeignKey(Singer)

class Track(models.Model):
rank = models.IntegerField()
name = models.CharField()
album = models.ForeignKey(Album, related_name="tracks")

A présent, écrivons les sérialiseurs correspondants :

NB : cet article n’a pas pour vocation d’expliquer la syntaxe Serpy, je vous invite à consulter la documentation officielle pour plus de détails.

import serpy

class ProductionCompanySerializer(serpy.SerpySerializer):
name = serializer.Field()

class SingerSerializer(serpy.SerpySerializer):
name = serializer.Field()
production_company = ProductionCompanySerializer()

class TrackSerializer(serpy.SerpySerializer):
rank = serializer.IntField()
name = serializer.Field()

class AlbumSerializer(serpy.SerpySerializer):
name = serializer.Field()
singer = SingerSerializer()
tracks = serializer.MethodField()

def get_tracks(cls, o):
result = []
for track in o.tracks.all():
result.append(TrackSerializer(track).data)
return result

Comme on peut le voir, il n’y pas de magie mais Serpy nous permet quand même d’écrire assez rapidement et facilement nos sérialiseurs.

Et maintenant, voyons comment utiliser un sérialiseur Serpy :

from serializer import AlbumSerializer

albums = Album.objects.all()
serialized_albums = AlbumSerializer(albums, many=True)
dict_albums = serialized_albums.data

La syntaxe est identique à celle de Django Rest Framework ce qui facilite grandement son utilisation.

Serpy, la solution miracle ?

Oui

C’est beaucoup plus rapide (entre 18 et 20 fois) qu’un sérialiseur de Django Rest Framework. Et une fois l’écriture des sérialiseurs effectués, son utilisation est identique à ceux de Django Rest Framework.

Chez Wedge, nous nous appuyons par défaut sur Serpy pour sérialiser nos données, afin d’anticiper les futurs enjeux de performance. Nous nous servons uniquement des sérialiseurs Django Rest Framework pour valider les données en entrée.

Non

Cela demande de bien optimiser le QuerySet que l’on fournit en entrée pour en tirer toute sa puissance. Or, c’est vite fastidieux d’identifier comment le faire quand vous avez un sérialiseur qui utilise des sérialiseurs qui eux-mêmes utilisent des sérialiseurs et ainsi de suite.

Et là vous me direz :

“- Bah, les sérialiseurs DRF ne permettent pas de faire mieux ? “

“- C’est pas faux !”

“- Alors, pourquoi non ? “

“- Et bien simplement pour pouvoir teaser mon prochain article… 😉”

Django Rest Framework : Serpy sérialiseur et optimisation Django ORM