Trabaja con funciones remotas

Una función remota de BigQuery te permite implementar tu función en otros lenguajes que no sean SQL y JavaScript, o con bibliotecas o servicios no permitidos en las funciones definidas por el usuario de BigQuery.

Descripción general

Una función remota de BigQuery te permite incorporar la funcionalidad de Google SQL con software fuera de BigQuery; para ello, proporciona una integración directa con Cloud Functions y Cloud Run. Con las funciones remotas de BigQuery, puedes implementar las funciones en Cloud Functions o Cloud Run implementadas con cualquier lenguaje compatible y, luego, invocarlas desde las consultas de Google SQL.

Flujo de trabajo

  1. Crea el extremo HTTP en Cloud Functions o Cloud Run.
  2. Crea una función remota en BigQuery.
    1. Crea una conexión del tipo CLOUD_RESOURCE.
    2. Crea una función remota.
  3. Usa la función remota en una consulta como cualquier otra función definida por el usuario.

Limitaciones

  • Las funciones remotas solo admiten uno de los siguientes tipos de datos como tipo de argumento o tipo de datos que se muestra:

    • Booleano
    • Bytes
    • Numérica
    • String
    • Fecha
    • Fecha y hora
    • Tiempo
    • Marca de tiempo
    • JSON

    Las funciones remotas no son compatibles con los tipos ARRAY, STRUCT, INTERVAL ni GEOGRAPHY.

  • No puedes crear funciones remotas temporales.

  • No puedes crear funciones remotas con valores de tabla.

  • No puedes usar funciones remotas cuando creas vistas materializadas.

  • Siempre se supone que el valor de retorno de una función remota no es determinista, por lo que el resultado de una consulta que llama a una función remota no se almacena en caché.

  • Es posible que veas solicitudes repetidas con los mismos datos a tu extremo, incluso después de obtener respuestas exitosas, debido a errores de red transitorios o errores internos de BigQuery.

  • Cuando se omite una evaluación de función remota para algunas filas debido a un cortocircuito, por ejemplo, en expresiones condicionales o en una declaración MERGE con WHEN [NOT] MATCHED, el procesamiento por lotes no se usa con la función remota. En este caso, el campo calls en el cuerpo de la solicitud HTTP tiene exactamente un elemento.

  • Si el conjunto de datos asociado con la función remota se replica en una región de destino mediante la replicación de conjunto de datos entre regiones, la función remota solo se puede consultar en la región en la que se creó.

Crear un extremo

Para crear una función remota que pueda implementar la lógica empresarial, debes crear un extremo HTTP mediante Cloud Functions o Cloud Run. El extremo debe poder procesar un lote de filas en una sola solicitud POST HTTP y mostrar los resultados para el lote como una respuesta HTTP.

Si creas la función remota a través de BigQuery Dataframes, no es necesario que crees el extremo HTTP de forma manual; el servicio lo hace automáticamente.

Consulta el instructivo de Cloud Functions y otra documentación de Cloud Functions sobre cómo escribir, implementar, probar y mantener una Cloud Function.

Consulta la guía de inicio rápido de Cloud Run y otra documentación de Cloud Run sobre cómo escribir, implementar, probar y mantener un servicio de Cloud Run.

Se recomienda mantener la autenticación predeterminada en lugar de permitir la invocación no autenticada de tu servicio de Cloud Functions o Cloud Run.

Formato de entrada

BigQuery envía solicitudes POST HTTP con cuerpo JSON en el siguiente formato:

Nombre del campo Descripción Tipo de campo
requestId Es el ID de la solicitud. Es único cuando se envían varias solicitudes a este extremo en una consulta de Google SQL. Siempre proporcionado. String.
emisor Es el nombre completo del recurso del trabajo para la consulta en GoogleSQL que llama a la función remota. Siempre proporcionado. String.
sessionUser Es el correo electrónico del usuario que ejecuta la consulta de GoogleSQL. Siempre proporcionado. String.
userDefinedContext Es el contexto definido por el usuario que se usó cuando se creó la función remota en BigQuery. Opcional. Un objeto JSON con pares clave-valor.
llamadas Un lote de datos de entrada. Siempre proporcionado. Un array JSON.

