Trabalhar com funções remotas

Uma função remota do BigQuery permite implementar a função em outras linguagens além do SQL e do JavaScript ou com bibliotecas ou serviços que não são permitidos nas funções definidas pelo usuário do BigQuery.

Visão geral

Uma função remota do BigQuery permite incorporar a funcionalidade do SQL do BigQuery com software fora do BigQuery fornecendo uma integração direta com o Cloud Functions e o Cloud Run. Com as funções remotas do BigQuery, é possível implantar as funções no Cloud Functions ou no Cloud Run implementadas com qualquer linguagem compatível e, em seguida, invocá-las em consultas do GoogleSQL.

Fluxo de trabalho

  1. Crie o endpoint HTTP no Cloud Functions ou no Cloud Run.
  2. Crie uma função remota no BigQuery.
    1. Crie uma conexão do tipo CLOUD_RESOURCE.
    2. Crie uma função remota.
  3. Use a função remota em uma consulta como qualquer outra função definida pelo usuário.

Limitações

  • As funções remotas são compatíveis apenas com um dos seguintes tipos de dados como tipo de argumento ou de retorno:

    • Booleano
    • Bytes
    • Numérico
    • String
    • Data
    • Datetime
    • Momento
    • Carimbo de data/hora
    • JSON

    As funções remotas não são compatíveis com os tipos ARRAY, STRUCT, INTERVAL ou GEOGRAPHY.

  • Não é possível criar funções remotas temporárias.

  • Não é possível criar funções remotas com valor de tabela.

  • Não é possível usar funções remotas ao criar visualizações materializadas.

  • O valor de retorno de uma função remota é sempre considerado não determinístico. Portanto, o resultado de uma consulta que chama uma função remota não é armazenado em cache.

  • É possível ter solicitações repetidas com os mesmos dados para o endpoint, mesmo após respostas bem-sucedidas, devido a erros transitórios de rede ou internos do BigQuery.

  • Quando uma avaliação de função remota é ignorada para algumas linhas devido a curto-circuito, por exemplo, emexpressões condicionais ou aMERGE declaração comWHEN [NOT] MATCHED, os lotes não serão usados com a função remota. Nesse caso, o campo calls no corpo da solicitação HTTP tem exatamente um elemento.

  • Se o conjunto de dados associado à função remota for replicado para uma região de destino por meio da replicação do conjunto de dados entre regiões, a função remota só poderá ser consultada na região em que foi criado.

Crie um endpoint

Para criar uma função remota que possa implementar a lógica de negócios, é preciso criar um endpoint HTTP usando o Cloud Functions ou o Cloud Run. O endpoint precisa processar um lote de linhas em uma única solicitação POST HTTP e retornar os resultados em lote como uma resposta HTTP.

Se estiver criando a função remota usando o BigQuery DataFrames, não será necessário criar manualmente o endpoint HTTP. O serviço faz isso para você automaticamente.

Consulte o tutorial do Cloud Functions e outras documentações do Cloud Functions para saber como escrever, implantar, testar e manter uma função do Cloud.

Consulte o guia de início rápido do Cloud Run e outras documentações do Cloud Run sobre como escrever, implantar, testar e manter um serviço do Cloud Run.

É recomendável manter a autenticação padrão, e não permitir a invocação não autenticada da função do Cloud ou do serviço do Cloud Run.

Formato da entrada

O BigQuery envia solicitações HTTP POST com corpo JSON no seguinte formato:

Nome do campo Descrição Tipo de campo
requestId ID da solicitação. Exclusivo em várias solicitações enviadas para esse endpoint em uma consulta GoogleSQL. Sempre fornecido. String.
autor da chamada Nome completo do recurso do job para a consulta GoogleSQL que chama a função remota. Sempre fornecido. String.
sessionUser E-mail do usuário que está executando a consulta GoogleSQL. Sempre fornecido. String.
userDefinedContext O contexto definido pelo usuário que foi usado ao criar a função remota no BigQuery. Opcional. Um objeto JSON com pares de chave-valor.
chamadas Um lote de dados de entrada. Sempre fornecido. Uma matriz JSON.

Cada elemento é uma matriz JSON, que é uma lista de argumentos codificada em JSON de uma chamada de função remota.

