¿Cómo acelerar rápida y fácilmente el acceso a las aplicaciones API?

La respuesta es simple: usar herramientas probadas como el almacenamiento en caché y el escalado horizontal. Debemos decir de inmediato que estas no son las únicas herramientas, pero la mayoría de las veces son los enfoques clásicos probados los que resultan ser los más efectivos incluso en las condiciones modernas. Veamos un ejemplo práctico.



Sobre el problema original



La plataforma de video PREMIER, como corresponde a un recurso moderno, ha creado un servicio de recomendación para sus clientes basado en el aprendizaje automático. Muchos usuarios recurren a la plataforma de video - alrededor de un millón al día, PREMIER es muy popular - y las llamadas llegan tanto a través de un formulario web, desde aplicaciones para dispositivos móviles como desde Smart TV.



Los datos iniciales sobre cuya base funciona el aprendizaje automático de nuestro servicio se almacenan en el DBMS de ClickHouse columnar. De acuerdo con el cronograma, los datos se procesan en segundo plano para construir modelos (que se utilizarán para emitir recomendaciones finales). Los resultados del cálculo se guardan en el DBMS relacional de PostgreSQL.



La solución al problema de la interacción rápida entre el servidor de aplicaciones y el cliente en poco tiempo sigue siendo relevante siempre. Para garantizar la velocidad de trabajo requerida, y el tiempo de respuesta no debería haber sido superior a 50 ms, tuvimos que optimizar la estructura de la base de datos relacional, implementar el almacenamiento en caché y el escalado horizontal. Hablaremos de algunas de las técnicas ahora.







Acerca del almacenamiento en caché



El almacenamiento en caché es una técnica de optimización común. Creamos un almacenamiento intermedio adicional, más rápido que el principal, y colocamos allí los datos más populares. El acceso a los datos almacenados en caché se acelera drásticamente y esto aumenta significativamente la velocidad de la aplicación. Al desarrollar una aplicación de alta carga, el almacenamiento en caché es la opción más común para optimizar el rendimiento de la aplicación sin expandir los recursos de hardware. El almacenamiento en caché puede generar algunos ahorros en términos de uso de recursos para volver a generar la misma salida para la misma entrada.



La capacidad de la caché es limitada: cuanto más rápido es el almacenamiento, más caro es, por lo que debe usarse de manera eficiente. Por supuesto, teóricamente puede prescindir del almacenamiento en caché si su almacenamiento principal es lo suficientemente rápido. Pero será económicamente rentable, ya que Tendrá que llevar a cabo una actualización de hardware significativa, que a menudo se reduce a un aumento de RAM y / o al reemplazo de discos de HDD a SSD. Aquellos. aumentar significativamente los requisitos de infraestructura, lo que afectará los parámetros económicos de toda la aplicación que se está creando. Sin el almacenamiento en caché probado por el tiempo, en la mayoría de los casos, difícilmente será posible crear un producto masivo.



Sin embargo, agregar una capa de almacenamiento en caché no resuelve automáticamente todos los problemas. También es necesario reflexionar sobre las reglas para su cumplimiento, que dependen de las características de la tarea, que pueden cambiar según la situación y a medida que se desarrolla el servicio. Y tenga en cuenta que esto no es una panacea, sino solo un remedio que aliviará los síntomas de problemas de desempeño en partes específicas de su aplicación. Si la aplicación tiene problemas de arquitectura profundos, un entorno de ejecución mediocre, es más probable que el almacenamiento en caché agregue problemas.



Hay varias opciones sobre dónde almacenar los recursos en caché: localmente: en la instancia del cliente en el navegador, en un servicio CDN de terceros, en el lado de la aplicación. Hablaremos sobre el almacenamiento en caché en la aplicación. La memoria de proceso de la aplicación es quizás la opción de almacenamiento en caché de datos más común con la que se encontrará. Sin embargo, esta solución tiene sus inconvenientes, ya que la memoria está asociada con un proceso que realiza una tarea específica. Esto es aún más importante si planea escalar horizontalmente la aplicación, ya que la memoria no se asigna entre procesos, es decir, no estará disponible en otros procesos, como los responsables del procesamiento asincrónico. Sí, el almacenamiento en caché funciona, pero realmente no lo aprovechamos al máximo.



Almacenamiento en caché de recomendación







Volviendo al proyecto, entendemos la necesidad de una solución de almacenamiento en caché centralizada. Para una caché compartida, puede usar, por ejemplo, Memcached: si una aplicación está conectada a la misma instancia, puede usarla en muchos procesos. Por un lado, Memcached es una solución simple y conveniente, por otro lado, es bastante limitada cuando se trata de una gestión precisa de invalidación, tipificación de datos y consultas más complejas al almacén de datos en caché. Ahora, de hecho, el almacenamiento de Redis se ha convertido en el estándar en las tareas de almacenamiento en caché, que carece de las desventajas de Memcached.