Cada elemento en sí es un array JSON, que es una lista de argumentos codificados en JSON de una llamada a función remota.

Ejemplo de una solicitud:

{
 "requestId": "124ab1c",
 "caller": "//bigquery.googleapis.com/projects/myproject/jobs/myproject:US.bquxjob_5b4c112c_17961fafeaf",
 "sessionUser": "test-user@test-company.com",
 "userDefinedContext": {
  "key1": "value1",
  "key2": "v2"
 },
 "calls": [
  [null, 1, "", "abc"],
  ["abc", "9007199254740993", null, null]
 ]
}

Formato de salida

BigQuery espera que el extremo muestre una respuesta HTTP en el siguiente formato; de lo contrario, BigQuery no podrá consumirlo y fallará la consulta que llama a la función remota.

Nombre del campo Descripción Rango de valores
replies Es un lote de valores de retorno. Obligatorio para obtener una respuesta correcta. Un array JSON.

Cada elemento corresponde a un valor de retorno codificado en JSON de la función externa.

El tamaño del array debe coincidir con el tamaño del array JSON de calls en la solicitud HTTP. Por ejemplo, si el array JSON en calls tiene 4 elementos, este array JSON también debe tener 4 elementos.

errorMessage Mensaje de error cuando se muestra el código de respuesta HTTP distinto de 200. En el caso de los errores que no se pueden reintentar, se muestra como parte del mensaje de error del trabajo de BigQuery al usuario. Opcional. String. El tamaño debe ser inferior a 1 KB.

Ejemplo de una respuesta correcta:

{
  "replies": [
    1,
    0
  ]
}

Ejemplo de una respuesta con errores:

{
  "errorMessage": "Received but not expected that the argument 0 be null".
}

Código de respuesta HTTP

El extremo debe mostrar el código de respuesta HTTP 200 para que sea una respuesta correcta. Cuando BigQuery recibe cualquier otro valor, considera la respuesta como una falla y vuelve a intentarlo cuando el código de respuesta HTTP es 408, 429, 500, 503 o 504 hasta algún límite interno.

Codificación JSON de tipo de datos SQL

La codificación JSON en la solicitud o respuesta HTTP sigue la codificación JSON de BigQuery existente para la función TO_JSON_STRING.

Código de Cloud Function de muestra

El siguiente código de muestra de Python implementa la adición de todos los argumentos de números enteros de la función remota. Controla una solicitud con los argumentos para invocaciones por lotes y muestra todos los resultados en una respuesta.

import functions_framework

from flask import jsonify

# Max INT64 value encoded as a number in JSON by TO_JSON_STRING. Larger values are encoded as
# strings.
# See https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_encodings
_MAX_LOSSLESS=9007199254740992

@functions_framework.http
def batch_add(request):
  try:
    return_value = []
    request_json = request.get_json()
    calls = request_json['calls']
    for call in calls:
      return_value.append(sum([int(x) if isinstance(x, str) else x for x in call if x is not None]))
    replies = [str(x) if x > _MAX_LOSSLESS or x < -_MAX_LOSSLESS else x for x in return_value]
    return_json = jsonify( { "replies":  replies } )
    return return_json
  except Exception as e:
    return jsonify( { "errorMessage": str(e) } ), 400

Si suponemos que la función se implementa en el proyecto my_gcf_project en la región us-east1 como el nombre de la función remote_add, se puede acceder a ella mediante el extremo https://us-east1-my_gcf_project.cloudfunctions.net/remote_add.

Código de Cloud Run de muestra

En el siguiente código de muestra de Python, se implementa un servicio web, que se puede compilar e implementar en Cloud Run para la misma funcionalidad.

import os

from flask import Flask, request, jsonify

# Max INT64 value encoded as a number in JSON by TO_JSON_STRING. Larger values are encoded as
# strings.
# See https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_encodings
_MAX_LOSSLESS=9007199254740992

app = Flask(__name__)

@app.route("/", methods=['POST'])
def batch_add():
  try:
    return_value = []
    request_json = request.get_json()
    calls = request_json['calls']
    for call in calls:
      return_value.append(sum([int(x) if isinstance(x, str) else x for x in call if x is not None]))
    replies = [str(x) if x > _MAX_LOSSLESS or x < -_MAX_LOSSLESS else x for x in return_value]
    return jsonify( { "replies" :  replies } )
  except Exception as e:
    return jsonify( { "errorMessage": str(e) } ), 400

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

