Saltar al contenido principal

“La pandemia de COVID-19 ha cambiado el significado de trabajar, estudiar y socializar. Al igual que tantos otros, yo también he recurrido a Microsoft Teams para mantener el contacto con mis compañeros. En esta entrada de blog, nuestros amigos del grupo de producto de Microsoft Teams (Rish Tandon, vicepresidente corporativo, Aarthi Natarajan, director de ingeniería de grupos, y Martin Taillefer, arquitecto), comparten algunas de las cosas que han aprendido sobre cómo administrar y escalar una aplicación de productividad empresarial segura”. Mark Russinovich, director de tecnología, Azure.


 

La escala, la resistencia y el rendimiento no se adquieren de un día para otro. Se requiere una inversión sostenida y deliberada, un día tras otro, y una mentalidad que dé prioridad al rendimiento para crear productos que satisfagan a los usuarios. Desde su lanzamiento en 2017, Teams ha experimentado un fuerte crecimiento, con 13 millones de usuarios diarios en julio de 2019 y 20 millones en noviembre de 2019. En abril, dijimos que Teams tiene más de 75 millones de usuarios activos diarios, 200 millones de participantes en reuniones diarias y 4100 millones de minutos de reuniones al día. Creíamos que estábamos habituados al trabajo continuo necesario para modificar la escala del servicio a ese ritmo, dado el rápido crecimiento que Teams había experimentado hasta la fecha. La COVID-19 cuestionó ese supuesto. ¿Nos daría esta experiencia la capacidad de mantener el servicio en ejecución durante un período de crecimiento que antes era inimaginable?

Una base sólida

Teams se basa en una arquitectura de microservicios, con varios cientos de microservicios que funcionan de forma cohesionada para ofrecer las numerosas características del producto, como la mensajería, las reuniones, los archivos, el calendario y las aplicaciones. El uso de microservicios permite que los equipos de cada componente trabajen y publiquen sus cambios de forma independiente.

Azure es la plataforma en la nube que sustenta todos los servicios en la nube de Microsoft, incluido Microsoft Teams. Nuestras cargas de trabajo se ejecutan en instancias de Azure Virtual Machines (VM), los servicios más antiguos se implementan a través de Azure Cloud Services y las versiones más recientes en Azure Service Fabric. Nuestra pila de almacenamiento principal es Azure Cosmos DB y algunos servicios utilizan Azure Blob Storage. Contamos con Azure Cache for Redis para aumentar el rendimiento y la resistencia. Aprovechamos Traffic Manager y Azure Front Door para redirigir el tráfico hacia donde queremos que vaya. Usamos Event Hubs y Queue Storage para la comunicación y nos basamos en Azure Active Directory para administrar los inquilinos y usuarios.

 

	Diagrama que muestra que Azure es la plataforma que sustenta los servicios de Teams y los servicios principales de Office 365

 

Aunque esta entrada se centra principalmente en nuestro back-end en la nube, merece la pena resaltar que las aplicaciones cliente de Teams también usan patrones de diseño y marcos modernos, lo que proporciona una experiencia de usuario enriquecida y soporte para experiencias sin conexión o con una conexión intermitente. La capacidad básica de actualizar nuestros clientes rápidamente y junto con el servicio es un factor clave para una iteración rápida. Si desea conocer mejor nuestra arquitectura, vea esta sesión de Microsoft Ignite 2019.

Desarrollo ágil

Nuestras canalizaciones de CI/CD se basan en Azure Pipelines. Usamos una estrategia de implementación basada en anillos con puertas que se basan en una combinación de pruebas automatizadas completas y señales de telemetría. Nuestras señales de telemetría se integran con las canalizaciones de administración de incidentes para proporcionar alertas sobre las métricas definidas por el cliente y por el servicio. Usamos principalmente Azure Data Explorer para los análisis.

Además, usamos una canalización de experimentación con cuadros de mandos que evalúan el comportamiento de las características con respecto a las métricas clave del producto, como la tasa de bloqueos, el consumo de memoria, la capacidad de respuesta de la aplicación, el rendimiento y la interacción con el usuario. Esto nos ayuda a saber si las características nuevas están funcionando como esperamos.

