Lista de comprobación de eficiencia del rendimiento
La eficiencia del rendimiento es la capacidad de la carga de trabajo para escalar con el fin de satisfacer de manera eficiente las demandas que los usuarios hayan ejercido sobre ella y es uno de los pilares del marco de buena arquitectura de Microsoft Azure. Use esta lista de comprobación para revisar la arquitectura de la aplicación desde un punto de vista de la eficiencia del rendimiento.
Diseño de aplicación
Crear particiones de la carga de trabajo. Diseñe las partes del proceso de forma que sean discretas y se puedan descomponer. Reduzca el tamaño de cada parte, al tiempo que sigue las reglas habituales de separación de problemas y el principio de responsabilidad única. Esto permite que las partes componentes se distribuyan de forma que se maximice el uso de cada unidad de proceso (como un rol o un servidor de base de datos). También resulta más fácil escalar la aplicación al agregar instancias de recursos específicos. En dominios complejos, considere la posibilidad de adoptar una arquitectura de microservicios.
Diseñar para escalar. El escalado permite a las aplicaciones reaccionar a una carga variable aumentando y disminuyendo el número de instancias de roles, colas y otros servicios que usan. No obstante, la aplicación debe diseñarse teniendo en cuenta esto. Por ejemplo, la aplicación y los servicios que utiliza deben ser sin estado, para permitir que las solicitudes se enruten a cualquier instancia. Esto también impide que la adición o la eliminación de instancias específicas afecten negativamente a los usuarios actuales. También debe implementar la configuración o la detección automática de instancias a medida que se agregan y eliminan, de forma que el código de la aplicación pueda realizar el enrutamiento necesario. Por ejemplo, una aplicación web puede utilizar un conjunto de colas con un enfoque round-robin para enrutar las solicitudes a los servicios de fondo que se ejecutan en roles de trabajador. La aplicación web debe poder detectar los cambios en el número de colas, con el fin de enrutar correctamente las solicitudes y equilibrar la carga en la aplicación.
Escalar como unidad. Planee la incorporación de recursos adicionales para adaptarse al crecimiento. Para cada recurso, conozca los límites de escalado superiores y utilice el particionamiento o la descomposición para traspasar estos límites. Determine las unidades de escala para el sistema en términos de conjuntos de recursos bien definidos. Esto facilita la aplicación de operaciones de escalado horizontal y tiende a evitar los impactos negativos en la aplicación debidos a las limitaciones impuestas por la falta de recursos en alguna parte del sistema global. Por ejemplo, la incorporación de un número x de roles web y de trabajo podría requerir un número y de colas adicionales y un número z de cuentas de almacenamiento para administrar la carga de trabajo adicional generadas por los roles. Así que, una unidad de escalado podría estar formada por un número x de roles web y de trabajo, un número y de colas y un número z de cuentas de almacenamiento. Diseñe la aplicación de forma que sea fácil de escalar agregando una o varias unidades de escalado. Considere la posibilidad de usar el patrón de stamps de implementación para implementar unidades de escalado.
Evitar afinidad del cliente. Siempre que sea posible, asegúrese de que la aplicación no requiere afinidad. Así, las solicitudes se pueden enrutar a cualquier instancia y el número de instancias carece de importancia. Esto también evita la sobrecarga de tener que almacenar, recuperar y mantener la información de estado para cada usuario.
Aprovechar las características de escalado automático de la plataforma. Cuando la plataforma de hospedaje admite una función de escalado automático, como Escalado automático de Azure, es preferible su uso al de mecanismos de terceros o personalizados a menos que el mecanismo integrado no pueda cumplir los requisitos. Use reglas de escalado programadas siempre que sea posible para garantizar que los recursos estén disponibles sin retrasos iniciales, pero agregue escalado automático reactivo a las reglas cuando sea apropiado para hacer frente a cambios inesperados en la demanda. Puede utilizar las operaciones de escalado automático en el modelo de implementación clásica para ajustar el escalado automático y para agregar contadores personalizados a las reglas. Para más información, consulte Instrucciones de escalado automático.
Descargue tareas con uso intensivo de CPU y de E/S como tareas en segundo plano. Si se espera que una solicitud a un servicio tarde mucho tiempo en ejecutarse o que absorba una cantidad considerable de recursos, descargue el procesamiento de esta solicitud a una tarea independiente. Utilice roles de trabajo o trabajos en segundo plano (en función de la plataforma de hospedaje) para ejecutar estas tareas. Esta estrategia permite que el servicio continúe recibiendo más solicitudes y siga respondiendo. Para obtener más información, consulte Background jobs guidance(Guía de trabajos en segundo plano).
Distribuir la carga de trabajo para tareas en segundo plano. Donde haya muchas tareas en segundo plano, o las tareas requieran mucho tiempo o recursos, distribuya el trabajo en varias unidades de proceso (por ejemplo, roles de trabajo o trabajos en segundo plano). Para ver una posible solución, consulte el patrón de consumidores simultáneos.
Considere la posibilidad de pasar a una arquitectura shared-nothing. Una arquitectura shared-nothing emplea nodos autosuficientes e independientes que no tienen ningún punto de contención (como servicios o almacenamiento compartidos). En teoría, este sistema puede escalarse casi indefinidamente. Si bien un enfoque shared-nothing no suele ser práctico en líneas generales para la mayoría de las aplicaciones, puede ofrecer oportunidades para diseñar haciauna mejor escalabilidad. Por ejemplo, evitar el uso del estado de sesión del lado servidor, la afinidad del cliente y el particionamiento de datos constituyen buenos ejemplos de cómo moverse hacia un arquitectura shared-nothing.
Administración de datos
Use particiones de datos. Divida los datos entre varias bases de datos y servidores de base de datos o diseñe la aplicación para que utilice servicios de almacenamiento de datos que puedan proporcionar estas particiones de forma transparente (entre los ejemplos se incluyen Base de datos elástica de Elastic Database de Azure SQL Database y Azure Table Storage). Este enfoque puede ayudar a maximizar el rendimiento y permitir un escalado más fácil. Existen diferentes técnicas de particionamiento, como horizontal, vertical y funcional. Puede utilizar una combinación de ellas para aprovechar al máximo el rendimiento mejorado de consultas, simplificar la escalabilidad, flexibilizar la administración, mejorar la disponibilidad y hacer coincidir el tipo de almacén con los datos que va a contener. Además, considere la posibilidad de utilizar distintos tipos de almacén de datos para tipos de datos diferentes, eligiendo los tipos en función de si están optimizados para el tipo de datos específico. Esto puede incluir el uso de almacenamiento de tablas, una base de datos de documentos o un almacén de datos de familia de columnas en lugar de, o como, una base de datos relacional. Para obtener más información, consulte Data partitioning guidance(Guía de creación de particiones de datos).
Diseñe para coherencia final. La coherencia final mejora la escalabilidad al reducir o suprimir el tiempo necesario para sincronizar los datos relacionados con particiones en varios almacenes. La desventaja es que los datos no son siempre coherentes cuando se leen y algunas operaciones de escritura pueden provocar conflictos. La coherencia final es perfecta para situaciones en las que se leen con frecuencia los mismos datos pero no se suelen escribir tan a menudo. Para más información, consulte Data consistency primer(Información básica sobre la coherencia de datos).
Reducir interacciones fragmentadas entre componentes y servicios. Evite diseñar interacciones en las que una aplicación deba realizar varias llamadas a un servicio (cada una de las cuales devuelve una pequeña cantidad de datos) en lugar de una única llamada que pueda devolver todos los datos. Siempre que sea posible, combine varias operaciones relacionadas en una única solicitud cuando la llamada se realiza a un servicio o componente que tiene una latencia apreciable. Esto facilita la supervisión del rendimiento y la optimización de operaciones complejas. Por ejemplo, utilice procedimientos almacenados en bases de datos para encapsular una lógica compleja y reducir el número de recorridos de ida y vuelta y el bloqueo de recursos.
Usar colas para equilibrar la carga de escrituras de datos de alta velocidad. Los incrementos en la demanda de un servicio pueden saturarlo y provocar errores cada vez mayores. Para evitar esto, considere la posibilidad de implementar el patrón de equilibrio de carga basado en colas. Utilice una cola que actúe como búfer entre una tarea y un servicio que invoca. Esto puede suavizar las cargas pesadas intermitentes que, de lo contrario, podrían provocar errores del servicio o tiempo de espera de la tarea.
Minimizar la carga en el almacén de datos. El almacén de datos es por lo general un cuello de botella en el procesamiento, un recurso costoso que no suele resultar fácil de escalar horizontalmente. Siempre que sea posible, quite lógica (por ejemplo, el procesamiento de documentos XML u objetos JSON) del almacén de datos y realice el procesamiento dentro de la aplicación. Por ejemplo, en lugar de pasar XML a la base de datos (que no sea como cadena opaca para el almacenamiento), serialice o deserialice el XML dentro de la capa de aplicación y páselo de forma nativa al almacén de datos. Suele ser mucho más fácil escalar horizontalmente la aplicación que el almacén de datos, por lo que debe intentar hacer la mayor cantidad posible del procesamiento de proceso intensivo dentro de la aplicación.
Minimizar el volumen de datos recuperados. Recupere solo los datos que necesita especificando las columnas y utilizando criterios para seleccionar las filas. Utilice parámetros de valores de tabla y el nivel de aislamiento adecuado. Use mecanismos, como etiquetas de entidad, para evitar tener que recuperar datos innecesariamente.
Utilizar la caché de forma agresiva. Utilice el almacenamiento en caché siempre que sea posible para reducir la carga de los recursos y servicios que generan o proporcionan datos. El almacenamiento en caché normalmente es adecuado para datos que sean relativamente estáticos o cuya obtención requiera un procesamiento considerable. El almacenamiento en caché debe realizarse en todos los niveles donde sea adecuado en cada capa de la aplicación, incluido el acceso a datos y la generación de la interfaz de usuario. Para obtener más información, consulte Caching Guidance(Guía de almacenamiento en caché).
Controlar el crecimiento y la retención de datos. La cantidad de datos almacenados por una aplicación crece con el tiempo. Este crecimiento aumenta los costos de almacenamiento y la latencia en el acceso a los datos, lo que afecta al rendimiento de la aplicación. Es posible archivar periódicamente algunos de los datos antiguos a los que ya no se tiene acceso o mover los datos a los que rara vez se tiene acceso a un almacén de larga duración que sea más económico, aunque la latencia de acceso sea mayor.
Optimizar los objetos de transferencia de datos (DTO) utilizando un formato binario eficaz. Los DTO se pasan entre las capas de una aplicación muchas veces. Al minimizar el tamaño se reduce la carga en los recursos y la red. Sin embargo, busque un equilibrio entre el ahorro y la sobrecarga de convertir los datos al formato requerido en cada ubicación donde se utiliza. Adopte un formato que tenga la máxima interoperabilidad para permitir la reutilización fácil de un componente.
Establecer control de caché. Diseñe y configure la aplicación para usar el almacenamiento en caché de salida o el almacenamiento en caché de fragmentos siempre que sea posible, a fin de minimizar la carga de procesamiento.
Habilitar el almacenamiento en caché del cliente. Las aplicaciones web deben habilitar la configuración de la caché en el contenido que puede almacenarse en caché. Esto suele estar desactivado de forma predeterminada. Configure el servidor a fin de proporcionar los encabezados de control de caché adecuados para habilitar el almacenamiento en caché de contenido en servidores proxy y clientes.
Usar Azure Blob Storage y Azure Content Delivery Network para reducir la carga en la aplicación. Considere la posibilidad de almacenar contenido público estático o relativamente estático como imágenes, recursos, scripts y hojas de estilos en el almacenamiento de blobs. Este enfoque libera la aplicación de la carga producida por la generación dinámica de este contenido para cada solicitud. Además, considere la posibilidad de usar Content Delivery Network para almacenar en caché este contenido y ofrecérselo a los clientes. Con Content Delivery Network puede mejorar el rendimiento en el cliente porque el contenido se proporciona desde el centro de datos geográficamente más cercano que contiene una caché de Content Delivery Network. Para más información, consulte el Content Delivery Network Guidance(Instrucciones sobre la red de entrega de contenido).
Optimizar y ajustar consultas SQL e índices. Algunas instrucciones o construcciones de T-SQL pueden tener un impacto negativo en el rendimiento, que se puede reducir al optimizar el código en un procedimiento almacenado. Por ejemplo, evite la conversión de tipos datetime a tipos varchar antes de comparar con un valor literal datetime. Utilice en su lugar funciones de comparación de fecha y hora. La falta de índices adecuados puede ralentizar la ejecución de la consulta. Si utiliza un marco de asignación relacional o de objetos, entienda cómo funciona y cómo puede afectar al rendimiento de la capa de acceso a datos. Para obtener más información, consulte Optimizar consultas.
Considerar la posibilidad de desnormalizar datos. La normalización de datos ayuda a evitar la duplicación y las incoherencias. Sin embargo, mantener varios índices, comprobar la integridad referencial, realizar varios accesos a fragmentos pequeños de datos y combinar tablas para volver a ensamblar los datos supone una sobrecarga que puede afectar al rendimiento. Piense si es aceptable tener un volumen de almacenamiento de información adicional y duplicación para reducir la carga en el almacén de datos. Además, vea si se puede confiar en la propia aplicación (que normalmente será más fácil de escalar) para realizar tareas, como la administración de la integridad referencial, a fin de reducir la carga en el almacén de datos. Para obtener más información, consulte Data partitioning guidance(Guía de creación de particiones de datos).
Implementación
Revisar los antipatrones de rendimiento. Consulte Antipatrones de rendimiento para aplicaciones en la nube para conocer los procedimientos comunes que es probable que provoquen problemas de escalabilidad cuando una aplicación está bajo presión.
Usar llamadas asincrónicas. Siempre que sea posible, use código asincrónico al obtener acceso a recursos o servicios que pueden estar limitados por ancho de banda de E/S o de red, o que tienen una latencia apreciable, para evitar bloquear el subproceso que realiza la llamada.
Evitar bloquear recursos y utilizar en su lugar un enfoque optimista. No bloquee nunca el acceso a recursos como almacenamiento u otros servicios que tienen una latencia apreciable, porque esto suele provocar un rendimiento deficiente. Utilice siempre enfoques optimistas para administrar operaciones simultáneas, como escribir en el almacenamiento. Use características de la capa de almacenamiento para administrar los conflictos. En aplicaciones distribuidas, los datos pueden ser solo coherentes en último lugar.
Comprimir datos altamente comprimibles a través de redes de alta latencia y ancho de banda bajo. En la mayoría de los casos, en una aplicación web, el volumen mayor de datos generados por la aplicación y que se pasan a través de la red son respuestas HTTP a solicitudes de cliente. La compresión HTTP puede reducir esto considerablemente, en especial para el contenido estático. De esta forma, se puede reducir el costo y la carga en la red, si bien la compresión de contenido dinámico aplica una carga fraccionalmente mayor en el servidor. En otros entornos más generalizados, la compresión de datos puede reducir el volumen de datos que se transmiten y minimizar el tiempo y los costos de transferencia, pero los procesos de compresión y descompresión se verán sobrecargados. Por lo tanto, la compresión solo se debe usar cuando haya un aumento de rendimiento demostrable. Otros métodos de serialización, como la codificación JSON o binaria, pueden reducir el tamaño de carga a la vez que tienen menos impacto en el rendimiento, mientras que XML es probable que lo aumente.
Minimizar el tiempo que las conexiones y los recursos están en uso. Mantenga los recursos y las conexiones solo si necesita utilizarlos. Por ejemplo, abra las conexiones lo más tarde posible y permita que se devuelven al grupo de conexiones en cuanto pueda. Adquiera recursos tan tarde como sea posible y deshágase de ellos tan pronto como sea posible.
Minimizar el número de conexiones necesarias. Las conexiones de servicio absorben recursos. Siempre que sea posible, limite el número necesario y asegúrese de que se reutilizan las conexiones existentes. Por ejemplo, después de realizar la autenticación, utilice la suplantación cuando sea necesario para ejecutar código como identidad específica. Esto puede ayudar a hacer un mejor uso del grupo de conexiones gracias a su reutilización.
Nota
Las API de algunos servicios reutilizan automáticamente las conexiones siempre que se sigan las directrices específicas del servicio. Es importante comprender las condiciones que permiten la reutilización de las conexiones para cada servicio que usa la aplicación.
Enviar solicitudes por lotes para optimizar el uso de red. Por ejemplo, envíe y lea mensajes en lotes al tener acceso a una cola y realice varias lecturas o escrituras como lote si tiene acceso a un almacenamiento o a una memoria caché. Esto puede ayudar a maximizar la eficiencia de los servicios y almacenes de datos al reducir el número de llamadas a través de la red.
Evitar un requisito para almacenar el estado de la sesión del servidor siempre que sea posible. La administración del estado de sesión del servidor requiere normalmente la afinidad del cliente (es decir, el enrutamiento de cada solicitud a la misma instancia de servidor), lo que afecta a la posibilidad de escalado del sistema. Lo más adecuado es diseñar clientes que no tengan estado con respecto a los servidores que utilizan. Sin embargo, si la aplicación debe mantener el estado de sesión, almacene datos confidenciales o grandes volúmenes de datos por cliente en una caché distribuida del servidor a la que puedan tener acceso todas las instancias de la aplicación.
Optimizar esquemas de almacenamiento de tablas. Al utilizar almacenes de tablas que necesitan pasar y procesar nombres de tablas y columnas, como el almacenamiento de tablas de Azure, considere la posibilidad de usar nombres más cortos para reducir esta sobrecarga. Sin embargo, no sacrifique la legibilidad o la facilidad de administración usando nombres excesivamente compactos.
Crear dependencias de recursos durante la implementación o al iniciar la aplicación. Evite llamadas repetidas a métodos que prueban la existencia de un recurso y que luego crean el recurso si no existe. Métodos como CloudTable.CreateIfNotExists y CloudQueue.CreateIfNotExists de la biblioteca de clientes de almacenamiento de Azure siguen este patrón. Estos métodos pueden suponer una sobrecarga considerable si se invocan antes de cada acceso a una tabla de almacenamiento o una cola de almacenamiento. En su lugar:
- Cree los recursos necesarios al implementar la aplicación o al iniciarla por primera vez (es aceptable una única llamada a CreateIfNotExists para cada recurso en el código de inicio para un rol web o de trabajo). Sin embargo, asegúrese de controlar las excepciones que pueden surgir si el código intenta obtener acceso a un recurso que no existe. En estas situaciones, debe registrar la excepción y posiblemente avisar a un operador de que falta un recurso.
- En algunas circunstancias, puede ser adecuado crear el recurso que falta como parte del código de control de excepciones. Sin embargo, debe tener cuidado al adoptar este enfoque dado que la no existencia del recurso podría ser indicio de un error de programación (un nombre de recurso mal escrito, por ejemplo) o de algún otro problema de nivel de infraestructura.
Uso de marcos de trabajo ligeros. Elija cuidadosamente las API y los marcos de trabajo que utiliza para minimizar el uso de recursos, el tiempo de ejecución y la carga general de la aplicación. Por ejemplo, el uso de Web API para controlar las solicitudes de servicio puede reducir la superficie de la aplicación y aumentar la velocidad de ejecución, pero es posible que no resulte adecuado para escenarios avanzados en los que se requieren funciones de Windows Communication Foundation adicionales.
Posibilidad de reducir el número de cuentas de servicio. Por ejemplo, utilice una cuenta específica para obtener acceso a recursos o servicios que imponen un límite de conexiones o consiga un mejor rendimiento manteniendo menos conexiones. Este enfoque es común para servicios como bases de datos pero puede afectar a la capacidad de auditar con precisión las operaciones debido a la suplantación del usuario original.
Realización de pruebas de generación de perfiles de rendimiento y de carga durante el desarrollo, como parte de las rutinas de prueba y antes de la versión final para asegurarse de que la aplicación funciona y escala como corresponde. Estas pruebas deben realizarse en el mismo tipo de hardware que la plataforma de producción y con los mismos tipos y cantidades de datos y de carga de usuarios que se encontrarán en producción. Para más información, consulte Testing the performance of a cloud service(Prueba de rendimiento de un servicio en la nube).