Consulta la guía sobre cómo compilar y, luego, implementar el código.

Si suponemos que el servicio de Cloud Run se implementa en el proyecto my_gcf_project en la región us-east1 como el nombre del servicio remote_add, se puede acceder a él mediante el extremo https://remote_add-<project_id_hash>-ue.a.run.app.

Crea una función remota

BigQuery usa una conexión CLOUD_RESOURCE para interactuar con tu Cloud Function. Para crear una función remota, debes crear una conexión CLOUD_RESOURCE. Si creas la función remota a través de BigQuery Dataframes y se te otorgó el rol de administrador de IAM del proyecto (roles/resourcemanager.projectIamAdmin), entonces no tienes que crear la conexión y otorgarle acceso de forma manual; el servicio lo hace automáticamente.

Crear una conexión

Debes tener una conexión de recursos de Cloud para conectarte a Cloud Functions y Cloud Run.

Selecciona una de las opciones siguientes:

Console

  1. Ve a la página de BigQuery.

    Ir a BigQuery

  2. Para crear una conexión, haz clic en Agregar y, luego, en Conexiones a fuentes de datos externas.

  3. En la lista Tipo de conexión, selecciona Modelos remotos de Vertex AI, funciones remotas y BigLake (Cloud Resource).

  4. En el campo ID de conexión, escribe un nombre para tu conexión.

  5. Haga clic en Crear conexión.

  6. Haz clic en Ir a la conexión.

  7. En el panel Información de conexión, copia el ID de la cuenta de servicio para usarlo en un paso posterior.

bq

  1. En un entorno de línea de comandos, crea una conexión:

    bq mk --connection --location=REGION --project_id=PROJECT_ID \
        --connection_type=CLOUD_RESOURCE CONNECTION_ID
    

    El parámetro --project_id anula el proyecto predeterminado.

    Reemplaza lo siguiente:

    • REGION: tu región de conexión
    • PROJECT_ID: El ID del proyecto de Google Cloud.
    • CONNECTION_ID: Es un ID para tu conexión.

    Cuando creas un recurso de conexión, BigQuery crea una cuenta de servicio del sistema única y la asocia con la conexión.

    Solución de problemas: Si recibes el siguiente error de conexión, actualiza el SDK de Google Cloud:

    Flags parsing error: flag --connection_type=CLOUD_RESOURCE: value should be one of...
    
  2. Recupera y copia el ID de cuenta de servicio para usarlo en un paso posterior:

    bq show --connection PROJECT_ID.REGION.CONNECTION_ID
    

    El resultado es similar al siguiente:

    name                          properties
    1234.REGION.CONNECTION_ID     {"serviceAccountId": "connection-1234-9u56h9@gcp-sa-bigquery-condel.iam.gserviceaccount.com"}
    

Terraform

Agrega la siguiente sección a tu archivo main.tf.

 ## This creates a cloud resource connection.
 ## Note: The cloud resource nested object has only one output only field - serviceAccountId.
 resource "google_bigquery_connection" "connection" {
    connection_id = "CONNECTION_ID"
    project = "PROJECT_ID"
    location = "REGION"
    cloud_resource {}
}        
Reemplaza lo siguiente:

  • CONNECTION_ID: Es un ID para tu conexión.
  • PROJECT_ID: El ID del proyecto de Google Cloud.
  • REGION: tu región de conexión

Configura el acceso

Debes otorgar a la conexión nueva acceso de solo lectura a tu servicio de Cloud Functions o Cloud Run. No se recomienda permitir la invocación no autenticada para el servicio de Cloud Functions o Cloud Run.

Para otorgar roles, sigue estos pasos:

  1. Ir a la página IAM y administración

    Ir a IAM y administración

  2. Haga clic en Agregar.

    Se abre el cuadro de diálogo Agregar principales.

  3. En el campo Principales nuevas (New principals), ingresa el ID de la cuenta de servicio que copiaste antes.

  4. En el campo Seleccionar un rol, selecciona una de las siguientes opciones:

    • Si usas una Cloud Function de 1ª gen., elige Cloud Function y, luego, selecciona Rol de invocador de Cloud Functions.
    • Si usas una Cloud Function de 2ª gen., elige Cloud Run y, luego, selecciona Rol de invocador de Cloud Run.
    • Si usas un servicio de Cloud Run, elige Cloud Run y, luego, selecciona Rol de invocador de Cloud Run.
  5. Haz clic en Guardar.

