fr en

Factory-Boy : Optimiser la création en masse

Posté le Thu 12 September 2024 dans développement

Le problème

Lorsque vous créez de grands ensembles de données en utilisant factory_boy, vous pouvez vous retrouver à utiliser MyFactory.create_batch(), ce qui est excellent pour spécifier la taille de la liste, mais est limité en termes de performance lorsqu'il s'agit de factory basées sur des modèles Django.

En effet, voici le code source concerné :

    @classmethod
    def create_batch(cls, size, **kwargs):
        """Create a batch of instances of the given class, with overridden attrs.

        Args:
            size (int): the number of instances to create

        Returns:
            object list: the created instances
        """
        return [cls.create(**kwargs) for _ in range(size)]

Cela signifie qu'une instance est générée et créée pour chaque itération, entraînant de nombreuses requêtes SQL, surtout si votre factory utilise SubFactory (lorsqu'un modèle est associé via une ForeignKey).

La solution

Pour éviter trop de requêtes SQL, il serait préférable d'utiliser bulk_create depuis le manager Django.

Une solution simple consiste à générer les instances puis à les sauvegarder. Vous pouvez également passer des paramètres (comme notifications_enabled par exemple) :

class ContactFactory(DjangoModelFactory):
    class Meta:
        model = Contact

# Créer mille contacts
contact_list = ContactFactory.simple_generate_batch(
    create=False, size=1000, notifications_enabled=True
)
contact_list = Contact.objects.bulk_create(contact_list)

Mais que faire si notre factory a une SubFactory ? Vous rencontrerez certainement un problème de N+1. Pour y remédier, vous pouvez créer en masse séquentiellement, en conservant les clés primaires :

class ContactFactory(DjangoModelFactory):
    class Meta:
        model = Contact

class NotificationFactory(DjangoModelFactory)
    contact = factory.SubFactory(ContactFactory)

    class Meta:
        model = Notification

size = 1000
# Création des contacts
contact_list = ContactFactory.simple_generate_batch(
    create=False, size=size, notifications_enabled=True
)
contact_list = Contact.objects.bulk_create(contact_list)

# Créer une notification pour chaque contact
obj_list = NotificationFactory.simple_generate_batch(
    create=False, size=size, contact=None
)
for pos, obj in enumerate(obj_list):
    obj.contact_id = contact_list[pos].pk
Notification.objects.bulk_create(obj_list)