Redis es un almacén rápido de valores clave. Aumenta la eficiencia de trabajar con datos, porque es posible definir la estructura. proporciona un control detallado sobre la discapacidad y la preferencia, lo que le permite elegir entre seis políticas diferentes. Redis admite tanto la preferencia perezosa como la ansiosa, así como la preferencia temporal. El uso de estructuras de datos de Redis puede proporcionar optimizaciones tangibles, según las entidades comerciales. Por ejemplo, en lugar de almacenar objetos como cadenas serializadas, los desarrolladores pueden usar una estructura de datos hash para almacenar y manipular campos y valores por clave. Hash elimina la necesidad de recuperar la cadena completa, deserializarla y reemplazarla en la caché con un nuevo valor en cada actualización, lo que significa un menor consumo de recursos y un mejor rendimiento. Otras estructuras de datos,Las "hojas", los "conjuntos", los "conjuntos ordenados", los "hiperregistros", los "mapas de bits" y los "índices geográficos" sugeridos por Redis se pueden utilizar para implementar escenarios aún más complejos. Los conjuntos ordenados para analizar datos de series de tiempo ofrecen una complejidad y un volumen reducidos en el procesamiento y la transferencia de datos. La estructura de datos de HyperLogLog se puede utilizar para contar elementos únicos en un conjunto utilizando solo una pequeña cantidad de memoria persistente, específicamente 12 KB para cada HyperLogLog (más unos pocos bytes para la clave en sí). Una parte significativa de los cerca de 200 comandos disponibles en Redis están dedicados a operaciones de procesamiento de datos e incrustación de lógica en la propia base de datos usando scripts Lua.Los comandos integrados y las capacidades de scripting brindan flexibilidad para procesar datos directamente en Redis sin tener que enviar datos a través de la red a su aplicación, lo que reduce la sobrecarga de implementar lógica de almacenamiento en caché adicional. Tener los datos de la caché disponibles inmediatamente después de un reinicio puede reducir significativamente el tiempo de preparación de la caché y aliviar la carga de volver a calcular el contenido de la caché desde el almacén de datos principal. Hablaremos sobre las características de la configuración de Redis y las perspectivas de la agrupación en clústeres en los siguientes artículos.Hablaremos sobre las características de la configuración de Redis y las perspectivas de la agrupación en clústeres en los siguientes artículos.Hablaremos sobre las características de la configuración de Redis y las perspectivas de la agrupación en clústeres en los siguientes artículos.



Después de elegir una herramienta de almacenamiento en caché, el problema principal parecía ser la sincronización de los datos almacenados en el caché y los datos almacenados en la aplicación. Hay diferentes formas de sincronizar datos según la lógica empresarial de su aplicación. En nuestro caso, la dificultad radicaba en la creación de un algoritmo de invalidación de datos. Los datos, colocados en la caché, se almacenan allí por un tiempo limitado, siempre que exista la necesidad de su uso en la situación actual, o al menos la probabilidad de que ocurra. A medida que se desarrolla la situación, deben dejar espacio para otros datos que, en las condiciones cambiadas, son más necesarios. La tarea principal en este caso es la selección de criterios por los cuales los datos serán desalojados de la caché. Muy a menudo, este es el momento de la relevancia de los datos, pero vale la pena recordar otros parámetros: sobre el volumen, la clasificación (siempre que sea igual, con tolerancias,vida útil), categoría (datos principales o auxiliares), etc.



La forma básica y común de mantener los datos actualizados es la obsolescencia del tiempo. También utilizamos este método, teniendo en cuenta la actualización periódica centralizada de los datos de la aplicación de recomendación. Sin embargo, no todo es tan simple como parece a primera vista: en este caso, es extremadamente importante monitorear el ranking para que solo los datos realmente populares entren en la caché. Esto es posible gracias a la recopilación de estadísticas de consultas y la implementación de "precalentamiento de datos", es decir precarga de datos en la caché al inicio de la aplicación. La gestión del tamaño de la caché también es un aspecto importante del almacenamiento en caché. En nuestra aplicación se generan alrededor de millones de recomendaciones, por lo que no es realista almacenar todos estos datos en la caché. La administración del tamaño de la caché se realiza eliminando datos de la caché para dejar espacio para nuevos datos.Hay varios métodos estándar: TTL, FIFO, LIFO, último acceso. Por ahora, usamos TTL, porque La instancia de Redis no va más allá de la memoria asignada y los recursos de disco.



Recuerde que el caché es diferente. La mayoría de las veces, hay dos categorías: escritura completa y diferida. En el primer caso, la escritura se realiza de forma sincrónica tanto en la memoria caché como en el almacenamiento principal. En el segundo, inicialmente, la escritura se realiza solo en la caché, y la escritura en el almacenamiento principal se aplazará hasta que los datos cambiados sean reemplazados por otro bloque de caché. La caché de escritura diferida es más difícil de implementar, ya que requiere monitorear los datos en la caché para su posterior escritura en el almacén principal cuando se eliminan de la caché. Usamos la primera opción junto con el procedimiento de calentamiento de caché. Tenga en cuenta que "calentar" en sí mismo es una tarea importante y difícil, nuestra solución se discutirá en los siguientes artículos.