Crea una función remota

Para crear una función remota, sigue estos pasos:

SQL

Ejecuta la siguiente declaración CREATE FUNCTION en BigQuery:

  1. En la consola de Google Cloud, ve a la página de BigQuery.

    Ir a BigQuery

  2. En el editor de consultas, escribe la siguiente oración:

    CREATE FUNCTION PROJECT_ID.DATASET_ID.remote_add(x INT64, y INT64) RETURNS INT64
    REMOTE WITH CONNECTION PROJECT_ID.LOCATION.CONNECTION_NAME
    OPTIONS (
      endpoint = 'ENDPOINT_URL'
    )
    

    Reemplaza lo siguiente:

    • DATASET_ID: el ID del conjunto de datos de BigQuery.
    • ENDPOINT_URL: la URL de tu extremo de función remota de Cloud Function o Cloud Run.

  3. Haz clic en Ejecutar.

Para obtener más información sobre cómo ejecutar consultas, visita Ejecuta una consulta interactiva.

BigQuery DataFrames

BigQuery Dataframes está en vista previa.

  1. Habilita las APIs necesarias y asegúrate de que se te hayan otorgado los roles necesarios, como se describe en la sección Requisitos de Funciones remotas.
  2. Usa el decorador remote_function.

    import bigframes.pandas as bpd
    
    # Set BigQuery DataFrames options
    bpd.options.bigquery.project = your_gcp_project_id
    bpd.options.bigquery.location = "us"
    
    # BigQuery DataFrames gives you the ability to turn your custom scalar
    # functions into a BigQuery remote function. It requires the GCP project to
    # be set up appropriately and the user having sufficient privileges to use
    # them. One can find more details about the usage and the requirements via
    # `help` command.
    help(bpd.remote_function)
    
    # Read a table and inspect the column of interest.
    df = bpd.read_gbq("bigquery-public-data.ml_datasets.penguins")
    df["body_mass_g"].head(10)
    
    # Define a custom function, and specify the intent to turn it into a remote
    # function. It requires a BigQuery connection. If the connection is not
    # already created, BigQuery DataFrames will attempt to create one assuming
    # the necessary APIs and IAM permissions are setup in the project. In our
    # examples we would be using a pre-created connection named
    # `bigframes-rf-conn`. We will also set `reuse=False` to make sure we don't
    # step over someone else creating remote function in the same project from
    # the exact same source code at the same time. Let's try a `pandas`-like use
    # case in which we want to apply a user defined scalar function to every
    # value in a `Series`, more specifically bucketize the `body_mass_g` value
    # of the penguins, which is a real number, into a category, which is a
    # string.
    @bpd.remote_function(
        [float],
        str,
        bigquery_connection="bigframes-rf-conn",
        reuse=False,
    )
    def get_bucket(num):
        if not num:
            return "NA"
        boundary = 4000
        return "at_or_above_4000" if num >= boundary else "below_4000"
    
    # Then we can apply the remote function on the `Series`` of interest via
    # `apply` API and store the result in a new column in the DataFrame.
    df = df.assign(body_mass_bucket=df["body_mass_g"].apply(get_bucket))
    
    # This will add a new column `body_mass_bucket` in the DataFrame. You can
    # preview the original value and the bucketized value side by side.
    df[["body_mass_g", "body_mass_bucket"]].head(10)
    
    # The above operation was possible by doing all the computation on the
    # cloud. For that, there is a google cloud function deployed by serializing
    # the user code, and a BigQuery remote function created to call the cloud
    # function via the latter's http endpoint on the data in the DataFrame.
    
    # The BigQuery remote function created to support the BigQuery DataFrames
    # remote function can be located via a property `bigframes_remote_function`
    # set in the remote function object.
    print(f"Created BQ remote function: {get_bucket.bigframes_remote_function}")
    
    # The cloud function can be located via another property
    # `bigframes_cloud_function` set in the remote function object.
    print(f"Created cloud function: {get_bucket.bigframes_cloud_function}")
    
    # Warning: The deployed cloud function may be visible to other users with
    # sufficient privilege in the project, so the user should be careful about
    # having any sensitive data in the code that will be deployed as a remote
    # function.
    
    # Let's continue trying other potential use cases of remote functions. Let's
    # say we consider the `species`, `island` and `sex` of the penguins
    # sensitive information and want to redact that by replacing with their hash
    # code instead. Let's define another scalar custom function and decorate it
    # as a remote function
    @bpd.remote_function(
        [str], str, bigquery_connection="bigframes-rf-conn", reuse=False
    )
    def get_hash(input):
        import hashlib
    
        # handle missing value
        if input is None:
            input = ""
        encoded_input = input.encode()
        hash = hashlib.md5(encoded_input)
        return hash.hexdigest()
    
    # We can use this remote function in another `pandas`-like API `map` that
    # can be applied on a DataFrame
    df_redacted = df[["species", "island", "sex"]].map(get_hash)
    df_redacted.head(10)
    
    

