Dois-je rester avec Python ou l'abandonner pour gérer la concurrence?


31

J'ai un projet LOC 10K écrit à Django avec pas mal de céleri ( RabbitMQ ) pour l'asynchronicité et les travaux en arrière-plan si nécessaire, et je suis arrivé à la conclusion que certaines parties du système gagneraient à être réécrites dans autre chose que Django pour une meilleure concurrence . Les raisons incluent:

  • Manipulation des signaux et objets mutables. Surtout quand un signal en déclenche un autre, les gérer dans Django à l'aide de l' ORM peut être surprenant lorsque les instances changent ou disparaissent. Je veux utiliser une approche de messagerie où les données transmises ne changent pas dans un gestionnaire ( l' approche de copie sur écriture de Clojure semble agréable, si je comprends bien).
  • Certaines parties du système ne sont pas basées sur le Web et nécessitent un meilleur support pour effectuer des tâches simultanément. Par exemple, le système lit les balises NFC et lorsque l'une est lue, une LED s'allume pendant quelques secondes (tâche Céleri), un son est joué (autre tâche Céleri) et la base de données est interrogée (autre tâche). Ceci est implémenté comme une commande de gestion Django, mais Django et son ORM étant synchrone par nature et partageant la mémoire est limitant (nous pensons ajouter plus de lecteurs NFC, et je ne pense pas que l'approche Django + Celery fonctionnera plus longtemps, J'aimerais voir de meilleures capacités de transmission de messages).

Quels sont les avantages et les inconvénients d'utiliser quelque chose comme Twisted ou Tornado par rapport à une langue comme Erlang ou Clojure ? Je m'intéresse aux avantages et aux inconvénients pratiques.

Comment en êtes-vous arrivé à la conclusion que certaines parties du système s'en tireraient mieux dans une autre langue? Souffrez-vous de problèmes de performances? Quelle est la gravité de ces problèmes? Si cela peut être plus rapide, est-il essentiel qu'il soit plus rapide?

Exemple 1: Django au travail en dehors d'une requête HTTP:

  1. Une balise NFC est lue.
  2. La base de données (et éventuellement LDAP) est interrogée, et nous voulons faire quelque chose lorsque les données deviennent disponibles (lumière rouge ou verte, émettre un son). Cela bloque l'utilisation de l'ORM Django, mais tant qu'il y a des travailleurs Celery disponibles, cela n'a pas d'importance. Peut être un problème avec plus de stations.

Exemple 2: «passage de message» à l'aide de signaux Django:

  1. Un post_deleteévénement est géré, d'autres objets peuvent être modifiés ou supprimés à cause de cela.
  2. À la fin, les notifications doivent être envoyées aux utilisateurs. Ici, ce serait bien si les arguments passés au gestionnaire de notifications étaient des copies d'objets supprimés ou à supprimer et garantis de ne pas changer dans le gestionnaire. (Cela pourrait être fait manuellement simplement en ne passant pas d'objets gérés par l'ORM aux gestionnaires, bien sûr.)

Je pense que de meilleures réponses se produiront si vous expliquez davantage pourquoi vous êtes parvenu à la conclusion
Winston Ewert

5
Avant que quiconque ne dise que les questions sur le choix de la langue sont hors sujet, je répondrai en disant que je pense que celle-ci est très bien car c'est un problème pratique avec des exigences spécifiques. J'espère que cela établit des comparaisons détaillées.
Adam Lear

Twisted est l'opposé de concurrent! Il s'agit d'un serveur à thread unique piloté par les événements, il ne vous mènera nulle part si vous avez besoin d'une véritable concurrence.

Réponses:


35

Pensées d'ouverture

Comment en êtes-vous arrivé à la conclusion que certaines parties du système s'en tireraient mieux dans une autre langue? Souffrez-vous de problèmes de performances? Quelle est la gravité de ces problèmes? Si cela peut être plus rapide, est-il essentiel qu'il soit plus rapide?

Asynchronie monofil

Il existe plusieurs questions et autres ressources Web qui traitent déjà des différences, des avantages et des inconvénients de l'asynchronie à un seul thread par rapport à la concurrence à plusieurs threads. Il est intéressant de lire comment le modèle asynchrone à thread unique de Node.js fonctionne lorsque les E / S sont le principal goulot d'étranglement, et de nombreuses demandes sont traitées en même temps.

Twisted, Tornado et d'autres modèles asynchrones font une excellente utilisation d'un seul thread. Étant donné qu'un grand nombre de programmes Web comportent de nombreuses E / S (réseau, base de données, etc.), le temps passé à attendre les appels distants s'additionne considérablement. C'est du temps qui pourrait être consacré à d'autres tâches, comme lancer d'autres appels de base de données, afficher des pages et générer des données. L'utilisation de ce thread unique est extrêmement élevée.

L'un des plus grands avantages de l'asynchronie à un seul thread est qu'il utilise beaucoup moins de mémoire. Dans l'exécution multi-thread, chaque thread nécessite une certaine quantité de mémoire réservée. À mesure que le nombre de threads augmente, la quantité de mémoire requise pour que les threads existent également. Puisque la mémoire est finie, cela signifie qu'il y a des limites sur le nombre de threads qui peuvent être créés à tout moment.


Exemple

Dans le cas d'un serveur Web, faites comme si chaque requête avait son propre thread. Supposons que 1 Mo de mémoire soit requis pour chaque thread et que le serveur Web dispose de 2 Go de RAM. Ce serveur Web serait capable de traiter (environ) 2000 requêtes à tout moment avant qu'il n'y ait tout simplement plus assez de mémoire pour traiter plus.

Si votre charge est considérablement plus élevée que cela, les demandes vont prendre très longtemps (en attendant que les anciennes demandes se terminent), ou vous devrez jeter plus de serveurs dans le cluster pour augmenter le nombre de demandes simultanées possibles .


Accès simultané multi-thread

La concurrence multithread repose à la place sur l'exécution de plusieurs tâches en même temps. Cela signifie que si un thread est bloqué en attente d'un appel de base de données à renvoyer, d'autres requêtes peuvent être traitées en même temps. L'utilisation des threads est plus faible, mais le nombre de threads en cours d'exécution est beaucoup plus important.

Le code multi-thread est également beaucoup plus difficile à raisonner. Il y a des problèmes de verrouillage, de synchronisation et d'autres problèmes de concurrence amusants. L'asynchronie monofil ne souffre pas des mêmes problèmes.

Cependant, le code multi-thread est beaucoup plus performant pour les tâches gourmandes en CPU . S'il n'y a aucune possibilité pour un thread de «céder» - comme un appel réseau qui se bloquerait normalement - un modèle à un seul thread n'aura tout simplement aucune concurrence.

Les deux peuvent coexister

Il y a bien sûr un chevauchement entre les deux; ils ne s'excluent pas mutuellement. Par exemple, le code multi-thread peut être écrit de manière non bloquante, pour mieux utiliser chaque thread.


Le résultat final

Il y a beaucoup d'autres questions à considérer, mais j'aime penser aux deux comme ceci:

  • Si votre programme est lié aux E / S , alors l'asynchronie à un seul thread fonctionnera probablement très bien.
  • Si votre programme est lié au CPU , alors un système multi-thread sera probablement le meilleur.

Dans votre cas particulier, vous devez déterminer quel type de travail asynchrone est en cours d'exécution et à quelle fréquence ces tâches se produisent.

  • Se produisent-ils à chaque demande? Si c'est le cas, la mémoire va probablement devenir un problème avec l'augmentation du nombre de requêtes.
  • Ces tâches sont-elles ordonnées? Si c'est le cas, vous devrez considérer la synchronisation si vous utilisez plusieurs threads.
  • Ces tâches sont-elles gourmandes en CPU? Si oui, un seul thread est-il capable de suivre la charge?

Il n'y a pas de réponse simple. Vous devez considérer quels sont vos cas d'utilisation et les concevoir en conséquence. Parfois, un modèle asynchrone à un seul thread est préférable. D'autres fois, l'utilisation d'un certain nombre de threads pour réaliser un traitement parallèle massif est nécessaire.

autres considérations

Il y a d'autres problèmes que vous devez également prendre en compte, plutôt que simplement le modèle de concurrence que vous choisissez. Connaissez-vous Erlang ou Clojure? Pensez-vous que vous seriez capable d'écrire du code multithread sécurisé dans l'une de ces langues afin d'améliorer les performances de votre application? Cela va-t-il prendre beaucoup de temps pour se mettre au courant dans l'une de ces langues, et la langue que vous apprenez vous sera-t-elle avantageuse à l'avenir?

Qu'en est-il des difficultés liées à la communication entre ces deux systèmes? Sera-t-il trop complexe de maintenir deux systèmes distincts en parallèle? Comment le système Erlang recevra-t-il les tâches de Django? Comment Erlang communiquera-t-il ces résultats à Django? Les performances sont-elles suffisamment importantes pour que la complexité supplémentaire en vaille la peine?


Dernières pensées

J'ai toujours trouvé que Django était assez rapide, et il est utilisé par certains sites très fréquentés. Vous pouvez effectuer plusieurs optimisations de performances pour augmenter le nombre de demandes simultanées et le temps de réponse. Certes, je n'ai encore rien fait avec Celery, donc les optimisations de performances habituelles ne résoudront probablement pas les problèmes que vous pourriez avoir avec ces tâches asynchrones.

Bien sûr, il y a toujours la suggestion de jeter plus de matériel sur le problème. Le coût d'approvisionnement d'un nouveau serveur est-il moins cher que le coût de développement et de maintenance d'un sous-système entièrement nouveau?

J'ai posé beaucoup trop de questions à ce stade, mais c'était mon intention. La réponse ne sera pas facile sans analyse et sans plus de détails. Cependant, être capable d'analyser les problèmes revient à connaître les questions à poser… alors j'espère que j'ai aidé sur ce front.

Mon intuition dit qu'une réécriture dans une autre langue n'est pas nécessaire. La complexité et le coût seront probablement trop importants.


modifier

Réponse au suivi

Votre suivi présente des cas d'utilisation très intéressants.


1. Django travaillant en dehors des requêtes HTTP

Votre premier exemple consistait à lire des balises NFC, puis à interroger la base de données. Je ne pense pas que l'écriture de cette partie dans une autre langue vous sera utile, simplement parce que l'interrogation de la base de données ou d'un serveur LDAP va être liée par les E / S du réseau (et potentiellement les performances de la base de données). En revanche, le nombre de demandes simultanées sera lié par le serveur lui-même, car chaque commande de gestion sera exécutée comme son propre processus. Il y aura un temps de configuration et de démontage qui aura un impact sur les performances, car vous n'envoyez pas de messages à un processus déjà en cours. Cependant, vous pourrez envoyer plusieurs demandes en même temps, car chacune sera un processus isolé.

Pour ce cas, je vois deux pistes sur lesquelles vous pouvez enquêter:

  1. Assurez-vous que votre base de données est capable de gérer plusieurs requêtes à la fois avec le pool de connexions. (Oracle, par exemple, requiert que vous configuriez Django en conséquence 'OPTIONS': {'threaded':True}.) Il peut y avoir des options de configuration similaires au niveau de la base de données ou au niveau de Django que vous pouvez modifier pour votre propre base de données. Quelle que soit la langue dans laquelle vous écrivez vos requêtes de base de données, vous devrez attendre que ces données reviennent avant de pouvoir allumer les LED. Les performances du code d'interrogation peuvent faire une différence cependant, et l'ORM de Django n'est pas rapide comme l'éclair ( mais , généralement assez rapide).
  2. Minimisez le temps de configuration / démontage. Avoir un processus en cours d'exécution et lui envoyer des messages. (Corrigez-moi si je me trompe, mais c'est ce sur quoi votre question d'origine se concentre réellement.) Que ce processus soit écrit en Python / Django ou un autre langage / framework est couvert ci-dessus. Je n'aime pas l'idée d'utiliser des commandes de gestion aussi fréquemment. Est-il possible d'avoir un petit morceau de code fonctionnant en permanence, qui pousse les messages des lecteurs NFC dans la file d'attente de messages, que Celery lit et transmet ensuite à Django? La configuration et le démontage d'un petit programme, même s'il est écrit en Python (mais pas Django!), Devraient être meilleurs que de démarrer et d'arrêter un programme Django (avec tous ses sous-systèmes).

Je ne sais pas quel serveur Web vous utilisez pour Django. mod_wsgipour Apache vous permet de configurer le nombre de processus et de threads dans les processus que le service demande. Assurez-vous de modifier la configuration appropriée de votre serveur Web pour optimiser le nombre de demandes réparables.


2. «Passage de messages» avec des signaux Django

Votre deuxième cas d'utilisation est également assez intéressant; Je ne suis pas sûr d'avoir les réponses à cela. Si vous supprimez des instances de modèle et souhaitez les utiliser ultérieurement, il peut être possible de les sérialiser JSON.dumpspuis de les désérialiser JSON.loads. Il sera impossible de recréer complètement le graphique d'objet plus tard (interrogation de modèles liés), car les champs associés sont chargés paresseusement à partir de la base de données et ce lien n'existera plus.

L'autre option serait de marquer en quelque sorte un objet à supprimer, et de le supprimer uniquement à la fin du cycle de demande / réponse (après que tous les signaux ont été traités). Cela peut nécessiter un signal personnalisé pour l'implémenter, plutôt que de s'appuyer sur post_delete.


1
beaucoup de FUD et de doutes sur le verrouillage et d'autres choses qui ne sont pas des problèmes avec Erlang, aucun des problèmes d'état partagé traditionnels que vous répertoriez ne sont des considérations avec un langage et un runtime spécifiquement conçus pour ne pas partager l'état. Erlang peut gérer des dizaines de milliers de processus discrets en très peu de RAM, la pression mémoire n'est pas un problème non plus.

@Jarrod, personnellement, je ne connais pas Erlang, donc j'accepte ce que vous dites à cet égard. Sinon, presque tout le reste que j'ai mentionné est pertinent. Coût, complexité et si les outils actuels sont correctement utilisés ou non.
Josh Smeaton


C'est le genre de réponse épique que j'aime vraiment lire ^^. +1, bon travail!
Laurent Bourgault-Roy

De plus, si vous avez des modèles DJango, ils peuvent être utilisés dans erlang avec Erlydtl
Zachary K

8

J'ai fait un développement hautement évolutif très sophistiqué pour un grand FAI américain . Nous avons fait de sérieux numéros de tranasaction en utilisant un serveur Twisted , et c'était un cauchemar de complexité pour obtenir Python / Twisted pour évoluer sur tout ce qui était lié au CPU . La liaison avec les E / S n'est pas un problème, mais la liaison avec le processeur était impossible. Nous pouvions assembler rapidement les systèmes, mais les faire évoluer vers des millions d'utilisateurs simultanés était un cauchemar de configuration et de complexité s'ils étaient liés par le processeur.

J'ai écrit un billet de blog à ce sujet, Python / Twisted VS Erlang / OTP .

TLDR; Erlang a gagné.


4

Problèmes pratiques avec Twisted (que j'aime et que j'utilise depuis environ cinq ans):

  1. La documentation laisse à désirer et le modèle est assez complexe à apprendre de toute façon. J'ai du mal à faire travailler d'autres programmeurs Python sur du code Twisted.
  2. J'ai fini par utiliser les E / S de fichiers bloquants et l'accès à la base de données faute de bonnes API de blocage. Cela peut vraiment nuire aux performances.
  3. Il ne semble pas y avoir une énorme communauté et une communauté saine utilisant Twisted; par exemple, Node.js a un développement beaucoup plus actif, en particulier pour la programmation back-end Web.
  4. C'est toujours Python, et au moins CPython n'est pas la chose la plus rapide.

J'ai fait un peu de travail en utilisant Node.js avec CoffeeScript et si les performances simultanées sont votre préoccupation, cela peut valoir le coup.

Avez-vous envisagé d'exécuter plusieurs instances de Django avec un arrangement pour répartir les clients entre les instances?


1
La documentation Python en général laisse à désirer: / (Ne dis pas que c'est si mauvais, mais pour une langue aussi populaire, on s'attendrait à ce qu'elle soit bien meilleure).
Tour

3
Je trouve que la documentation Python et en particulier la documentation Django sont parmi les meilleurs documents pour n'importe quelle langue. De nombreuses bibliothèques tierces laissent cependant à désirer.
Josh Smeaton

1

Je vous suggère ce qui suit avant d'envisager de passer à une autre langue.

  1. Utilisez LTTng pour enregistrer des événements système tels que des défauts de page, des changements de contexte et des attentes d'appel système.
  2. Convertissez chaque fois que cela prend trop de temps pour utiliser la bibliothèque C et utilisez n'importe quel modèle de conception que vous aimez (multi-threading, basé sur un événement de signal, rappel asynchrone ou Unix traditionnel select), ce qui est bon pour les E / S.

Je n'utiliserais pas le filetage en Python une fois que l'application a la priorité dans les performances. Je prendrais l'option ci-dessus, qui peut résoudre de nombreux problèmes comme la réutilisation de logiciels, la connectivité avec Django , les performances, la facilité de développement, etc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.