Blog de Philip Doxakis    LinkedIn    GitHub    RSS

Comment augmenter le débit du traitement en c#?

Introduction

Dans une application web, il y a parfois des bouts de traitement qui demandent beaucoup de ressource. Dans cet article, je vais m’intéresser aux solutions pour augmenter le débit en c#.

Problématique

Prenons l’exemple de la conversion de fichier.

En fonction de la taille du fichier, cela peut demander pas mal de ressource (CPU, RAM, Disque, Réseau). Supposons que l’on va faire le traitement en différé afin de ne pas affecter l’expérience utilisateur.

Stratégies et diverses solutions

Il y a plusieurs stratégies qu’on peut utiliser.

Le but est d’optimiser le temps total. On est donc à la recherche des bottlenecks.

Traitement parallèle

Vous pourriez paralléliser les tâches.

Il y a plusieurs méthodes de la classe Parallel du package System.Threading.Tasks qui vous permette de contrôler le débit désiré.

var processorCount = Environment.ProcessorCount
Parallel.For(0, 5, new ParallelOptions { MaxDegreeOfParallelism = processorCount }, (index) =>
{
    Console.WriteLine("#" + index);
});

J’utilise donc le nombre optimal de thread pour ne pas surcharger la machine. Cela permet de maintenir un certain débit. Dans ce cas-ci, il y a un thread par processeur.

De plus, si vous ne voulez pas interrompre le traitement, vous pourriez conserver les exceptions dans un dictionnaire ou une liste pour faire la gestion des exceptions en différé.

Traitement single thread

Il n’est parfois pas utile de faire le traitement sur plusieurs thread lorsqu’il y a beaucoup de synchronisation à faire et qu’elle est coûteuse en temps de calcul. Si le temps est suffisamment court, on peut utiliser: lock(obj){ ... } pour éviter la concurrence. Dans tous les cas, il faut mesurer.

Fichiers temporaires

Ce n’est pas à proprement dit un moyen pour accélérer le traitement. Cela permet d’accroître la capacité de stockage si vous utilisez un processus en 32 bits. Évidemment, si le disque dur n’est pas un SSD, cela peut être pas mal plus lent. Il ne faut pas oublier de supprimer les fichiers temporaires à la fin du traitement. Il peut manquer d’espace disque.

Traitement par batch

Le traitement par batch ou ensemble de données permet une meilleure utilisation du réseau et de la RAM disponible. La machine qui reçoit la demande peut paralléliser à son tour le traitement.

32 bits vs 64 bits

Un processus en 32 bits est limité en quantité de RAM exploitable. Par contre, c’est généralement plus rapide si le programme ne tire pas avantage de l’architecture 64 bits.

Gotchas #1 : Context http != Context http

Le contexte http est différent entre les threads. Vous pouvez le redéfinir au début pour avoir accès au contexte http.

var context = HttpContext.Current;
Parallel.For(0, 5, (index) =>
{
    HttpContext.Current = context;

    ...
});

Gotchas #2 : Lazy load

C’est bien d’utiliser du lazy loading. Cela présente plein d’avantages.

Par contre, en multi-thread, ce n’est pas génial. Avant le traitement parallèle, vous pourriez forcer le chargement des instances / des propriétés pour éviter les problèmes de concurrence.

L’influence de l’architecture

Distance entre les machines

La distance entre les machines peut influencer le temps de traitement.

La plupart des gens se soucient d’être près du client. (Client -> Web et Web -> Base de données)

Or, il y a aussi d’autres communications en réalité dans des configurations plus complexe. Un serveur peut être down et la requête peut être réacheminée à un autre data-center en fonction de votre setup.

Il faut tenir compte de la position du client sur la planète et faire le traitement aussi près que possible. Au besoin, offrez-vous un DNS Anycast. (Par exemple, DNSimple et Amazon AWS Route53 offrent des services qui répondent à ce besoin.)

Base de données

Je ne vais pas explorer le sujet en profondeur. Je vais juste énumérer quelques idées.

  • Changer la structure des tables
  • Ajouter / modifier des index
  • Réplication (Master-Master, Master-Slave)
  • Sharding
  • Utilisation de cache (RAM, Redis, SQL)

Cela peut augmenter le débit en lecture et/ou écriture.

On peut aussi s’interroger si l’on tolère une lecture impropre et si l’on tolère de ne pas lire les données les plus à jour. (Délai de quelques minutes / heures / etc.)

Même qu’il pourrait y avoir 3 types de connexion SQL :

  • Écriture
  • Lecture (temps réel)
  • Lecture (en différé)

Cela peut permettre de réduire la charge de travail. La base de données ne devient plus le bottleneck.

Exécution des tâches sur le serveur web (IIS)

Certains utilisent le serveur web pour faire les diverses tâches.

Cela présente plusieurs avantages :

  • Réutilisation du code source.
  • Simplifie le déploiement

Toutefois, ce n’est pas une architecture qui évolue bien.

  • Il y a des timeout (limite de temps) (Sur IIS, on peut augmenter le délai)
  • Perte de temps (entre les appels pour faire le travail)
  • Partage du CPU et de la RAM pour le traitement et répondre aux utilisateurs

Avec le temps, vous allez avoir besoin de plus d’un serveur web pour répondre à la demande. Vous ne serez plus en mesure de scale up et les tâches risquent d’être exécuté sur un serveur à la fois. Bref, cela devient de plus en plus difficile de distribuer les tâches à faire.

Note: il est possible de rouler une application web en 64 bits si vous avez besoin de davantage de RAM. Il y a seulement une option à cocher dans les options avancées de l’application pool. C’est en fait plutôt simple à faire. Toutefois, cela dégrade les performances et d’ailleurs, Microsoft recommande d’utilisez une application pool en 32 bits. Ce n’est donc pas une piste intéressante à explorer.

Application dédiée indépendante

Le changement le plus important : on change les responsabilités des machines.

Les serveurs web répondent uniquement aux utilisateurs. On introduit la notion de Worker. Des machines dédiées à faire des traitements. D’ailleurs, cela peut être une simple application console. Le fait d’ajouter une machine augmente de façon constante le débit.

Cela présente des avantages intéressants :

  • Possible de s’adapter en fonction du trafic sur le réseau / d’un horaire
  • Parallélisation sur tous les processeurs disponibles
  • Aucune limite quant à l’utilisation de la RAM / le disque / le réseau
  • Multi-environnement (travaillez sur plusieurs environnements client / bases de données)

Conclusion

Enfin, ce n’est pas toujours évident de trouver les bottlenecks. Essayez des alternatives, mesurez et comparez.