Debes tener el permiso bigquery.routines.create en el conjunto de datos en el que creas la función remota y el permiso bigquery.connections.delegate (disponible a través del rol Connection Admin de BigQuery) en la conexión que usa la función remota.

Proporciona contexto definido por el usuario

Puedes especificar user_defined_context en OPTIONS como una forma de pares clave-valor, que serán parte de cada solicitud HTTP al extremo. Con el contexto definido por el usuario, puedes crear varias funciones remotas, pero reutilizar un solo extremo, el cual proporciona diferentes comportamientos según el contexto que se le pasa.

En los siguientes ejemplos, se crean dos funciones remotas para encriptar y desencriptar datos BYTES mediante el mismo extremo.

CREATE FUNCTION `PROJECT_ID.DATASET_ID`.encrypt(x BYTES)
RETURNS BYTES
REMOTE WITH CONNECTION `PROJECT_ID.LOCATION.CONNECTION_NAME`
OPTIONS (
  endpoint = 'ENDPOINT_URL',
  user_defined_context = [("mode", "encryption")]
)

CREATE FUNCTION `PROJECT_ID.DATASET_ID`.decrypt(x BYTES)
RETURNS BYTES
REMOTE WITH CONNECTION `PROJECT_ID.LOCATION.CONNECTION_NAME`
OPTIONS (
  endpoint = 'ENDPOINT_URL',
  user_defined_context = [("mode", "decryption")]
)

Limita la cantidad de filas en una solicitud por lotes

Puedes especificar max_batching_rows en OPTIONS como la cantidad máxima de filas en cada solicitud HTTP para evitar el tiempo de espera de Cloud Functions. Si no se especifica, BigQuery decidirá cuántas filas se incluyen en un lote.

Usa una función remota en una consulta

Asegúrate de haber otorgado el permiso en tu Cloud Function para que pueda acceder la cuenta de servicio de BigQuery asociada con la conexión de la función remota.

También debes tener el permiso bigquery.routines.get en el conjunto de datos donde se encuentra el rol remota, y el permiso bigquery.connections.use, que puedes obtenerBigQuery Connection User en la conexión que usa la función remota.

Puedes usar una función remota en una consulta como una función definida por el usuario.

Por ejemplo, puedes usar la función remote_add en la consulta de ejemplo:

SELECT
  val,
  `PROJECT_ID.DATASET_ID`.remote_add(val, 2)
FROM
  UNNEST([NULL,2,3,5,8]) AS val;

En este ejemplo, se produce el siguiente resultado:

+------+-----+
|  val | f0_ |
+------+-----+
| NULL |   2 |
|    2 |   4 |
|    3 |   5 |
|    5 |   7 |
|    8 |  10 |
+------+-----+

Regiones admitidas

Hay dos tipos de ubicaciones en BigQuery:

  • Una región es un lugar geográfico específico, como Londres.

  • Una multirregión es un área geográfica grande, como Estados Unidos, que contiene dos o más lugares geográficos.

Regiones individuales