Escala horizontalmente en una aplicación de recomendación



Para proporcionar acceso PREMIER a la aplicación de recomendación, utilizamos el protocolo HTTP, que suele ser la opción principal en la interacción de aplicaciones. Es adecuado para organizar la interacción entre aplicaciones, especialmente si se utilizan Kubernetes e Ingress Controller como entorno de infraestructura. El uso de Kubernetes facilita el escalado. La herramienta es capaz de equilibrar automáticamente la solicitud entre pods en el clúster para un funcionamiento uniforme, lo que facilita a los desarrolladores la ampliación. El módulo Ingress Controller es responsable de esto, que define las reglas para la conexión externa a aplicaciones en Kubernetes. De forma predeterminada, las aplicaciones de Kubernetes no son accesibles desde la red externa. Para proporcionar acceso externo a las aplicaciones, debe declarar un recurso de Ingress que admita el equilibrio automático.Estamos usando Nginx Ingress Controller, que admite SSL / TLS, reglas de reescritura de URI y VirtualServer y VirtualServerRoute para enrutar solicitudes a diferentes aplicaciones según el URI y el encabezado del host.



La configuración básica en el controlador de ingreso le permite usar solo las funciones básicas de Nginx; esto es enrutamiento basado en el host y la ruta, y funciones adicionales como reglas de reescritura de URI, encabezados de respuesta adicionales, tiempos de espera de conexión no están disponibles. Las anotaciones aplicadas al recurso de Ingress le permiten usar las funciones de Nginx en sí (generalmente disponibles a través de la configuración de la aplicación) y cambiar el comportamiento de Nginx para cada recurso de Ingress.



Planeamos utilizar Nginx Ingress Controller no solo en el proyecto que estamos considerando ahora, sino también en otras aplicaciones, de las que hablaremos más adelante. Hablaremos de esto en los siguientes artículos.







Riesgos y consecuencias del uso del almacenamiento en caché



Cualquier equipo que trabaje para optimizar una aplicación de uso intensivo de datos tendrá muchas preguntas sobre cómo optimizar el almacenamiento en caché. Al mismo tiempo, el problema del almacenamiento en caché no se puede resolver "de una vez por todas"; con el tiempo, surgen varios problemas. Por ejemplo, a medida que crece su base de usuarios, es posible que encuentre estadísticas contradictorias sobre la popularidad de los datos por categoría, y será necesario abordar este problema.



Si bien el almacenamiento en caché es una herramienta poderosa para acelerar una aplicación, no es la única solución. Dependiendo de su situación, es posible que deba optimizar la lógica de la aplicación, cambiar la pila y el entorno de infraestructura. También debe tener cuidado al validar requisitos no funcionales. Quizás, después de discutirlo con el propietario del producto, se exagerarán los requisitos de la aplicación. Cabe recordar que cada una de las soluciones tiene sus propias características.



Los riesgos de proporcionar datos desactualizados, el aumento de la complejidad general de la solución y la probabilidad de introducir errores latentes deben abordarse antes de aplicar cualquier método de almacenamiento en caché en un proyecto. Después de todo, en este caso, el almacenamiento en caché solo complicará la resolución de problemas, sino que simplemente ocultará los problemas de rendimiento y escalabilidad: ¿son lentas las consultas de la base de datos? - la caché da como resultado un almacenamiento rápido. ¿Son lentas las llamadas a la API? - caché de resultados en el cliente. Esto se debe a que la complejidad del código que administra el almacenamiento en caché aumenta significativamente a medida que aumenta la complejidad de la lógica empresarial.



En las primeras versiones de la aplicación, el almacenamiento en caché realmente tiene un efecto tangible inmediatamente después de la implementación. Después de todo, la lógica empresarial también es simple: usted guarda el valor y lo recupera. La invalidación es fácil porque las dependencias entre entidades comerciales son triviales o inexistentes. Sin embargo, con el tiempo, para mejorar el rendimiento, necesitará almacenar en caché un número cada vez mayor de entidades comerciales.



El almacenamiento en caché no es una solución milagrosa para los problemas de rendimiento. En muchos casos, optimizar el código y el almacenamiento del núcleo le irá bien a largo plazo. Además, la introducción del almacenamiento en caché debería ser una reacción al problema y no una optimización prematura.



En conclusión, optimizar el rendimiento de la aplicación y hacerla escalable es un proceso continuo que tiene como objetivo lograr un comportamiento predecible dentro de requisitos no funcionales específicos. El almacenamiento en caché es necesario para reducir el costo del hardware y el tiempo de desarrollo dedicado a mejorar el rendimiento y la escalabilidad.



Enlaces:






All Articles