Pregunta

En general, ¿qué tipo de decisiones de diseño ayudan a que una aplicación escale bien?

(Nota:Habiendo aprendido sobre Notación O grande, Estoy buscando recopilar más principios de programación aquí.Intenté explicar la notación Big O respondiendo mi propia pregunta a continuación, pero quiero que la comunidad mejore tanto esta pregunta como las respuestas).

Respuestas hasta ahora
1) Definir escala.¿Necesita escalar para muchos usuarios, tráfico y objetos en un entorno virtual?
2) Mira tus algoritmos.¿La cantidad de trabajo que realizan aumentará linealmente con la cantidad de trabajo real, es decir?¿Número de elementos para recorrer, número de usuarios, etc.?
3) Mire su hardware.¿Su aplicación está diseñada de manera que pueda ejecutarla en varias máquinas si una no puede mantener el ritmo?

Pensamientos secundarios
1) No optimice demasiado y demasiado pronto: pruebe primero.Quizás se produzcan cuellos de botella en lugares imprevistos.
2) Tal vez la necesidad de escalar no supere la Ley de Moore, y tal vez actualizar el hardware sea más barato que refactorizar.

¿Fue útil?

Solución

Lo único que diría es que escriba su aplicación para que pueda implementarse en un clúster desde el principio.Todo lo que esté por encima de eso es una optimización prematura.Su primer trabajo debería ser conseguir suficientes usuarios como para tener un problema de escala.

Primero, cree el código lo más simple posible, luego perfile el sistema en segundo lugar y optimícelo solo cuando haya un problema de rendimiento obvio.

A menudo, las cifras obtenidas al perfilar su código son contrarias a la intuición;los cuellos de botella tienden a residir en módulos que no creía que fueran lentos.Los datos son los reyes cuando se trata de optimización.Si optimiza las partes que cree que serán lentas, a menudo optimizará las cosas equivocadas.

Otros consejos

Bien, entonces has llegado a un punto clave al usar la "notación O grande".Esa es una dimensión que ciertamente puede morderte el trasero si no prestas atención.También hay otras dimensiones en juego que algunas personas no ven a través de los lentes de la "gran O" (pero si miras más de cerca, realmente lo son).

Un ejemplo simple de esa dimensión es una unión a una base de datos.Existen "mejores prácticas" para construir, por ejemplo, una unión interna izquierda que ayudará a que SQL se ejecute de manera más eficiente.Si desglosas el cálculo relacional o incluso miras un plan explicativo (Oracle), puedes ver fácilmente qué índices se utilizan en qué orden y si se están realizando escaneos de tablas u operaciones anidadas.

El concepto de elaboración de perfiles también es clave.Debe estar bien instrumentado y con la granularidad adecuada en todas las partes móviles de la arquitectura para identificar y corregir cualquier ineficiencia.Digamos, por ejemplo, que está creando una aplicación web MVC2 de 3 niveles y subprocesos múltiples con un uso liberal de AJAX y procesamiento del lado del cliente junto con un OR Mapper entre su aplicación y la base de datos.Un flujo lineal y simplista de solicitud/respuesta única se ve así:

browser -> web server -> app server -> DB -> app server -> XSLT -> web server -> browser JS engine execution & rendering

Debería tener algún método para medir el rendimiento (tiempos de respuesta, rendimiento medido en "cosas por unidad de tiempo", etc.) en cada una de esas áreas distintas, no sólo a nivel de caja y sistema operativo (CPU, memoria, E/S de disco, etc.), pero específicos para el servicio de cada nivel.Entonces, en el servidor web necesitarás conocer todos los contadores del servidor web que estás utilizando.En el nivel de aplicación, necesitará esa visibilidad adicional de cualquier máquina virtual que esté utilizando (jvm, clr, lo que sea).La mayoría de los mapeadores OR se manifiestan dentro de la máquina virtual, así que asegúrese de prestar atención a todos los detalles si son visibles para usted en esa capa.Dentro de la base de datos, necesitarás saber todo que se está ejecutando y todos los parámetros de ajuste específicos para su tipo de base de datos.Si tiene mucho dinero, BMC Patrol es una buena apuesta para la mayor parte (con los módulos de conocimiento (KM) apropiados).En el extremo económico, ciertamente puede rodar el suyo propio, pero su kilometraje variará según su profundidad de experiencia.