En un conjunto de datos de una sola región de BigQuery, solo puedes crear una función remota que use una Cloud Function implementada en la misma región. Por ejemplo:

  • Una función remota en la región individual us-east4 de BigQuery solo puede usar una Cloud Function de us-east4.

Por lo tanto, para regiones individuales, las funciones remotas solo son compatibles en regiones que admiten Cloud Functions y BigQuery.

Multirregiones

En un conjunto de datos multirregión de BigQuery (US, EU), solo puedes crear una función remota que use una función de Cloud Functions implementada en una región dentro de la misma área geográfica grande (US, EU). Por ejemplo:

  • Una función remota en la multirregión US de BigQuery solo puede usar una Cloud Function implementada en una sola región del área geográfica US, como us-central1, us-east4, us-west2, etcétera.
  • Una función remota en la multirregión EU de BigQuery solo puede usar una Cloud Function implementada en cualquier región individual de los estados miembros de la Unión Europea, como europe-north1, europe-west3, etcétera.

Para obtener más información sobre regiones y multirregiones de BigQuery, consulta la página Ubicaciones de conjuntos de datos. Para obtener más información sobre las regiones de Cloud Functions, consulta la página Ubicaciones de Cloud Functions.

Conexiones

Para una ubicación de una sola región o multirregional, solo puedes crear una función remota en la misma ubicación que la conexión que usas. Por ejemplo, para crear una función remota en la multirregión US, usa una conexión ubicada en la multirregión US.

Precios

Usa los Controles del servicio de VPC

Controles del servicio de VPC es una función de Google Cloud que te permite configurar un perímetro seguro para protegerte del robo de datos. Si deseas usar los Controles del servicio de VPC con funciones remotas para obtener seguridad adicional o usar extremos con configuración de entrada internal traffic, sigue la Guía de los Controles del servicio de VPC para:

  1. Crear un perímetro de servicio

  2. Agrega el proyecto de BigQuery de la consulta con la función remota en el perímetro.

  3. Agrega el proyecto de extremo al perímetro y configura Cloud Functions API o Cloud Run API en los servicios restringidos según el tipo de extremo. Para obtener más detalles, consulta Controles del servicio de VPC de Cloud Functions y Controles del servicio de VPC de Cloud Run.

Prácticas recomendadas para las funciones remotas

  • Filtra previamente tu entrada: si tu entrada se puede filtrar con facilidad antes de pasarla a una función remota, es probable que tu consulta sea más rápida y económica.

  • Mantén la escalabilidad de tu Cloud Function. La escalabilidad es una función de instancias mínimas, instancias máximas y simultaneidad.

    • Cuando sea posible, usa el valor predeterminado para la cantidad máxima de instancias de tu Cloud Function.
    • Ten en cuenta que no hay un límite predeterminado para las funciones de HTTP de Cloud Functions de 1ª gen. Para evitar eventos de escalamiento ilimitados con Cloud Functions HTTP de 1a gen. durante la prueba o en producción, recomendamos configurar un límite, por ejemplo, 3,000.
  • Sigue otras sugerencias de Cloud Functions para obtener un mejor rendimiento. Las consultas de funciones remotas que interactúan con una Cloud Function de latencia alta pueden fallar debido al tiempo de espera.

  • Implementa tu extremo a fin de que muestre un código de respuesta HTTP correcto y una carga útil para una respuesta con errores.

    • Para minimizar los reintentos de BigQuery, usa códigos de respuesta HTTP distintos de 408, 429, 500, 503 y 504 para una respuesta con errores, y asegúrate de detectar todas las excepciones en el código de la función. De lo contrario, el marco de trabajo del servicio HTTP puede mostrar automáticamente el código 500 para cualquier excepción que no se detecte. Es posible que veas solicitudes HTTP que se reintentaron cuando BigQuery vuelva a intentar una partición de datos o una consulta con errores.

    • El extremo debe mostrar una carga útil de JSON en el formato definido para una respuesta con errores. Aunque no es estrictamente obligatorio, ayuda a BigQuery a distinguir si la respuesta con errores es de la implementación de la función o de la infraestructura de Cloud Functions o Cloud Run. Para este último, BigQuery puede volver a intentarlo con un límite interno diferente.

Cuotas

Para obtener información sobre las cuotas de las funciones remotas, consulta Cuotas y límites.