Consejos y trucos

Este documento describe las mejores prácticas para diseñar, implementar, probar e implementar funciones en la nube.

Exactitud

Esta sección describe las mejores prácticas generales para diseñar e implementar funciones en la nube.

Escribir funciones idempotentes

Sus funciones deberían producir el mismo resultado incluso si se llaman varias veces. Esto le permite volver a intentar una invocación si la invocación anterior falla en la mitad de su código. Para obtener más información, consulte Reintentar funciones controladas por eventos .

No iniciar actividades en segundo plano

La actividad en segundo plano es cualquier cosa que sucede después de que finaliza su función. Una invocación de función finaliza una vez que la función regresa o indica su finalización, como llamando al argumento callback en las funciones controladas por eventos de Node.js. Cualquier código que se ejecute después de una terminación elegante no podrá acceder a la CPU y no realizará ningún progreso.

Además, cuando se ejecuta una invocación posterior en el mismo entorno, su actividad en segundo plano se reanuda, interfiriendo con la nueva invocación. Esto puede provocar comportamientos inesperados y errores difíciles de diagnosticar. El acceso a la red después de que finaliza una función generalmente provoca que se restablezcan las conexiones (código de error ECONNRESET ).

La actividad en segundo plano a menudo se puede detectar en registros de invocaciones individuales, al encontrar cualquier cosa que se registre después de la línea que dice que la invocación finalizó. A veces, la actividad en segundo plano puede quedar oculta más profundamente en el código, especialmente cuando están presentes operaciones asincrónicas como devoluciones de llamada o temporizadores. Revise su código para asegurarse de que todas las operaciones asincrónicas finalicen antes de finalizar la función.

Eliminar siempre archivos temporales

El almacenamiento en disco local en el directorio temporal es un sistema de archivos en memoria. Los archivos que escribe consumen memoria disponible para su función y, a veces, persisten entre invocaciones. Si no se eliminan explícitamente estos archivos, es posible que se produzca un error de falta de memoria y un posterior inicio en frío.

Puede ver la memoria utilizada por una función individual seleccionándola en la lista de funciones en GCP Console y eligiendo el gráfico de uso de memoria .

No intente escribir fuera del directorio temporal y asegúrese de utilizar métodos independientes de la plataforma/sistema operativo para construir rutas de archivos.

Puede reducir los requisitos de memoria al procesar archivos más grandes mediante canalización. Por ejemplo, puedes procesar un archivo en Cloud Storage creando una secuencia de lectura, pasándola por un proceso basado en secuencias y escribiendo la secuencia de salida directamente en Cloud Storage.

Marco de funciones

Cuando implementa una función, Functions Framework se agrega automáticamente como una dependencia, utilizando su versión actual. Para garantizar que las mismas dependencias se instalen de manera consistente en diferentes entornos, le recomendamos que fije su función a una versión específica de Functions Framework.

Para hacer esto, incluya su versión preferida en el archivo de bloqueo correspondiente (por ejemplo, package-lock.json para Node.js o requirements.txt para Python).

Herramientas

Esta sección proporciona pautas sobre cómo utilizar herramientas para implementar, probar e interactuar con Cloud Functions.

Desarrollo local

La implementación de funciones lleva un poco de tiempo, por lo que suele ser más rápido probar el código de su función localmente.

Los desarrolladores de Firebase pueden utilizar el emulador de funciones en la nube de Firebase CLI .

Utilice Sendgrid para enviar correos electrónicos

Cloud Functions no permite conexiones salientes en el puerto 25, por lo que no puede realizar conexiones no seguras a un servidor SMTP. La forma recomendada de enviar correos electrónicos es utilizar SendGrid . Puede encontrar otras opciones para enviar correos electrónicos en el tutorial Envío de correo electrónico desde una instancia para Google Compute Engine.

Actuación

Esta sección describe las mejores prácticas para optimizar el rendimiento.

Utilice las dependencias sabiamente

Debido a que las funciones no tienen estado, el entorno de ejecución a menudo se inicializa desde cero (durante lo que se conoce como arranque en frío ). Cuando ocurre un arranque en frío, se evalúa el contexto global de la función.

Si sus funciones importan módulos, el tiempo de carga de esos módulos puede aumentar la latencia de invocación durante un inicio en frío. Puede reducir esta latencia, así como el tiempo necesario para implementar su función, cargando las dependencias correctamente y no cargando las dependencias que su función no utiliza.