Todos nuestros servicios y clientes utilizan un servicio de administración de la configuración centralizado. Este servicio proporciona el estado de configuración para activar y desactivar las características del producto, ajustar los valores del período de vida de la memoria caché, controlar las frecuencias de las solicitudes de red y establecer puntos de conexión de red de contacto para las API. Esto proporciona un marco flexible para lanzar componentes nuevos “en la sombra” y realizar pruebas A/B para poder medir con precisión el impacto de los cambios y comprobar que sean seguros y eficaces para todos los usuarios.

Principales estrategias de resistencia

Utilizamos varias estrategias de resistencia en nuestra flota de servicios:

  • Sistemas tolerantes a errores de tipo activo/activo. Un sistema tolerante a errores activo-activo se define como dos (o más) rutas de acceso heterogéneas funcionalmente independientes, donde cada ruta no solo atiende el tráfico activo constante, sino que también tiene la capacidad de asumir el 100 % del tráfico esperado, a la vez que aprovecha la selección de ruta de acceso del cliente y el protocolo para llevar a cabo una conmutación por error fluida. Adoptamos esta estrategia para los casos en los que hay un dominio de error muy grande o un impacto en el cliente con un costo razonable que justifique la creación y el mantenimiento de sistemas heterogéneos. Por ejemplo, usamos el sistema DNS de Office 365 para todos los dominios de cliente visibles externamente. Además, los datos de clase CDN estáticos se hospedan tanto en Azure Front Door como en Akamai.
  • Memorias caché optimizadas para resistir. Aprovechamos ampliamente las memorias caché entre los componentes, tanto para el rendimiento como para la resistencia. Las memorias caché ayudan a reducir la latencia media y proporcionan un origen de datos en el caso de que un servicio de nivel inferior no esté disponible. El mantenimiento de los datos en la memoria caché durante mucho tiempo conlleva problemas de actualización de esos datos, pero, aún así, es la mejor defensa frente a errores en niveles inferiores. Nos centramos en el tiempo de actualización (TTR) de los datos de la memoria caché, así como en el período de vida (TTL). Al establecer un TTL largo y un valor de TTR más corto, podemos ajustar el nivel de actualización que se mantiene en los datos respecto a la cantidad de tiempo que queremos retenerlos por si se produce un error en una dependencia de nivel inferior.
  • Disyuntor. Este es un patrón de diseño común que impide que un servicio realice una operación que es probable que produzca un error. Proporciona una oportunidad para que el servicio de nivel inferior se recupere sin saturarse con solicitudes de reintento. También mejora la respuesta de un servicio cuando sus dependencias están teniendo problemas, lo que ayuda al sistema a ser más tolerante a las condiciones de error.
  • Aislamiento compartimentado. Particionamos algunos de los servicios críticos en implementaciones totalmente aisladas. Si algo va mal en una implementación, el aislamiento compartimentado está diseñado para ayudar a las otras implementaciones a seguir funcionando. Esta mitigación mantiene la funcionalidad para el máximo de clientes posible.
  • Limitación de la frecuencia a nivel de API. Nos aseguramos de que los servicios críticos pueden limitar las solicitudes a nivel de API. Estos límites de la frecuencia se administran en el sistema de administración de configuración centralizado que he mencionado en los párrafos anteriores. Esta funcionalidad nos ha permitido limitar la frecuencia en las API no críticas durante la pandemia de COVID-19.
  • Patrones de reintento eficientes. Comprobamos que todos los clientes de las API implementen una lógica de reintentos eficiente para evitar picos de tráfico cuando se producen errores en la red.
  • Tiempos de espera. Un uso coherente de la semántica de tiempos de espera impide que el trabajo se detenga cuando surgen problemas en una dependencia de nivel inferior.
  • Control fluido de los errores de red. Hemos realizado inversiones a largo plazo para mejorar la experiencia del cliente cuando la conexión es deficiente o nula. Las principales mejoras en esta área se lanzaron para producción justo cuando comenzó la pandemia de COVID-19. Esto permitió que nuestro cliente proporcionase una experiencia constante, independientemente de la calidad de la red.

