En este documento se describen las prácticas recomendadas para diseñar, implementar, probar y desplegar funciones de Cloud Run.
Corrección
En esta sección se describen las prácticas recomendadas generales para diseñar e implementar funciones de Cloud Run.
Escribir funciones idempotentes
Tus funciones deben producir el mismo resultado aunque se llamen varias veces. Esto te permite volver a intentar una invocación si la anterior falla a mitad del código. Para obtener más información, consulta Reintentar funciones controladas por eventos.
Asegurarse de que las funciones HTTP envían una respuesta HTTP
Si tu función se activa mediante HTTP, recuerda enviar una respuesta HTTP, como se muestra a continuación. Si no lo haces, la función se ejecutará hasta que se agote el tiempo de espera. Si esto ocurre, se te cobrará todo el tiempo de espera. Los tiempos de espera también pueden provocar comportamientos impredecibles o arranques en frío en invocaciones posteriores, lo que da lugar a comportamientos impredecibles o latencia adicional.
Node.js
Python
Go
Java
C#
Ruby
PHP
No iniciar actividades en segundo plano
La actividad en segundo plano es todo lo que ocurre después de que tu función haya finalizado.
Una invocación de función finaliza cuando la función devuelve un valor o indica que se ha completado de otro modo, como llamando al argumento callback
en las funciones basadas en eventos de Node.js. Cualquier código que se ejecute después de la finalización correcta no podrá acceder a la CPU y no avanzará.
Además, cuando se ejecuta una invocación posterior en el mismo entorno, tu actividad en segundo plano se reanuda, lo que interfiere con la nueva invocación. Esto puede provocar un comportamiento inesperado y errores difíciles de diagnosticar. Acceder a la red después de que finalice una función suele provocar que se restablezcan las conexiones (código de error ECONNRESET
).
La actividad en segundo plano se puede detectar en los registros de invocaciones individuales. Para ello, busca cualquier elemento que se registre después de la línea que indica que la invocación ha finalizado. A veces, la actividad en segundo plano puede estar más oculta en el código, sobre todo cuando hay operaciones asíncronas, como retrollamadas o temporizadores. Revisa el código para asegurarte de que todas las operaciones asíncronas finalicen antes de terminar la función.
Eliminar siempre los archivos temporales
El almacenamiento en disco local en el directorio temporal es un sistema de archivos en memoria. Los archivos que escribes consumen la memoria disponible para tu función y, a veces, se conservan entre invocaciones. Si no se eliminan explícitamente estos archivos, puede que se produzca un error de falta de memoria y un arranque en frío.
Para ver la memoria que usa una función concreta, selecciónala en la lista de funciones de laGoogle Cloud consola y elige el gráfico Uso de memoria.
Si necesitas acceder a almacenamiento a largo plazo, considera la posibilidad de usar los montajes de volúmenes de Cloud Run con Cloud Storage o volúmenes NFS.
Puedes reducir los requisitos de memoria al procesar archivos más grandes mediante el procesamiento simultáneo. Por ejemplo, puedes procesar un archivo en Cloud Storage creando un flujo de lectura, pasándolo por un proceso basado en flujos y escribiendo el flujo de salida directamente en Cloud Storage.
Framework de Functions
Para asegurarte de que las mismas dependencias se instalan de forma coherente en todos los entornos, te recomendamos que incluyas la biblioteca Functions Framework en tu gestor de paquetes y que fijes la dependencia a una versión específica de Functions Framework.
Para ello, incluye la versión que prefieras en el archivo de bloqueo correspondiente (por ejemplo, package-lock.json
para Node.js o requirements.txt
para Python).
Si Functions Framework no aparece explícitamente como dependencia, se añadirá automáticamente durante el proceso de compilación con la última versión disponible.
Herramientas
En esta sección se proporcionan directrices sobre cómo usar herramientas para implementar, probar e interactuar con funciones de Cloud Run.
Desarrollo local
El despliegue de funciones lleva un poco de tiempo, por lo que suele ser más rápido probar el código de tu función de forma local.
Informes de errores
En los lenguajes que usan el control de excepciones, no se deben lanzar excepciones no detectadas, ya que fuerzan los inicios en frío en invocaciones futuras.
No salgas manualmente
Si sales manualmente, puede que se produzca un comportamiento inesperado. En su lugar, usa las siguientes expresiones idiomáticas específicas de cada idioma:
Node.js
No uses process.exit()
. Las funciones HTTP deben enviar una respuesta con res.status(200).send(message)
, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea de forma implícita o explícita).
Python
No uses sys.exit()
. Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).
Go
No uses os.Exit()
. Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).
Java
No uses System.exit()
. Las funciones HTTP deben enviar una respuesta con response.getWriter().write(message)
, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea de forma implícita o explícita).
C#
No uses System.Environment.Exit()
. Las funciones HTTP deben enviar una respuesta con context.Response.WriteAsync(message)
, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea de forma implícita o explícita).
Ruby
No uses exit()
ni abort()
. Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).
PHP
No uses exit()
ni die()
. Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).
Usar SendGrid para enviar correos
Las funciones de Cloud Run no permiten conexiones salientes en el puerto 25, por lo que no puedes establecer conexiones no seguras con un servidor SMTP. La forma recomendada de enviar correos es usar un servicio de terceros, como SendGrid. Puedes consultar otras opciones para enviar correos en el tutorial Enviar correos desde una instancia de Google Compute Engine.
Rendimiento
En esta sección se describen las prácticas recomendadas para optimizar el rendimiento.
Evitar la baja simultaneidad
Como los arranques en frío son costosos, poder reutilizar instancias iniciadas recientemente durante un pico es una optimización excelente para gestionar la carga. Limitar la simultaneidad limita el uso que se puede hacer de las instancias, lo que provoca más arranques en frío.
Aumentar la simultaneidad ayuda a diferir varias solicitudes por instancia, lo que facilita la gestión de los picos de carga.Usa las dependencias con cabeza
Como las funciones no tienen estado, el entorno de ejecución se inicializa a menudo desde cero (durante lo que se conoce como arranque en frío). Cuando se produce un arranque en frío, se evalúa el contexto global de la función.
Si tus funciones importan módulos, el tiempo de carga de esos módulos puede aumentar la latencia de invocación durante un arranque en frío. Puedes reducir esta latencia, así como el tiempo necesario para desplegar tu función, cargando las dependencias correctamente y no cargando las que no utilice.
Usar variables globales para reutilizar objetos en invocaciones futuras
No se garantiza que el estado de una función de Cloud Run se conserve para futuras invocaciones. Sin embargo, las funciones de Cloud Run suelen reutilizar el entorno de ejecución de una invocación anterior. Si declaras una variable en el ámbito global, su valor se puede reutilizar en invocaciones posteriores sin tener que volver a calcularse.
De esta forma, puedes almacenar en caché objetos que pueden ser caros de recrear en cada invocación de función. Mover estos objetos del cuerpo de la función al ámbito global puede mejorar significativamente el rendimiento. En el siguiente ejemplo, se crea un objeto pesado solo una vez por instancia de función y se comparte en todas las invocaciones de función que llegan a la instancia dada:
Node.js
Python
Go
Java
C#
Ruby
PHP
Es especialmente importante almacenar en caché las conexiones de red, las referencias de bibliotecas y los objetos de cliente de API en el ámbito global. Consulta las prácticas recomendadas de redes para ver ejemplos.
Reduce los arranques en frío definiendo un número mínimo de instancias
De forma predeterminada, Cloud Run Functions escala el número de instancias en función del número de solicitudes entrantes. Puedes cambiar este comportamiento predeterminado definiendo un número mínimo de instancias que las funciones de Cloud Run deben mantener listas para atender solicitudes. Si defines un número mínimo de instancias, se reducirán los arranques en frío de tu aplicación. Si tu aplicación es sensible a la latencia, te recomendamos que definas un número mínimo de instancias y que completes la inicialización en el tiempo de carga.
Para saber cómo definir un número mínimo de instancias, consulta Usar instancias mínimas.
Notas sobre el arranque en frío y la inicialización
La inicialización global se produce en el momento de la carga. Sin él, la primera solicitud tendría que completar la inicialización y cargar los módulos, lo que provocaría una latencia mayor.
Sin embargo, la inicialización global también influye en los arranques en frío. Para minimizar este impacto, inicializa solo lo que se necesite para la primera solicitud, de modo que la latencia de la primera solicitud sea lo más baja posible.
Esto es especialmente importante si has configurado el número mínimo de instancias como se ha descrito anteriormente para una función sensible a la latencia. En ese caso, completar la inicialización en el tiempo de carga y almacenar en caché los datos útiles asegura que la primera solicitud no tenga que hacerlo y se sirva con una latencia baja.
Si inicializas variables en el ámbito global, en función del lenguaje, los tiempos de inicialización largos pueden dar lugar a dos comportamientos: - En algunas combinaciones de lenguajes y bibliotecas asíncronas, el framework de funciones puede ejecutarse de forma asíncrona y devolver resultados inmediatamente, lo que provoca que el código siga ejecutándose en segundo plano, lo que podría causar problemas como no poder acceder a la CPU. Para evitarlo, debes bloquear la inicialización del módulo, tal como se describe a continuación. De esta forma, también se asegura de que las solicitudes no se sirvan hasta que se complete la inicialización. - Por otro lado, si la inicialización es síncrona, el tiempo de inicialización prolongado provocará arranques en frío más largos, lo que podría ser un problema, especialmente con funciones de baja simultaneidad durante picos de carga.
Ejemplo de precalentamiento de una biblioteca asíncrona de Node.js
Node.js con Firestore es un ejemplo de biblioteca asíncrona de Node.js. Para aprovechar las ventajas de min_instances, el siguiente código completa la carga y la inicialización en el tiempo de carga, lo que bloquea la carga del módulo.
Se usa TLA, lo que significa que se requiere ES6. Para ello, se usa una extensión .mjs
para el código de Node.js o se añade type: module
al archivo package.json.
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
Node.js
import Firestore from '@google-cloud/firestore'; import * as functions from '@google-cloud/functions-framework'; const firestore = new Firestore({preferRest: true}); // Pre-warm firestore connection pool, and preload our global config // document in cache. In order to ensure no other request comes in, // block the module loading with a synchronous global request: const config = await firestore.collection('collection').doc('config').get(); functions.http('fetch', (req, res) => { // Do something with config and firestore client, which are now preloaded // and will execute at lower latency. });
Ejemplos de inicialización global
Node.js
Python
Go
Java
C#
Ruby
PHP
Las funciones de PHP no pueden conservar variables entre solicitudes. En el ejemplo de ámbitos anterior, se usa la carga diferida para almacenar en caché los valores de las variables globales en un archivo.
Esto es especialmente importante si defines varias funciones en un mismo archivo y cada una de ellas usa variables diferentes. Si no usas la inicialización diferida, puedes desperdiciar recursos en variables que se inicializan pero que nunca se usan.
Recursos adicionales
Consulta más información sobre cómo optimizar el rendimiento en el vídeo "Google Cloud Performance Atlas" (Atlas de rendimiento de Google Cloud) sobre el tiempo de arranque en frío de las funciones de Cloud Run.