Exemplo de solicitação:

{
 "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 da saída

O BigQuery espera que o endpoint retorne uma resposta HTTP no formato a seguir. Caso contrário, o BigQuery não poderá consumir o endpoint e falhará na consulta que chama a função remota.

Nome do campo Descrição Intervalo de valor
respostas Um lote de valores de retorno. Obrigatório para uma resposta bem-sucedida. Uma matriz JSON.

Cada elemento corresponde a um valor de retorno codificado em JSON da função externa.

O tamanho da matriz precisa ser igual ao tamanho da matriz JSON de calls na solicitação HTTP. Por exemplo, se a matriz JSON em calls tiver quatro elementos, esta matriz JSON precisará ter quatro elementos.

errorMessage Mensagem de erro quando o código de resposta HTTP diferente de 200 é retornado. Para erros que não podem ser repetidos, retornamos como parte da mensagem de erro do job do BigQuery para o usuário. Opcional. String. O tamanho precisa ser inferior a 1 KB.

Exemplo de resposta bem-sucedida:

{
  "replies": [
    1,
    0
  ]
}

Exemplo de resposta com falha:

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

Código de resposta HTTP

Seu endpoint retornará o código de resposta HTTP 200 para uma resposta bem-sucedida. Quando o BigQuery recebe qualquer outro valor, ele considera a resposta como uma falha e tenta novamente quando o código de resposta HTTP é 408, 429, 500, 503 ou 504 até algum limite interno.

Codificação JSON de tipos de dados SQL

A codificação JSON em solicitações/respostas HTTP segue a atual codificação JSON do BigQuery para a função TO_JSON_STRING.

Exemplo de código de função do Cloud

O código Python de amostra a seguir implementa a adição de todos os argumentos inteiros da função remota. Ele processa uma solicitação com os argumentos para invocações em lote e retorna todo o resultado em uma resposta.

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

Supondo que a função seja implantada no projeto my_gcf_project na região us-east1 como o nome da função remote_add, ela pode ser acessada pelo endpoint https://us-east1-my_gcf_project.cloudfunctions.net/remote_add.

Exemplo de código do Cloud Run

O exemplo de código Python a seguir implementa um serviço da Web, que pode ser criado e implantado no Cloud Run para a mesma funcionalidade.

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)))

Consulte o guia sobre como criar e implantar o código.

Supondo que o serviço do Cloud Run seja implantado no projeto my_gcf_project na região us-east1 como o nome do serviço remote_add, ele pode ser acessado pelo endpoint https://remote_add-<project_id_hash>-ue.a.run.app.

Criar uma função remota

O BigQuery usa uma conexão CLOUD_RESOURCE para interagir com a função do Cloud. Para criar uma função remota, você precisa criar uma conexão CLOUD_RESOURCE. Se estiver criando a função remota usando o BigQuery DataFrames e você recebeu o administrador do IAM do projeto (roles/resourcemanager.projectIamAdmin), você não precisará criar manualmente a conexão e conceder acesso a ela. O serviço faz isso para você automaticamente.

Criar uma conexão

Você precisa ter uma conexão de recursos do Cloud para se conectar ao Cloud Function e ao Cloud Run.

Selecione uma das seguintes opções:

Console

  1. Acessar a página do BigQuery.

    Acessar o BigQuery

  2. Para criar uma conexão, clique em Adicionar e em Conexões com fontes de dados externas.

  3. Na lista Tipo de conexão, selecione Modelos remotos da Vertex AI, funções remotas e BigLake (Cloud Resource).

  4. No campo ID da conexão, insira um nome para a conexão.

  5. Clique em Criar conexão.

  6. Clique em Ir para conexão.

  7. No painel Informações da conexão, copie o ID da conta de serviço para uso em uma etapa posterior.

bq

  1. Em um ambiente de linha de comando, crie uma conexão:

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

    O parâmetro --project_id substitui o projeto padrão.

    Substitua:

    • REGION: sua região de conexão
    • PROJECT_ID: o ID do projeto do Google Cloud
    • CONNECTION_ID: um ID para sua conexão

    Quando você cria um recurso de conexão, o BigQuery cria uma conta de serviço do sistema exclusiva e a associa à conexão.

    Solução de problemas: se você receber o seguinte erro de conexão, atualize o SDK Google Cloud:

    Flags parsing error: flag --connection_type=CLOUD_RESOURCE: value should be one of...
    
  2. Recupere e copie o ID da conta de serviço para uso em uma etapa posterior:

    bq show --connection PROJECT_ID.REGION.CONNECTION_ID

    O resultado será assim:

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

