Cloud Run: procesos en Background y Multithread
TL;DR
Problema: Cloud Run no funciona bien cuando se ejecuta un proceso que ocurre en background (fuera del ciclo request-response).
Solución: mantener en foreground y pensar Cloud Run como un sistema distribuido multi-thread
Google Cloud Run
Es un excelente producto, ver en especial el quick start. Todos los detalles en el sitio de Google. Si ocupas nodejs te recomiendo este link para “dockerizar” tu aplicación y así poder subirla a Cloud Run.
Procesos en Background
Al diseñar una aplicación web que tenga procesos que requieran tiempos extendidos de ejecución (~20 segundos o más) éstos deben ser considerados para ejecutarse en background. De esta manera se evitan timeouts en el browser y peor todavía que el browser haga el mismo request repetidas veces.
Como solución general, en el backend se suele correr estos procesos en forma asíncrona y al terminar su ejecución se notifica al cliente mediante un websocket, email o alguna otra forma menos convencional.
En el caso de Cloud Run hacer procesamiento en background toma alrededor de unas 40–100 veces el tiempo normal. Es por esto que se debe considerar como una restricción para un buen diseño de aplicación y, en la medida de lo posible, correr estos procesos en foreground. En otras palabras, antes de entregar una respuesta.
Multithread
Por otra parte Cloud Run funciona como un procesador multithread y además como balanceador de carga, por lo que se parece más bien a un sistema distribuido, y eso es algo que no necesariamente un desarrollador web tiene en mente al crear este tipo de aplicaciones.
Entonces, surge la necesidad de preocuparse por la atomicidad de ciertos procesos. Para esto se debe tener en mente el concepto de “Thread safe”. Si la aplicación ocupa cosas tan simples como el acceso al disco local para guardar temporalmente un archivo, entonces se puede transformar en un problema en Cloud Run.
Para evitar esta situación se puede utilizar una variable de instancia como se define en la documentación (ver “Using global variables”). Con esto se logra el equivalente a ocupar una base de datos moderna en que la concurrencia deja de ser un tema por el cual preocuparse.
Ejemplo basado en este código:
const instanceFunction = async data => {
// hacer algo, acceso a disco, etc. (recuerda usar await)
return { foo: 'var' };
};exports.crearPdf = async (req, res) => {
const functionVar = await instanceFunction()
res.send(functionVar);
};
Otras consideraciones
Finalmente, recomiendo en base a la práctica pensar en que cualquier código que se debe ejecutar en forma atómica debe ser diseñado de tal forma que pueda transformarse en una librería externa o un procedimiento independiente y desacoplado.