Aplicación Cloud Convert
Aplicación que convierte archivos entre los siguientes formatos de audio: MP3, ACC, OGG, WAV, WMA
Nombres | Apellido | Correo @uniandes | Usuario de GitHub |
---|---|---|---|
Ronald | Lugo | [email protected] | @RonaldLugo |
Alejandro | Santamaría | [email protected] | @miso-alejosaur |
Hector | Tenezaca | [email protected] | @htenezaca |
Javier | López Grau | [email protected] | @muniter |
Objetivo
Desarrollar un servicio de conversión entre diferentes formatos de audio y poner a prueba su rendimiento y capacidad bajo unas características de infraestructura local definidas
- alcance entrega 5: despliegue en PaaS - Google App Engine
Arquitectura
Versión: app desplegada en plataforma PaaS - Google App Engine
La siguiente es la arquitectura de la aplicación
Convención del diagrama:
GCP: Google Cloud Platform
CE: Google Cloud Compute engine
SQL: Google Cloud SQL
LB: Load Balancer
MIG: Google App Engine
PSU: Google Cloud Pub/Sub
GCS: Google Cloud Storage
flowchart TD
subgraph Client
web[Web Client]
end
subgraph MAIL
mail[Sendgrid]
end
web<--http-->MIG
subgraph GCP
subgraph MIG[API Google App Engine]
direction BT
api1[API instance 1 - CE]
end
PUBSUB[PSU Google Cloud Pub/Sub]
subgraph SQL
db[Database \n PostgreSQL]
end
subgraph MIGC[Converter Google App Engine]
direction BT
co1[Converter instance 1 - CE]
end
subgraph GCS
gcs[bucket - GCS]
end
MIG<-->db
MIG -->PUBSUB
MIGC<-->db
PUBSUB<-->MIGC
MIGC<--service agent-->gcs
MIG <--service agent-->gcs
MIGC--api-->mail
end
Componentes
A nivel de infraestructura
Componente | Propósito |
---|---|
Client | Consume el servicio de conversión. |
Google App Engine | Habilita el environment, expone un dns de consumo, administra internamente temas como el balanceo de carga, las instancias de despliegue y el escalamiento (de acuerdo a la configuración establecida) |
API | Autentica, y despacha los servicios. |
Cloud Pub/Sub | Servició de mensajería, por donde se despachan solicitudes de conversión |
Converter | Recibe solicitudes de conversión |
Bucket | Almacenamiento común de transferencia de archivos para converter y api |
Database | Persistencia de usuarios, tasks, metadata de conversiones |
Servicio para el envío de email de notificación de conversión finalizada |
Nota: el alcance actual no incluye el desarrollo del cliente web, por lo cual en este alcance se usa Postman para simular las peticiones que realizaría el cliente web
Tecnológica
Se utiliza docker para orquestar el levantamiento del componente Converter
Tecnologías
- Postgres: motor de base de datos relacional.
- Flask: web framework.
- Cloud Pub/Sub: sistema de mensajería asíncrona.
- SqlAlchemy: ORM para la comunicación.
- uvicorn: HTTP <-> ASGI bridge para la comunicación del Flask.
- ffmpeg: convertidor de formatos de audio.
- Google Cloud storage: Almacenamiento de archivos.
- Load Balancer: balanceador de carga de peticiones HTTP.
- Google App Engine: entorno administrado tipo PaaS para el despliegue de aplicaciones
Servicios de Google Cloud Platform utilizados
- Google App Engine
- Compute Engine
- SQL
- Cloud Storage
- Monitoring
- Pub/Sub
Ejemplo de conversión
Este es el flujo normal que ocurre cuando un usuario crea una tarea de conversión.
sequenceDiagram
participant cli as Cliente Web
participant api as API
participant gcs as Cloud Storage
participant psub as Cloud Pub/Sub
participant db as Database
participant co as Converter
Note over cli,co: El usuario ya está autenticado
cli->>api: Solicitud de conversión
api->>gcs: Almacena archivo
api->>db: Crear record de conversión
api->>psub: Encola conversión
api->>cli: Notifica conversión iniciada
psub-->>co: Solicitud de conversión
co->>gcs: Retira archivo
co->>co: Realiza conversión
co->>gcs: Almacena archivo convertido
co->>db: Reporta resultado de conversión
co-->>cli: Email al cliente con link de descarga
Endpoints implementados
Endpoint | Método | Descipción | Parámetros | Consideraciones |
---|---|---|---|---|
/api/auth/signup |
POST | Creación de cuenta de usuario |
|
|
/api/auth/login |
POST | Login que recupera token de autorización |
|
|
/api/tasks |
GET | Recupera todas las tareas de conversión |
|
|
/api/task |
POST | Crea una nueva tarea de conversión de formatos |
|
|
/api/tasks/<int:id_task> |
GET | Recupera una tarea específica de conversión |
|
|
/api/tasks/<int:id_task> |
PUT | Actualiza una tarea de conversión existente |
|
|
/api/tasks/<int:task_id> |
DELETE | Elimina una tarea de conversión existente |
|
|
/api/files/<string:file_id> |
GET | Descarga de archivo |
|
|
/benchmark/conversion/start |
POST | Lanzamiento masivo concurrente de conversión |
|
|
/benchmark/conversion/result |
GET | Obtiene las tareas con su estado | ||
/benchmark/conversion/data |
GET | Obtiene cantidad de tareas procesadas por minuto |
Información adicional en documentación del API en Postman en el siguiente link: documentación API, también puede usar el archivo JSON que describe la API
Instrucciones Generales de despliegue
Requerimientos:
- Infraestructura:
- 1 servicio de App Engine tipo estandar para despliegue de API
- 1 servicio de App Engine tipo flex para despliegue de Converter
- 1 Instancia de cloud sql de desarrollo con Postgres 14
- 1 topic de pub/sub
- 1 subscription de pub/sub
- 1 bucket de cloud storage
- Software a instalar
- Git
- Docker
- Python
instrucciones de despliegue en App Engine estandar
- crear proyecto en App Engine
cd uni_cloud_convert/services/[api/converter]
gcloud auth application-default login
gcloud app create
- configurar app yaml
- desplegar app y verificar su despliegue
gcloud app deploy
gcloud app browse
Bucket en Cloud Storage
- Configurar permisos de Service Agent y descargar archivo service-account.json
El servicio se encargará de crear el bucket si no existe a partir del nombre configurado en el archivo .env
Puede seguir las siguientes instrucciones para la creación
Cloud Pub/Sub
- Habilitar el servicio de Cloud Pub/Sub
El API hará el bootstrap de la creación de los recursos necesarios para el funcionamiento de la aplicación, por lo que no es necesario crearlos manualmente.
Tomar en cuenta que para este proyecto por la lógica de negocio se debe activar la siguiente configuración en la Subscripción:
- Acknowledge deadline: 1 min
- Exactly once delivery: Enabled
Database
Crear la instancia en Cloud-SQL de Postgres.
Puede seguir las siguientes instrucciones para la creación.
Nota: es muy importante que seleccione que la instancia tenga Private IP y comparta la misma red que los compute engine. Para que se puedan comunicar.
Si tiene algún problema en la configuración de Private IP pude seguir el siguiente instructivo
Health Checks
Con estas instrucciones están listo el despliegue, se puede continuar haciendo health checks.
Para confirmar el funcionamiento de las partes de la app:
# Cliente Web (En el response se verá el resultado)
curl $API_PUBLIC_URL/api-health
# Converter (Revisar los logs para ver el resultado)
curl $API_PUBLIC_URL/converter-health
# Ping, pong style (mirar los logs)
curl $API_PUBLIC_URL/ping
Probar el uso del servicio
- Instalar Postman localmente
- Configurar un environment con la $API_PUBLIC_URL pública asignada al Load Balancer
- Configurar los endpoints vistos previamente
- Deshabilitar variable de entorno
STRESS_TEST
en la máquina converter. NOTA: Antes de correr cualquier prueba no olvide habilitar el envío de correos, editando en el archivo .env:STRESS_TEST=0
- Inicialmente usar Signup para crear usuario, y Login para iniciar sesión. tomar el token id y actualizarlo en los header/authorization de los servicios que lo requieren
- Probar el flujo de servicios deseado
Comandos frecuentes
# Rendimiento de la máquina virtual
top
# Rendimiento de containers
sudo docker stats
# Containers en ejecución
sudo docker container ls
# conectarse a un container en particular con bash
sudo docker exec -it <<container_name>> bash
Logs desde App Engine
- Ir a la consola de GCP
- Ingresar al servicio de App Engine
- Identificar el servicio API/Converter requerido
- En la columna Diagnose desplegar las opciones de TOOLS y seleccionar Logs
- Se tendrá la salida de logs generados por la aplicación
Análisis de Capacidad
Nota: Se hace comparativo en cada punto con entrega de la primera semana, segunda semana, tercera semana y cuarta semana.
Se realizan pruebas de carga y estrés a la aplicación para lograr dimensionar la capacidad de la misma en un entorno de infraestructura definido. A continuación se describen las pruebas realizadas, los análisis de los resultados y las conclusiones sobre el rendimiento de la aplicación
Inicializar máquina virtual de prueba
Requerimientos:
- 1 Máquina virtual tipo E2 e2-small para las pruebas
- Debian 11 (bullseye)
- Software a instalar
- Git
- Python3
- pip: Python package manager
- locust
Instrucciones
- Clonar el repositorio
git clone https://github.com/muniter/uni_cloud_convert.git
- Instalar dependencias
sudo apt install python3 python3-pip
- Cambiar de usuario a root:
sudo su
- Instalar locust
pip install locust==2.12.2
- Cerrar sesión: necesario para que posteriormente aparezca el comando
locust
en$PATH
exit
- Iniciar sesión y colocarse como usuario root
sudo su
- Habilitar variable de entorno
STRESS_TEST
en la máquina converter.
NOTA: Antes de correr cualquier prueba no olvide deshabilitar el envío de correos, editando en el archivo .env:
STRESS_TEST=1
Preámbulo
Hallazgos en ejecución en App Engine, tanto en capa web como en worker
- La configuración de API fue natural ya que el desarrollo estaba realizado para funcionar en docker, de tal manera que al paso a App Engine no implicó cambios profundos al desarrollo
- La configuración del Converter si tuvo un impacto profundo en su funcionamiento, ya que no estaba pensado para funcionar bajo peticiones http, sin embargo, App Engine requiere la exposición de un servicio http para validar su disponibilidad
- Con la restricción de no configurar auto-scaling, el desempeño del API fue reducido, mientras que el del Converter fue muy satisfactorio para una sola instancia
- La comunicación de App Engine con otros servicios de GCP previamente configurados no implicó mayores cambios (ej: Pub/Sub, Cloud Storage), sin embargo, en SQL si se requirió configuraciones adicionales para habilitar la conexión
Procesamiento CPU (frente a la entrega anterior)
- La configuración de CPU ya no es explícita, es administrada por App Engine
- Database en Cloud SQL: mantuvo sus características
- Cloud Storage estándar: mantuvo sus características
- Pub/Sub: mantuvo sus características
Memoria
- La configuración de memoria ya no es explícita, es administrada por App Engine
Almacenamiento
- La configuración de almacenamiento ya no es explícita, es administrada por App Engine
- La introducción de Cloud Storage introdujo mayor latencia en la transferencia de red.
Escenarios del Plan de pruebas
1. Capacidad de solicitudes
Máxima cantidad de request/minuto que soporta la aplicación con usuario concurrentes.
Este escenarios es de vital importancia para el aplicativo, nos permitirá conocer la cantidad de request que podremos atender en una ventana de tiempo manteniendo un nivel de servicio aceptable. En este caso consideramos que una media de 1.5 segundos y perdidas menores del 1% son acciones representativas en la operación.
Limitantes:
- Archivo de tamaño mínimo de 5MB
- Tiempo de respuesta aceptable de 1.5 segundos
- Porcentaje de error máximo del 1%
- Error de timeout si una respuesta demora más de 10 segundos
Detalle de operación
La prueba se realiza enviando requests concurrentes al endpoint de crear tareas, /api/tasks
con un archivo de 5MB. Los request se presentan de la siguiente manera:
- El benchmark cuenta con usuarios
- Empieza con 2 usuarios
- Los usuarios envian un request entre 5 a 10 segundos de manera aleatoria.
- Cada segundo que pasa se añaden 2 nuevos usuarios.
- Usuarios máximos: 400
Vista simplificada del proceso: aunque no estén dibujados en el diagrama todo está operando en conjunto, se encola, se guarda en db y el convertidor trabaja.
sequenceDiagram
participant cli as Usuario
participant api as API
loop Every second
Note over cli,api: Se añaden dos usuarios más
par
loop Every 5~10 seconds
cli->>api: Solicitud de conversión
end
end
end
Instrucciones
Debe haber seguido antes todas las instrucciones de despliegue, tener la aplicación funcionando (probar con health checks) y haber seguido las instrucciones para inicializar la máquina de pruebas.
- Hacer login a la máquina de pruebas, y ser el usuario root:
# Ser usuario root
sudo su
cd /home/maestria
- Iniciar locust
# NOTA: reemplazar por la ip asignada al balanceador de carga
locust --host=http://<API_URL> --users=400 --spawn-rate=2 --web-port --autostart
- Navegar a
http://IP_DE_MAQUINA_VIRTUAL_TEST
para ver la interfaz de locust podrá ver tab de estadísticas, gráficas e instrucciones.
Resultados
Informe de resultados
Se relacionan las diferentes entregas para detalle de la comparación realizada
- App Engine: Entrega 5
- GCP Autoscaling Converter + Cloud Pub/Sub: Entrega 4
- GCP Autoscaling API: Entrega 3
- GCP PaaS: Entrega 2
- Local: Entrega 1
Estos son los puntos principales:
- La instancia de App Engine de entrada inicia con tiempos de respuesta superiores a los 1.500 ms, por lo cual una sola instancia de App Engine Estandar no cumple los requisitos establecidos
- Los timeout por request superiores a 10 segundos empiezan a ocurrir con 20 usuarios
Cuadro comparativo:
Datos \ Ambiente | Local | GCP PaaS | Autoscaling web + Cloud Storage | Autoscaling worker + Cloud Pub/Sub | App Engine (1 instancia) |
---|---|---|---|---|---|
RPS (<1500ms) | 7.4/s, 440/min | 6/s, 360/min | 2.6s, 156/min | 3.7/s, 222/min | 0/s, 0/min |
Usuarios (<1500ms) | 70 | 56 | 35 | 44 | 0 |
Peticiones concurrentes que generan Timeouts (> 10 segs) | 170 | 146 | 70 | 114 | 20 |
Durante la operación el punto crítico era la utilización de recursos de las instancias del API que atendían las peticiones:
A partir de esto:
- El API se encuentra principalmente restringido por la capacidad de procesamiento (CPU) que es capaz de alcancar con el máximo de instancias permitidas en App Engine
- Se requiere plantear una validación del funcionamiento del API a nivel de App Engine para identificar si los tiempos de respuesta superiores a 1.500 ms pueden reducirse con optimizaciones de código, o si por el contrario se debe replantear el despliegue de estándar a flexible
- El API esta restringida por recursos CPU asignados a cada instancia en particular y al máximo de instancias permitidas (max=1)
2. Capacidad de conversiones
Máxima cantidad de archivos procesables por minuto.
Este escenarios es de vital importancia para el aplicativo, nos permitirá conocer la cantidad de conversiones que podremos atender por parte de los usuarios, teniendo un tiempo de demoras aceptable de 10 minutos.
Limitantes:
- Archivo de tamaño mínimo de 5MB
- Tiempo de conversión (desde la solicitud hasta que el convertidor lo procesa) máximo de 10 minutos.
Detalle de operación
La prueba se realiza enviando un request a un endpoint especial /benchmark/conversion/start
con un archivo de 5MB, el formato esperado y el número de tareas a ejecutar. El proceso funciona de la siguiente manera:
- El usuario benchmark (tú) hace el llamado a la api para iniciar el benchmark con un archivo (mp3 de 5MB), nuevo formato (wav) y número de tareas (200) distribuidas en 4 peticiones consecutivas de (50) en cada petición.
- El api genera los artefactos en base de datos y file system para las 200 tareas.
- El api encola las 200 tareas rápidamente
- El convertidor desencola y convierte
Vista simplificada del proceso: aunque no estén dibujados en el diagrama todo está operando en conjunto, se encola, se guarda en db y el convertidor trabaja.
sequenceDiagram
participant ben as Usuario Benchmarker
participant api as API
participant mb as Message Broker
participant co as Converter
ben->>api: Solicita generar 200 conversiones
api->>api: Genera 200 conversiones
api->>mb: Encola 200 conversiones
api->>ben: Avisa que inició 200 conversiones
mb-->>co: Solicitud de conversión
co->>co: Conversión
Nota: en este escenario fue necesario lanzar 4 peticiones de 50 conversiones c/una para generar las 200 peticiones, debido a que el tiempo que le tomaba al API copiar 50 archivos excedía el timeout de Flask ('[CRITICAL] WORKER TIMEOUT', alcanzaba a 50 max), debido a la latencia inducida por el almacenamiento como servicio del Cloud Storage
Resultados
Se relacionan las diferentes entregas para detalle de la comparación realizada
- App Engine: Entrega 5
- GCP Autoscaling Converter + Cloud Pub/Sub: Entrega 4
- GCP Autoscaling API: Entrega 3
- GCP PaaS: Entrega 2
- Local: Entrega 1
En esta prueba vimos un comportamiento similar al obtenido con GCP Autoscaling API, sin embargo, la cantidad de trabajos lanzados al mismo tiempo si tuvo un incremento. Comparando métricas:
Datos \ Ambiente | Local | GCP PaaS | Autoscaling web + Cloud Storage | Autoscaling worker + Cloud Pub/Sub | App Engine |
---|---|---|---|---|---|
Promedio de archivos procesados por minuto | 18 | 3 | 3 | 8 | 11 |
Máximo de archivos procesados por minuto | 20 | 7 | 7 | 10 | 13 |
Valor más frecuente de archivos procesados por minuto | 20 | 2-3 | 2-3 | 7-8 | 11 |
Concurrencia soportada (peticiones simultáneas) | 400 | 200 | 60 | 50 | 50 |
Peticiones atendidas en menos de 10 minutos | 193 | 28 | 28 | 81 | 111 |
Tiempo de conversión para la concurrencia enviada | 400 en 20 minutos | 82 en 33 minutos | 60 en 24 minutos | 200 en 26 minutos | 225 en 20 minutos |
Esto lo atribuimos a:
- El performance de App Engine en configuración flexible, el cual con una sola instancia superó el rendimiento que se había logrado con 3 instancias en autoscaling
- Posiblemente el factor fundamental es el tipo de máquina usada por App Engine que es transparente para el usuario y no se configura explícitamente
- Sin embargo, los aspectos de CPU y Cloud Storage mencionados en el escenario 1, afectaron la capacidad de lanzar gran cantidad de peticiones al mismo tiempo, en este caso se ejecutó dentro de un loop estas peticiones
Ahora miremos el consumo de recursos converter:
y la distribución de carga de mensajes al Pub/sub:
- En este caso, en comparación con las anteriores entregas, el boost inicial fue alto, sin embargo se mantuvo, y tuvo algunas variaciones hacia arriba y abajo en los primeros 5 minutos, luego se estabilizó en un valor que aún era alto
- Esta es una actividad completamente bottlenecked por la CPU por lo cual vemos que no la deja descansar en ningún momento.
- El hecho de que siempre está saturada la CPU confirma que el solo utilizar 1 worker es la decisión correcta, al ser también un proceso 100% síncrono
- Por parte del PUB/SUB se observa una carga rápida de los 200 mensajes encolados, y un desencolamiento controlado por parte del worker de app-engine
Instrucciones
Debe haber seguido antes todas las instrucciones de despliegue, tener la aplicación funcionando (probar con health checks) y haber seguido las instrucciones para inicializar la máquina de pruebas.
- Hacer login a la máquina de pruebas
- Enviar el request para iniciar el benchmark
cd /home/maestria/
for var in {1..4}; do curl -F [email protected] -F newFormat=wav -F taskNumber=50 http://<API_URL>/benchmark/conversion/start; sleep 30; done
Ahora desde su máquina local:
- Copiar localmente la carpeta reporte del repositorio, modificar la primera línea del archivo report.js
http://$API_URL/benchmark/conversion/data
- Ejecutar index.html y monitorear durante 10 minutos para poder observar cuantas tareas se pudieron completar, y posteriormente para identificar cuando se complete el total de peticiones
Un ejemplo del reporte es el siguiente: reporte
Conclusiones y Limitaciones
Revisar análisis de resultados de cada escenario:
- Python y sus utilidades de transformación nos son ideales para actividades que están fuertemente restringidas por el uso de recursos (conversión de archivos).
- La aplicación tiene como principal bottleneck la CPU para el converter y las instancias de la api.
- Mantener Cloud Storage como servicio produce mayor latencia para escribir el archivo por parte del api, y no tanto impacto al obtenerlo del lado del convertidor.
- El uso de App Engine reduce las preocupaciones respecto a la infraestructura, sin embargo, no implica menos esfuerzo en desarrollo, ya que debe desarrollarse en pro de sacar el mejor provecho del funcionamiento PaaS
- Las dos posibilidades de App Engine (estándar y flexible) tienen diferentes características y ventajas. En este caso, estándar fue adecuado para peticiones API, y flexible fue necesario para soportar el funcionamiento de librerías como ffmpeg
- Definir tamaños máximos de archivos y estimar la capacidad máxima esperada de peticiones de carga, de tal manera que se pueda estimar cual es la capacidad de almacenamiento límite a la que podría llegar el sistema
- El uso de colas de mensajería permite desacoplar las dependencias de la respuesta del api frente al procesamiento de archivos, favoreciendo una mejor gestión y respuesta al usuario
- Las limitaciones en la infraestructura donde opera el sistema, afecta directamente los tiempos de respuesta, la cantidad de transacciones concurrentes y la velocidad en que se completa un proceso de conversión
- Cloud Storage brindó una alternativa de gestión de archivos y almacenamiento eficiente. El uso a través de servicios permite gestionar el bucket con esquemas robustos de seguridad, y desacoplado de los componentes de infraestructura en los que opera la aplicación. Sin embargo, el consumo de servicios y la gestión del ciclo de vida de la transferencia de cada archivo, generó mayor uso de cpu en las instancias del API, por lo cual su implementación debe ir acompañada de un análisis más profundo de arquitectura y diseño de la aplicación, de tal manera que se pueda sacar aún mejor provecho de Cloud Storage en aspectos como evitar la descarga de archivos a través del api si no con links directos a Cloud Storage de manera segura, o desacoplando la petición de conversión del almacenamiento en Cloud Storage, de tal manera que el api brinde respuestas rápidas al usuario, y gestione de manera más eficiente la transferencia de archivos al bucket
- App Engine es una alternativa muy favorable para el despliegue de aplicaciones. Es una alternativa a tener en cuenta en tiempo de diseño vs Compute Engine contemplando aspectos de adaptación tecnológica, impacto en el desarrollo, costo ante el escalamiento en cada caso, entre otros