Si conoce los patrones de diseño en la nube de Azure, es posible que muchos de estos conceptos le resulten familiares.  También usamos mucho la biblioteca Polly en nuestros microservicios, que proporciona implementaciones para algunos de estos patrones.

Nuestra arquitectura ha estado funcionando bien para nosotros. El uso de Teams ha crecido cada mes y la plataforma ha modificado la escala fácilmente para hacer frente a la demanda. Sin embargo, la escalabilidad no es algo que “se establece y te olvidas”. Necesita atención continua para abordar los comportamientos emergentes que se manifiestan en cualquier sistema complejo.

Cuando se comenzó a imponer el confinamiento por la COVID-19 en todo el mundo, tuvimos que aprovechar la flexibilidad arquitectónica integrada de nuestro sistema y usar todos los recursos posibles para responder con eficacia a una demanda que crecía rápidamente.

Previsión de la capacidad

Al igual que con cualquier otro producto, creamos e iteramos modelos constantemente para anticipar dónde se producirá el crecimiento, tanto en términos de número de usuarios como de patrones de uso. Los modelos se basan en datos históricos, patrones cíclicos, clientes grandes que se incorporan y otros tipos de indicadores.

Cuando comenzó la pandemia, vimos claro que nuestros modelos de previsión anteriores se estaban quedando obsoletos rápidamente, por lo que tuvimos que crear otros nuevos que tuvieran en cuenta el enorme crecimiento de la demanda global. Estábamos viendo nuevos patrones de uso entre los usuarios que ya teníamos, un uso nuevo de usuarios que ya existían pero estaban inactivos y muchos usuarios nuevos que se incorporaban al producto, todo al mismo tiempo. Además, tuvimos que tomar decisiones de dotación de recursos de forma acelerada para hacer frente a posibles cuellos de botella en la capacidad de proceso y de red. Usamos varias técnicas de modelado de predicción (ARIMA, aditiva, multiplicativa, logarítmica). Agregamos también límites básicos por países para evitar una previsión excesiva. Ajustamos los modelos intentando comprender los patrones de inflexión y crecimiento en función del uso en cada sector y zona geográfica. Incorporamos orígenes de datos externos, incluido el estudio de Johns Hopkins sobre las fechas del impacto de la COVID-19 por países, para aumentar la previsión de carga máxima para las regiones con un cuello de botella.

A lo largo del proceso, pecamos de precavidos y favorecimos un exceso de aprovisionamiento, pero, a medida que los patrones de uso se estabilizaron, también redujimos la escala según fue necesario.

Escalado de los recursos de proceso

En general, diseñamos Teams para soportar desastres naturales. El uso de varias regiones de Azure nos ayuda a mitigar el riesgo, no solo por un problema en un centro de datos, sino también por las interrupciones que se puedan producir en un área geográfica principal. Sin embargo, esto significa que aprovisionamos más recursos que estaban preparados para asumir la carga de una región afectada durante una eventualidad de este tipo. Para escalar los recursos horizontalmente, ampliamos rápidamente la implementación de cada microservicio crítico a más regiones en cada área geográfica principal de Azure. Al aumentar el número total de regiones por área geográfica, redujimos la capacidad total de reserva que cada región necesitaba mantener para absorber una carga de emergencia, lo que redujo nuestra necesidad de capacidad total. Al abordar la carga a esta nueva escala, descubrimos varias formas de mejorar nuestra eficacia:

  • Observamos que la reimplementación de algunos de nuestros microservicios para favorecer un mayor número de clústeres de proceso más pequeños nos permitió evitar algunas consideraciones de escalado por clúster, ayudó a acelerar las implementaciones y nos proporcionó un equilibrio de carga más pormenorizado.
  • Antes dependíamos de los tipos de máquina virtual (VM) específicos que usábamos para los diferentes microservicios. Al ser más flexibles en cuanto al tipo de máquina virtual o la CPU y centrarnos en la capacidad global de proceso o de memoria, pudimos hacer un uso más eficaz de los recursos de Azure en cada región.
  • Encontramos oportunidades de optimización en el propio código del servicio. Por ejemplo, algunas mejoras sencillas produjeron una reducción considerable de la cantidad de tiempo de CPU que se emplea para generar avatares (las pequeñas burbujas con las iniciales que se usan cuando no hay imágenes del usuario disponibles).

