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:
- 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).
- 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_wsgi
pour 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.dumps
puis 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
.