Suponiendo que todo es sincrónico (no suceden cosas basadas en colas por las que deba esperar), existen toneladas de oportunidades para problemas de rendimiento y/o escalabilidad.Pero dado que su publicación trata sobre escalabilidad, ignoremos el navegador, excepto cualquier llamada XHR remota que invocará otra solicitud/respuesta del servidor web.

Entonces, dado este dominio del problema, ¿qué decisiones podría tomar para ayudar con la escalabilidad?

  1. Manejo de conexiones.Esto también está vinculado a la gestión y autenticación de sesiones.Tiene que ser lo más limpio y liviano posible sin comprometer la seguridad.La métrica es el máximo de conexiones por unidad de tiempo.

  2. Conmutación por error de sesión en cada nivel.¿Necesario o no?Suponemos que cada nivel será un grupo de cajas horizontalmente bajo algún mecanismo de equilibrio de carga.El equilibrio de carga suele ser muy ligero, pero algunas implementaciones de conmutación por error de sesión pueden ser más pesadas de lo deseado.Además, el hecho de que esté ejecutando sesiones fijas puede afectar más profundamente sus opciones en la arquitectura.También debe decidir si desea vincular un servidor web a un servidor de aplicaciones específico o no.En el mundo remoto de .NET, probablemente sea más fácil unirlos.Si usa la pila de Microsoft, puede ser más escalable hacerlo en 2 niveles (omita la comunicación remota), pero debe hacer un compromiso de seguridad sustancial.En el lado de Java, siempre lo he visto al menos en 3 niveles.No hay razón para hacerlo de otra manera.

  3. Jerarquía de objetos.Dentro de la aplicación, necesitas la estructura de objetos más limpia y liviana posible.Traiga sólo los datos que necesita cuando los necesita.Eliminar brutalmente cualquier obtención de datos innecesaria o superflua.

  4. O ineficiencias del mapeador.Existe un desajuste de impedancia entre el diseño de objetos y el diseño relacional.La construcción de muchos a muchos en un RDBMS está en conflicto directo con las jerarquías de objetos (persona.dirección vs.ubicación.residente).Cuanto más complejas sean sus estructuras de datos, menos eficiente será su mapeador OR.En algún momento, es posible que tengas que cortar el anzuelo en una situación puntual y aplicar un enfoque de acceso a datos más... eh... primitivo (procedimiento almacenado + capa de acceso a datos) para obtener más rendimiento o escalabilidad de una situación particularmente. módulo feo.Comprenda el costo involucrado y conviértalo en una decisión consciente.

  5. Transformaciones XSL.XML es un mecanismo maravilloso y normalizado para el transporte de datos, ¡pero hombre, puede ser un perro de gran rendimiento!Dependiendo de la cantidad de datos que lleve consigo, del analizador que elija y de lo compleja que sea su estructura, fácilmente podría meterse en un rincón muy oscuro con XSLT.Sí, académicamente es una forma brillantemente limpia de hacer una capa de presentación, pero en el mundo real puede haber problemas de rendimiento catastróficos si no se le presta especial atención.He visto un sistema consumir más del 30% del tiempo de transacción solo en XSLT.No es agradable si intentas multiplicar por 4 la base de usuarios sin comprar cajas adicionales.

  6. ¿Puedes comprar tu salida de un atasco de escalabilidad?Absolutamente.Lo he visto suceder más veces de las que me gustaría admitir.La Ley de Moore (como ya mencionaste) sigue siendo válida hoy en día.Tenga algo de dinero extra a mano por si acaso.

  7. El almacenamiento en caché es una gran herramienta para reducir la tensión en el motor (aumentar la velocidad y el rendimiento es un efecto secundario útil).Sin embargo, tiene un costo en términos de uso de memoria y complejidad al invalidar el caché cuando está obsoleto.Mi decisión sería comenzar completamente limpio y agregar lentamente el almacenamiento en caché solo cuando decida que le resulta útil.Muchas veces se subestiman las complejidades y lo que comenzó como una forma de solucionar problemas de rendimiento termina causando problemas funcionales.Además, volvamos al comentario sobre el uso de datos.Si está creando objetos por valor de gigabytes cada minuto, no importa si almacena en caché o no.Rápidamente maximizarás tu uso de memoria y la recolección de basura arruinará tu día.Entonces, supongo que la conclusión es asegurarse de comprender exactamente lo que sucede dentro de su máquina virtual (creación de objetos, destrucción, GC, etc.) para que pueda tomar las mejores decisiones posibles.