Terraform

Anexe a seguinte seção ao seu arquivo 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 {}
}        
Substitua:

  • CONNECTION_ID: um ID para sua conexão
  • PROJECT_ID: o ID do projeto do Google Cloud
  • REGION: sua região de conexão

Configurar o acesso

Conceda à nova conexão acesso somente leitura à função do Cloud ou do Cloud Run. Não recomendamos invocações não autenticadas para seu serviço do Cloud Functions ou do Cloud Run.

Para conceder papéis, siga estas etapas:

  1. Acessar a página AM e administrador

    Acessar IAM e administrador

  2. Clique em Adicionar.

    A caixa de diálogo Adicionar principais é aberta.

  3. No campo Novos principais, digite o ID da conta de serviço que você copiou anteriormente.

  4. No campo Selecionar uma função, escolha uma das seguintes opções:

    • Se você estiver usando uma Função do Cloud de primeira geração, escolha Função do Cloud e selecione Papel de invocador da Função do Cloud.
    • Se você estiver usando uma Função do Cloud de segunda geração, escolha Cloud Run e selecione Papel de invocador do Cloud Run.
    • Se você estiver usando um serviço do Cloud Run, escolha Cloud Run e selecione Papel de invocador do Cloud Run.
  5. Clique em Save.

Criar uma função remota

Para criar uma função remota:

SQL

Execute a seguinte instrução CREATE FUNCTION no BigQuery:

  1. No Console do Google Cloud, acesse a página BigQuery.

    Ir para o BigQuery

  2. No editor de consultas, digite a seguinte instrução:

    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'
    )

    Substitua:

    • DATASET_ID: o ID do conjunto de dados do BigQuery.
    • ENDPOINT_URL: o URL da função do Cloud ou do endpoint da função remota do Cloud Run.

  3. Clique em Executar.

Para mais informações sobre como executar consultas, acesse Executar uma consulta interativa.

BigQuery DataFrames

O BigQuery DataFrames está em visualização.

  1. Ative as APIs necessárias e verifique se você recebeu os papéis necessários, conforme descrito na seção Requisitos de Funções remotas.
  2. Use o 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)
    
    

Na conexão usada pela função remota, você precisa ter as permissões bigquery.routines.create no conjunto de dados em que a função remota foi criada e a permissão bigquery.connections.delegate (disponível no papel Administrador de conexões do BigQuery) na conexão usada para a função remota.

Como fornecer contexto definido pelo usuário

É possível especificar user_defined_context em OPTIONS como uma forma de pares de chave-valor, que vão estar em todas as solicitações HTTP para o endpoint. Com o contexto definido pelo usuário, é possível criar várias funções remotas, mas reutilizar um único endpoint, que oferece vários comportamentos com base no contexto passado para ele.

Os exemplos a seguir criam duas funções remotas para criptografar e descriptografar dados de BYTES usando o mesmo endpoint.

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")]
)

Como limitar o número de linhas em uma solicitação em lote

É possível especificar max_batching_rows em OPTIONS como o número máximo de linhas em cada solicitação HTTP para evitar o tempo limite do Cloud Functions. Se não for especificado, o BigQuery vai decidir quantas linhas serão incluídas em um lote.

Usar uma função remota em uma consulta

Verifique se você concedeu a permissão na função do Cloud para que fique acessível à conta de serviço do BigQuery associada à conexão da função remota.

Você também precisa ter a permissão bigquery.routines.get no conjunto de dados em que a função está e a permissão bigquery.connections.use, que você pode receber pelo papel BigQuery Connection User na conexão usada pela função remota.

Use uma função remota em uma consulta da mesma forma que uma função definida pelo usuário.

É possível usar a função remote_add na consulta de exemplo:

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

Este exemplo produz a saída a seguir:

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

Regiões compatíveis