Optimización de la red y el enrutamiento

La mayor parte del consumo de la capacidad de Teams se produce durante el día en cualquier área geográfica de Azure, es decir, hay recursos inactivos durante la noche. Implementamos estrategias de enrutamiento para aprovechar esta capacidad inactiva (respetando siempre los requisitos de cumplimiento normativo y residencia de los datos):

  • El trabajo en segundo plano no interactivo se migra de forma dinámica a la capacidad que está inactiva en ese momento. Esto se hace programando rutas específicas de la API en Azure Front Door para asegurar que el tráfico llegue al lugar adecuado.
  • El tráfico de las llamadas y las reuniones se dirigió hacia varias regiones para controlar el gran aumento. Usamos Azure Traffic Manager para distribuir la carga de forma eficaz, aprovechando los patrones de uso observados. También trabajamos para crear runbooks que llevaron a cabo el equilibrio de carga en función del momento del día para evitar la limitación de la red de área extensa (WAN).

Parte del tráfico del cliente de Teams terminó en Azure Front Door. Sin embargo, a medida que implementamos más clústeres en más regiones, vimos que los nuevos clústeres no estaban obteniendo suficiente tráfico. Esto se debió a la distribución de la ubicación de los usuarios y la ubicación de los nodos de Azure Front-Door. Para solucionar esta distribución irregular del tráfico, usamos la capacidad Azure Front Door para redirigir el tráfico a nivel de país. En el siguiente ejemplo, puede ver que logramos mejorar la distribución del tráfico después de redirigir el tráfico adicional de Francia a la región Oeste de Reino Unido para uno de nuestros servicios.

	Grafo que muestra una distribución mejorada del tráfico después de redirigir el tráfico adicional de Francia a la región Oeste de Reino Unido 
Figura 1: Distribución mejorada del tráfico después de redirigir el tráfico entre regiones.

Mejoras de la memoria caché y el almacenamiento

Usamos muchas memorias caché distribuidas. Un montón de memorias caché de gran tamaño distribuidas. A medida que aumentaba el tráfico, aumentaba la carga en las memorias caché en un punto donde las memorias caché individuales no se escalarían. Implementamos algunos cambios sencillos con un impacto importante en el uso de la memoria caché:

  • Comenzamos a almacenar el estado de la memoria caché en un formato binario en lugar de JSON sin formato. Para esto usamos el formato de búfer de protocolo.
  • Empezamos a comprimir los datos antes de enviarlos a la memoria caché. Usamos la compresión LZ4 por su excelente relación velocidad-compresión.

Conseguimos una reducción del 65 % del tamaño de la carga, un 40 % de reducción del tiempo de deserialización y un 20 % de reducción del tiempo de serialización. Un logro absoluto.

La investigación reveló que algunas de nuestras memorias caché tenían una configuración de TTL excesivamente agresiva que dio lugar a una expulsión exagerada de datos que no era necesaria. Al aumentar esos valores de TTL, se redujo la latencia media y la carga en los sistemas de nivel inferior.

Degradación intencionada (restricciones en las características)

Como no sabíamos realmente hasta dónde tendríamos que llegar, decidimos que era prudente poner en marcha mecanismos que nos permitieran reaccionar rápidamente ante los picos de demanda inesperados con el fin de ganar tiempo para poner en línea más capacidad de Teams.

No todas las características tienen la misma importancia para nuestros clientes. Por ejemplo, el envío y la recepción de mensajes es más importante que la capacidad de ver que otra persona está escribiendo un mensaje en ese momento. Por este motivo, desactivamos el indicador de escritura durante dos semanas mientras trabajábamos para escalar verticalmente nuestros servicios. Esto redujo el tráfico máximo en un 30 % en algunas partes de nuestra infraestructura.