Perdón por la verbosidad.Me puse a rodar y me olvidé de mirar hacia arriba.Espero que algo de esto toque el espíritu de su investigación y no sea una conversación demasiado rudimentaria.

Bueno hay este blog llamado Alta escalabilidad que contiene mucha información sobre este tema.Algunas cosas útiles.

A menudo, la forma más eficaz de hacerlo es mediante un diseño bien pensado en el que la escala sea parte del mismo.

Decida qué significa realmente el escalado para su proyecto.¿Hay una cantidad infinita de usuarios? ¿Poder manejar barras y puntos en un sitio web son ciclos de desarrollo?

Utilice esto para centrar sus esfuerzos de desarrollo.

Jeff y Joel hablan sobre el escalamiento en el Podcast de desbordamiento de pila n.° 19.

FWIW, la mayoría de los sistemas escalarán de manera más efectiva si ignoran esto hasta que sea un problema: la ley de Moore aún se cumple y, a menos que su tráfico crezca más rápido que la ley de Moore, generalmente es más barato comprar una caja más grande (a $ 2 o $ 3 mil por pop) que pagar a los desarrolladores.

Dicho esto, el lugar más importante en el que centrarse es su nivel de datos;esa es la parte más difícil de escalar de su aplicación, ya que generalmente necesita tener autoridad y las bases de datos comerciales agrupadas son muy costosas; las variaciones de código abierto suelen ser muy difíciles de lograr.

Si cree que existe una alta probabilidad de que su aplicación necesite escalar, puede ser inteligente buscar sistemas como Memcached o Map Reduce relativamente temprano en su desarrollo.

Una buena idea es determinar cuánto trabajo genera cada tarea adicional.Esto puede depender de cómo esté estructurado el algoritmo.

Por ejemplo, imagina que tienes unos coches virtuales en una ciudad.En cualquier momento, querrás que cada coche tenga un mapa que muestre dónde están todos los coches.

Una forma de abordar esto sería:

    for each car {
       determine my position;  
       for each car {  
         add my position to this car's map;  
       }
    }

Esto parece sencillo:Mire la posición del primer automóvil y agréguelo al mapa de todos los demás automóviles.Luego mire la posición del segundo automóvil y agréguela al mapa de todos los demás automóviles.Etc.

Pero hay un problema de escalabilidad.Cuando hay 2 autos, esta estrategia requiere 4 pasos de "agregar mi posición";cuando hay 3 autos, se necesitan 9 pasos. Para cada "actualización de posición", debes recorrer la lista completa de autos, y cada auto necesita actualizar su posición.

Ignorando cuántas otras cosas se deben hacer con cada automóvil (por ejemplo, puede ser necesario un número fijo de pasos para calcular la posición de un automóvil individual), para N autos, se necesitan N2 "visitas a coches" para ejecutar este algoritmo.Esto no es ningún problema cuando tienes 5 coches y 25 escalones.Pero a medida que agregue automóviles, verá que el sistema se atasca.¡100 autos darán 10,000 pasos y 101 autos darán 10,201 pasos!

Un mejor enfoque sería deshacer el anidamiento de los bucles for.

    for each car {  
      add my position to a list;  
    }  
    for each car {    
      give me an updated copy of the master list;  
    }

Con esta estrategia, el número de pasos es múltiplo de N, no de N2. Entonces, 100 autos requerirán 100 veces el trabajo de 1 auto, NO 10,000 veces el trabajo..

Este concepto a veces se expresa en "notación O grande": el número de pasos necesarios es "O grande de N" o "O grande de N".2."

Tenga en cuenta que este concepto sólo se ocupa de la escalabilidad, no de optimizar el número de pasos para cada automóvil.Aquí no nos importa si se necesitan 5 pasos o 50 pasos por automóvil; lo principal es que N automóviles toman (X * N) pasos, no (X * N).2).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top