Há dois tipos de local no BigQuery:

  • Uma região é um lugar geográfico específico, como Londres.

  • Um local multirregional é uma área geográfica grande, como Estados Unidos, que contém dois ou mais lugares geográficos.

Regiões únicas

Em um conjunto de dados de região única do BigQuery, só é possível criar uma função remota que use uma função do Cloud implantada na mesma região. Por exemplo:

  • Uma função remota em uma região única us-east4 do BigQuery só pode usar uma função do Cloud em us-east4.

Portanto, para regiões únicas, as funções remotas têm suporte apenas em regiões compatíveis com o Cloud Functions e o BigQuery.

Multirregiões

Em um conjunto de dados multirregional (US, EU) do BigQuery, só é possível criar uma função remota que use uma função do Cloud implantada em uma região dentro da mesma grande área geográfica (EUA, UE). Exemplo:

  • Uma função remota na multirregião US só pode usar uma função do Cloud implantada em qualquer região da área geográfica dos EUA, como us-central1, us-east4, us-west2 etc.
  • Uma função remota na multirregião EU do BigQuery só pode usar uma função do Cloud implantada em qualquer região única nos estados-membros da União Europeia, como europe-north1, europe-west3 etc.

Saiba mais sobre regiões e multirregiões na página Locais dos conjuntos de dados. Saiba mais sobre as regiões do Cloud Functions na página Locais do Cloud Functions.

Conexões

Para os locais de região única ou multirregiões, só é possível criar uma função remota no mesmo local que a conexão usada. Por exemplo, para criar uma função remota na multirregião US, use uma conexão localizada na multirregião US.

Preços

Como usar VPC Service Controls

O VPC Service Controls é um recurso do Google Cloud em que é possível configurar um perímetro seguro para evitar a exfiltração de dados. Para usar o VPC Service Controls com funções remotas para aumentar a segurança ou para usar endpoints com configurações de entrada internal traffic, siga a Guia do VPC Service Controls para:

  1. criar um perímetro de serviço;

  2. Adicione o projeto do BigQuery da consulta usando a função remota no perímetro.

  3. Adicione o projeto de endpoint ao perímetro e defina Cloud Functions API ou Cloud Run API nos serviços restritos com base no seu tipo de endpoint. Confira mais detalhes em VPC Service Controls do Cloud Functions e Controles de serviço da VPC do Cloud Run.

Práticas recomendadas em funções remotas

  • Pré-filtre a entrada: se sua entrada puder ser filtrada com facilidade antes de ser passada para uma função remota, a consulta poderá ficar mais rápida e mais barata.

  • Mantenha a Função do Cloud escalonável. A escalonabilidade é uma função do número mínimo de instâncias, do máximo de instâncias e da simultaneidade.

    • Sempre que possível, use o valor padrão para o número máximo de instâncias da função do Cloud.
    • Observe que não há limite padrão para o Cloud Functions HTTP de primeira geração. Para evitar eventos de escalonamento ilimitado com o Cloud Functions HTTP de primeira geração durante testes ou na produção, recomendamos definir um limite. Por exemplo, 3000.
  • Siga outras dicas do Cloud Functions para melhorar o desempenho. As consultas de funções remotas que interagem com uma função do Cloud de alta latência podem falhar devido ao tempo limite.

  • Implemente seu endpoint para retornar um código de resposta HTTP e um payload certos para uma resposta com falha.

    • Para minimizar as tentativas do BigQuery, use códigos de resposta HTTP que não sejam 408, 429, 500, 503 e 504 para uma resposta com falha e verifique todas as exceções no código da função. Caso contrário, o framework de serviço HTTP poderá retornar automaticamente 500 para quaisquer exceções não identificadas. Ainda é possível ter novas solicitações HTTP quando o BigQuery tenta executar novamente uma partição ou consulta de dados com falha.

    • Seu endpoint precisa retornar um payload JSON no formato definido para uma resposta com falha. Mesmo que não seja estritamente obrigatório, ajuda o BigQuery a diferenciar se a resposta com falha é da implementação da função ou da infraestrutura do Cloud Functions/Cloud Run. Nesse caso, o BigQuery pode tentar novamente com um limite interno diferente.

Cotas

Para saber mais sobre cotas de funções remotas, consulte Cotas e limites.