Utilice variables globales para reutilizar objetos en futuras invocaciones

No hay garantía de que el estado de una función en la nube se conserve para futuras invocaciones. Sin embargo, Cloud Functions suele reciclar el entorno de ejecución de una invocación anterior. Si declara una variable en el ámbito global, su valor se puede reutilizar en invocaciones posteriores sin tener que volver a calcularlo.

De esta manera, puede almacenar en caché objetos que pueden resultar costosos de recrear en cada invocación de función. Mover dichos objetos del cuerpo de la función al alcance global puede generar mejoras significativas en el rendimiento. El siguiente ejemplo crea un objeto pesado solo una vez por instancia de función y lo comparte entre todas las invocaciones de funciones que llegan a la instancia determinada:

Nodo.js

console.log('Global scope');
const perInstance = heavyComputation();
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((req, res) => {
  console.log('Function invocation');
  const perFunction = lightweightComputation();

  res.send(`Per instance: ${perInstance}, per function: ${perFunction}`);
});

Pitón

import time

from firebase_functions import https_fn

# Placeholder
def heavy_computation():
  return time.time()

# Placeholder
def light_computation():
  return time.time()

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

@https_fn.on_request()
def scope_demo(request):

  # Per-function scope
  # This computation runs every time this function is called
  function_var = light_computation()
  return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
  

Esta función HTTP toma un objeto de solicitud ( flask.Request ) y devuelve el texto de respuesta, o cualquier conjunto de valores que se pueda convertir en un objeto Response usando make_response .

Es particularmente importante almacenar en caché las conexiones de red, las referencias de bibliotecas y los objetos de cliente API en el ámbito global. Consulte Optimización de redes para ver ejemplos.

Realizar una inicialización diferida de variables globales.

Si inicializa variables en el ámbito global, el código de inicialización siempre se ejecutará mediante una invocación de inicio en frío, lo que aumentará la latencia de su función. En ciertos casos, esto provoca tiempos de espera intermitentes en los servicios que se llaman si no se manejan adecuadamente en un bloque try / catch . Si algunos objetos no se utilizan en todas las rutas de código, considere inicializarlos de forma diferida según demanda:

Nodo.js

const functions = require('firebase-functions');
let myCostlyVariable;

exports.function = functions.https.onRequest((req, res) => {
  doUsualWork();
  if(unlikelyCondition()){
      myCostlyVariable = myCostlyVariable || buildCostlyVariable();
  }
  res.status(200).send('OK');
});

Pitón

from firebase_functions import https_fn

# Always initialized (at cold-start)
non_lazy_global = file_wide_computation()

# Declared at cold-start, but only initialized if/when the function executes
lazy_global = None

@https_fn.on_request()
def lazy_globals(request):

  global lazy_global, non_lazy_global

  # This value is initialized only if (and when) the function is called
  if not lazy_global:
      lazy_global = function_specific_computation()

  return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
  

Esta función HTTP utiliza globales inicializados de forma diferida. Toma un objeto de solicitud ( flask.Request ) y devuelve el texto de respuesta, o cualquier conjunto de valores que se pueda convertir en un objeto Response usando make_response .

Esto es particularmente importante si define varias funciones en un solo archivo y diferentes funciones utilizan diferentes variables. A menos que utilice una inicialización diferida, puede desperdiciar recursos en variables que se inicializan pero que nunca se utilizan.

Reduzca los arranques en frío estableciendo un número mínimo de instancias

De forma predeterminada, Cloud Functions escala la cantidad de instancias en función de la cantidad de solicitudes entrantes. Puede cambiar este comportamiento predeterminado estableciendo una cantidad mínima de instancias que Cloud Functions debe mantener listas para atender solicitudes. Establecer un número mínimo de instancias reduce los inicios en frío de su aplicación. Recomendamos establecer una cantidad mínima de instancias si su aplicación es sensible a la latencia.

Consulte Controlar el comportamiento de escalado para obtener más información sobre estas opciones de tiempo de ejecución.

Recursos adicionales

Obtenga más información sobre cómo optimizar el rendimiento en el vídeo "Google Cloud Performance Atlas" Tiempo de arranque en frío de funciones en la nube .