Tengo un proyecto LOC de 10K escrito en Django con una gran cantidad de Celery ( RabbitMQ ) para trabajos de asincronía y de fondo donde sea necesario, y he llegado a la conclusión de que partes del sistema se beneficiarían de ser reescrito en algo diferente a Django para una mejor concurrencia . Las razones incluyen:
- Manejo de señales y objetos mutables. Especialmente cuando una señal activa otra, manejarlas en Django usando el ORM puede ser sorprendente cuando las instancias cambian o desaparecen. Quiero usar un enfoque de mensajería donde los datos pasados no cambian en un controlador ( el enfoque de copia en escritura de Clojure parece bueno, si lo hago bien).
- Algunas partes del sistema no están basadas en la web y necesitan un mejor soporte para realizar tareas simultáneamente. Por ejemplo, el sistema lee las etiquetas NFC y, cuando se lee una, se enciende un LED durante unos segundos (tarea de apio), se reproduce un sonido (otra tarea de apio) y se consulta la base de datos (otra tarea). Esto se implementa como un comando de administración de Django, pero Django y su ORM son sincrónicos por naturaleza y comparten memoria es limitante (estamos pensando en agregar más lectores NFC, y no creo que el enfoque Django + Celery funcione más, Me gustaría ver mejores capacidades para pasar mensajes).
¿Cuáles son las ventajas y desventajas de usar algo como Twisted o Tornado en comparación con utilizar un lenguaje como Erlang o Clojure ? Estoy interesado en beneficios prácticos y detrimentos.
¿Cómo llegó a la conclusión de que a algunas partes del sistema les iría mejor en otro idioma? ¿Estás sufriendo problemas de rendimiento? ¿Qué tan graves son esos problemas? Si puede ser más rápido, ¿es esencial que sea más rápido?
Ejemplo 1: Django en el trabajo fuera de una solicitud HTTP:
- Se lee una etiqueta NFC.
- Se consulta la base de datos (y posiblemente LDAP), y queremos hacer algo cuando los datos estén disponibles (luz roja o verde, reproducir un sonido). Esto bloquea el uso de Django ORM, pero mientras haya trabajadores de Apio disponibles, no importa. Puede ser un problema con más estaciones.
Ejemplo 2: "pasar mensajes" utilizando señales de Django:
- Se
post_delete
maneja un evento, otros objetos pueden ser alterados o eliminados debido a esto. - Al final, las notificaciones deben enviarse a los usuarios. Aquí, sería bueno si los argumentos pasados al controlador de notificaciones fueran copias de objetos eliminados o que se eliminarán y se garantice que no cambien en el controlador. (Podría hacerse manualmente simplemente no pasando objetos gestionados por el ORM a los controladores, por supuesto).
fuente
Respuestas:
Pensamientos de apertura
¿Cómo llegó a la conclusión de que a algunas partes del sistema les iría mejor en otro idioma? ¿Estás sufriendo problemas de rendimiento? ¿Qué tan graves son esos problemas? Si puede ser más rápido, ¿es esencial que sea más rápido?
Asincronía de un solo hilo
Hay varias preguntas y otros recursos web que ya abordan las diferencias, los pros y los contras de la asincronía de subproceso único frente a la concurrencia de subprocesos múltiples. Es interesante leer acerca de cómo funciona el modelo asincrónico de un solo hilo de Node.js cuando la E / S es el principal cuello de botella, y hay muchas solicitudes atendidas a la vez.
Twisted, Tornado y otros modelos asincrónicos hacen un excelente uso de un solo hilo. Dado que gran parte de la programación web tiene muchas E / S (red, base de datos, etc.), el tiempo que pasa esperando llamadas remotas se suma significativamente. Ese es el tiempo que podría dedicarse a hacer otras cosas, como iniciar otras llamadas a la base de datos, generar páginas y generar datos. La utilización de ese hilo único es extremadamente alta.
Uno de los mayores beneficios de la asincronía de un solo hilo es que usa mucha menos memoria. En la ejecución de subprocesos múltiples, cada subproceso requiere una cierta cantidad de memoria reservada. A medida que aumenta el número de subprocesos, también lo hace la cantidad de memoria necesaria solo para que existan los subprocesos. Como la memoria es finita, significa que hay límites en la cantidad de subprocesos que se pueden crear en cualquier momento.
Ejemplo
En el caso de un servidor web, imagine que cada solicitud recibe su propio hilo. Digamos que se requiere 1 MB de memoria para cada subproceso, y el servidor web tiene 2 GB de RAM. Este servidor web podría procesar (aproximadamente) 2000 solicitudes en cualquier momento antes de que simplemente no haya suficiente memoria para procesar más.
Si su carga es significativamente más alta que esto, las solicitudes tardarán mucho tiempo (cuando espere a que se completen las solicitudes más antiguas), o tendrá que lanzar más servidores al clúster para ampliar la cantidad de solicitudes simultáneas posibles .
Concurrencia multihilo
La concurrencia de subprocesos múltiples se basa en la ejecución de varias tareas al mismo tiempo. Eso significa que si un subproceso está bloqueado esperando que vuelva una llamada de la base de datos, se pueden procesar otras solicitudes al mismo tiempo. La utilización de subprocesos es menor, pero la cantidad de subprocesos que se ejecutan es mucho mayor.
El código multiproceso también es mucho más difícil de razonar. Hay problemas con el bloqueo, la sincronización y otros problemas divertidos de concurrencia. La asincronía de un solo hilo no sufre los mismos problemas.
Sin embargo, el código multiproceso es mucho más eficaz para tareas intensivas de CPU . Si no existen oportunidades para que un subproceso "ceda", como una llamada de red que normalmente se bloquearía, un modelo de un solo subproceso simplemente no tendrá ningún tipo de concurrencia.
Ambos pueden coexistir
Por supuesto, hay una superposición entre los dos; No son mutuamente exclusivos. Por ejemplo, el código de varios subprocesos se puede escribir de forma no bloqueante, para utilizar mejor cada subproceso.
La línea de fondo
Hay muchos otros problemas a considerar, pero me gusta pensar en los dos de esta manera:
En su caso particular, debe determinar qué tipo de trabajo asincrónico se está completando y con qué frecuencia surgen esas tareas.
No hay una respuesta simple. Debe considerar cuáles son sus casos de uso y diseñar en consecuencia. A veces, un modelo asincrónico de un solo hilo es mejor. Otras veces, se requiere el uso de varios subprocesos para lograr un procesamiento paralelo masivo.
Otras Consideraciones
Hay otros problemas que debe considerar también, en lugar de solo el modelo de concurrencia que elija. ¿Conoces Erlang o Clojure? ¿Crees que serías capaz de escribir código seguro de subprocesos múltiples en uno de estos idiomas para mejorar el rendimiento de tu aplicación? ¿Va a tomar mucho tiempo ponerse al día en uno de estos idiomas, y el idioma que aprenda le beneficiará en el futuro?
¿Qué hay de las dificultades asociadas con la comunicación entre estos dos sistemas? ¿Será demasiado complejo mantener dos sistemas separados en paralelo? ¿Cómo recibirá el sistema Erlang tareas de Django? ¿Cómo Erlang comunicará esos resultados a Django? ¿Es el rendimiento suficientemente significativo un problema que valga la pena la complejidad adicional?
Pensamientos finales
Siempre he encontrado que Django es lo suficientemente rápido, y lo usan algunos sitios con mucho tráfico. Hay varias optimizaciones de rendimiento que puede hacer para aumentar la cantidad de solicitudes concurrentes y el tiempo de respuesta. Es cierto que hasta ahora no he hecho nada con Celery, por lo que las optimizaciones de rendimiento habituales probablemente no resolverán los problemas que pueda tener con estas tareas asincrónicas.
Por supuesto, siempre existe la sugerencia de lanzar más hardware al problema. ¿Es el costo de aprovisionar un nuevo servidor más barato que el costo de desarrollo y mantenimiento de un subsistema completamente nuevo?
He hecho demasiadas preguntas en este momento, pero esa era mi intención. La respuesta no será fácil sin análisis y más detalles. Sin embargo, poder analizar los problemas se reduce a conocer las preguntas que se deben hacer ... así que espero haber ayudado en ese frente.
Mi instinto dice que una reescritura en otro idioma es innecesaria. La complejidad y el costo probablemente serán demasiado grandes.
Editar
Respuesta al seguimiento
Su seguimiento presenta algunos casos de uso muy interesantes.
1. Django trabajando fuera de las solicitudes HTTP
Su primer ejemplo consistió en leer etiquetas NFC y luego consultar la base de datos. No creo que escribir esta parte en otro idioma sea tan útil para usted, simplemente porque consultar la base de datos o un servidor LDAP estará sujeto a la E / S de la red (y posiblemente al rendimiento de la base de datos). Por otro lado, el número de solicitudes simultáneas estará vinculado por el propio servidor, ya que cada comando de administración se ejecutará como su propio proceso. Habrá tiempo de configuración y desmontaje que afectará el rendimiento, ya que no está enviando mensajes a un proceso que ya se está ejecutando. Sin embargo, podrá enviar múltiples solicitudes al mismo tiempo, ya que cada una será un proceso aislado.
Para este caso, veo dos caminos que puedes investigar:
'OPTIONS': {'threaded':True}
.) Puede haber opciones de configuración similares a nivel de base de datos o nivel de Django que puede ajustar para su propia base de datos. No importa en qué idioma escriba las consultas de su base de datos, tendrá que esperar a que estos datos vuelvan antes de poder encender los LED. Sin embargo, el rendimiento del código de consulta puede marcar la diferencia, y el Django ORM no es tan rápido ( pero , por lo general, lo suficientemente rápido).No estoy seguro de qué servidor web está utilizando para Django.
mod_wsgi
para Apache le permite configurar la cantidad de procesos y subprocesos dentro de los procesos que el servicio solicita. Asegúrese de ajustar la configuración relevante de su servidor web para optimizar la cantidad de solicitudes que se pueden atender.2. "Pasar mensajes" con señales de Django
Su segundo caso de uso también es bastante interesante; No estoy seguro si tengo las respuestas para eso. Si está eliminando instancias de modelo y desea operar en ellas más tarde, podría ser posible serializarlas
JSON.dumps
y luego deserializarlasJSON.loads
. Será imposible recrear completamente el gráfico de objetos más adelante (consultar modelos relacionados), ya que los campos relacionados se cargan lentamente desde la base de datos y ese enlace ya no existirá.La otra opción sería marcar de alguna manera un objeto para su eliminación, y solo eliminarlo al final del ciclo de solicitud / respuesta (después de que todas las señales hayan sido atendidas). Puede requerir una señal personalizada para implementar esto, en lugar de confiar en ello
post_delete
.fuente
Hice un desarrollo muy sofisticado y altamente escalable para un importante ISP de EE . UU . Hicimos algunos números de acción serios usando un servidor Twisted , y fue una pesadilla de complejidad hacer que Python / Twisted escale en todo lo que estaba vinculado a la CPU . El enlace de E / S no es un problema, pero el enlace de la CPU era imposible. Podríamos juntar los sistemas rápidamente, pero lograr que escalen a millones de usuarios concurrentes fue una pesadilla de configuración y complejidad si estuvieran atados por la CPU.
Escribí una publicación de blog al respecto, Python / Twisted VS Erlang / OTP .
TLDR; Erlang ganó.
fuente
Problemas prácticos con Twisted (que amo y he usado durante unos cinco años):
He trabajado un poco usando Node.js con CoffeeScript y si el rendimiento concurrente es su preocupación, entonces puede valer la pena el salto.
¿Ha considerado ejecutar varias instancias de Django con algún arreglo para distribuir clientes entre instancias?
fuente
Sugeriré lo siguiente antes de considerar cambiar a otro idioma.
select
) que es bueno para E / S allí.No usaría threading en Python una vez que la aplicación tenga prioridad en el rendimiento. Tomaría la opción anterior, que puede resolver muchos problemas como la reutilización de software, la conectividad con Django , el rendimiento, la facilidad de desarrollo, etc.
fuente