Normalmente usamos una captura previa agresiva en muchas capas de nuestra arquitectura para que los datos necesarios estén a mano, lo que reduce la latencia media global. Sin embargo, la captura previa puede resultar costosa, ya que supone una cantidad de trabajo desperdiciado cuando se capturan datos que nunca se usarán y, además, requiere recursos de almacenamiento para los datos capturados previamente. En algunos casos, decidimos deshabilitar la captura previa, lo que liberó capacidad en algunos de nuestros servicios a costa de una latencia superior. En otros casos, aumentamos la duración de los intervalos de sincronización de la captura previa. Un ejemplo de esta práctica fue la supresión de la captura previa del calendario en el dispositivo móvil, que redujo el volumen de solicitudes en un 80 %:
 

	Grafo que muestra que la supresión de la captura previa del calendario en el dispositivo móvil redujo el volumen de solicitudes en un 80 %
Figura 2: Deshabilitación de la captura previa de detalles de eventos del calendario en el dispositivo móvil.

Administración de incidentes

Aunque tenemos un proceso de administración de incidentes consolidado que usamos para el seguimiento y el mantenimiento del sistema, esta experiencia era diferente. No solo nos estábamos enfrentando a un aumento enorme del tráfico, sino que nuestros ingenieros y compañeros estaban pasando también por situaciones personales y emocionales difíciles, al tiempo que se adaptaban a trabajar desde casa.

Para asegurarnos de que no solo atendíamos a nuestros clientes, sino también a nuestros ingenieros, realizamos algunos cambios:

  • Cambiamos las rotaciones de la administración de incidentes de una cadencia semanal a una cadencia diaria.
  • Cada ingeniero de guardia tenía al menos 12 horas de descanso entre turno y turno.
  • Incorporamos más administradores de incidentes de toda la compañía.
  • Aplazamos todos los cambios que no eran críticos de todos nuestros servicios.

Estos cambios nos permitieron asegurar que todos los administradores de incidentes y los ingenieros de guardia tuvieran tiempo suficiente para dedicarse a sus necesidades en casa, a la vez que atendían las demandas de nuestros clientes.

El futuro de Teams

Es fascinante mirar atrás y preguntarse cómo habría sido esta situación si se hubiera producido hace solo unos años. Habría sido imposible escalar la capacidad como hicimos sin la informática en la nube. Lo que hoy podemos hacer cambiando solo algunos archivos de configuración antes habría requerido la compra de nuevos equipos o incluso de nuevos edificios. A medida que se estabiliza la situación de escalado actual, volvemos a mirar hacia el futuro. Creemos que hay muchas oportunidades de mejora de nuestra infraestructura:

  • Tenemos previsto pasar de las implementaciones basadas en máquinas virtuales a implementaciones basadas en contenedores con Azure Kubernetes Service, que esperamos que reduzca nuestros costos operativos, aumente nuestra agilidad y nos alinee con el sector.
  • Esperamos minimizar el uso de REST en favor de protocolos binarios más eficientes, como gRPC. Reemplazaremos varias instancias de sondeo de todo el sistema por modelos más eficientes basados en eventos.
  • Estamos adoptando sistemáticamente prácticas de ingeniería del caos con el fin de asegurar que todos los mecanismos que implementemos para que nuestro sistema sea confiable sean siempre totalmente funcionales y estén listos para pasar a la acción.

Al mantener nuestra arquitectura en consonancia con los enfoques del sector y aprovechar las prácticas recomendadas del equipo de Azure, cuando tuvimos que pedir asistencia, los expertos nos pudieron ayudar rápidamente a solucionar problemas que abarcaban desde el análisis de datos y la supervisión hasta la optimización del rendimiento y la administración de incidentes. Estamos muy agradecidos por la actitud abierta de nuestros compañeros en Microsoft y de la amplia comunidad de desarrollo de software. Aunque las arquitecturas y las tecnologías son importantes, es el equipo humano el que mantiene el buen estado de los sistemas.

 


Entrada relacionada: Azure responde a la COVID-19.
Artículo relacionado:
Aumento de la capacidad de Azure para ayudar a los clientes; Microsoft durante la pandemia de COVID-19.

  • Explore

     

    Let us know what you think of Azure and what you would like to see in the future.

     

    Provide feedback

  • Build your cloud computing and Azure skills with free courses by Microsoft Learn.

     

    Explore Azure learning


Join the conversation