Desenvolver aplicativos com o GoogleSQL para Bigtable

Você pode usar o GoogleSQL para Bigtable para executar consultas dos seus aplicativos e descarregar funções de computação no Bigtable. Isso reduz a necessidade de processamento do lado do cliente. O GoogleSQL é uma linguagem de consulta estruturada compatível com ANSI implementada para o Bigtable e outros Google Cloud serviços. Para mais informações sobre outros usos do GoogleSQL para Bigtable, consulte a Visão geral do GoogleSQL para Bigtable.

Antes de começar

Verifique se você tem o seguinte:

Exemplo de cenário

Os exemplos neste guia usam uma tabela de amostra weather-data que pode ser acessada na instância de teste sem custo financeiro no Google Cloud console. Suponha que sua tabela armazene informações de diferentes estações meteorológicas identificadas por um station_id. Cada estação registra a temperatura na escala Fahrenheit em uma coluna chamada temp_f. Em vez de calcular a temperatura no aplicativo, você pode usar uma consulta SQL para converter essas temperaturas para a escala Celsius diretamente no Bigtable antes de enviar os resultados de volta ao aplicativo. Para mais informações sobre como consultar a tabela de dados meteorológicos, consulte Consultar dados de amostra.

Práticas recomendadas

Para otimizar o desempenho e o uso de recursos, considere as seguintes práticas recomendadas ao desenvolver aplicativos:

  • Reutilizar o cliente:crie um único BigtableDataClient para seu aplicativo. O cliente processa o gerenciamento de canais gRPC e foi projetado para alta simultaneidade. Recomendamos que você crie esse cliente uma vez após a inicialização do aplicativo e o reutilize para cada consulta.
  • Usar consultas parametrizadas:use PreparedStatement para separar a lógica de consulta dos valores de dados. Isso permite que o Bigtable armazene em cache o plano de execução e evita a injeção de SQL.
  • Reutilizar instruções preparadas:chame PreparedStatement apenas uma vez por string SQL. Armazene e reutilize a instrução em várias solicitações.

Criar o cliente do Bigtable

Para processar a conexão entre o aplicativo e o Bigtable, crie um BigtableDataClient. O exemplo a seguir mostra como compartilhar uma única instância de cliente no aplicativo para gerenciar conexões com eficiência:

BigtableDataSettings settings = BigtableDataSettings.newBuilder()
    .setProject(PROJECT_ID)
    .setInstance(INSTANCE_ID)
    .build();

// The client is thread-safe and should be reused.
try (BigtableDataClient dataClient = BigtableDataClient.create(settings)) {
    System.out.println("Connected to: " + INSTANCE_ID);
}

Preparar a consulta

Em vez de colocar os dados diretamente em uma string SQL, recomendamos o uso de consultas parametrizadas. No GoogleSQL para Bigtable, as consultas parametrizadas são equivalentes a uma instrução preparada. Isso permite separar a lógica de consulta dos valores de dados.

O exemplo a seguir mostra como definir sua consulta do GoogleSQL usando marcadores de posição de parâmetro, como @sid. Em seguida, crie um PreparedStatement especificando a string SQL e os tipos de cada parâmetro para que a consulta possa ser executada mais rapidamente na próxima vez:

Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("sid", SqlType.string());

String sql = "SELECT " +
             "ROUND((CAST(CAST(weather['temp_f'] AS STRING) AS INT64) - 32) * 5 / 9, 2) AS temp_celsius " +
             "FROM " + TABLE_NAME + " WHERE station_id = @sid LIMIT 1";

// Create and reuse the PreparedStatement.
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);

Executar a consulta

Forneça valores de parâmetro e execute a consulta. O exemplo a seguir mostra como usar o PreparedStatement para vincular valores aos parâmetros e criar um BoundStatement. Em seguida, ele executa a consulta e faz um loop nos resultados para imprimir o ResultSet, que é a temperatura em graus Celsius:

BoundStatement boundStatement = preparedStatement.bind()
    .setStringParam("sid", stationId)
    .build();

try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
    while (resultSet.next()) {
        System.out.println("Temp: " + resultSet.getDouble("temp_celsius"));
    }
}

Verificar a estrutura de dados

Ao desenvolver ou explorar o SQL, recomendamos que você explore os metadados associados à tabela para entender a estrutura de dados. Como o Bigtable tem um esquema flexível, se você usar consultas SELECT *, os resultados da consulta poderão mudar com base nos dados. Portanto, recomendamos que você não use consultas SELECT * em aplicativos de produção.

O exemplo a seguir analisa os metadados do conjunto de resultados (como os nomes e tipos de coluna) e os envia de volta ao cliente de forma independente dos dados. Isso permite que você receba as informações da coluna primeiro e crie a estrutura de dados necessária para processar os dados no aplicativo.

ResultSetMetadata metadata = resultSet.getMetadata();
System.out.print("Columns: ");
metadata.getColumnsList().forEach(col -> System.out.print(col.getName() + " "));
System.out.println();

Trabalhar com grupos de colunas

O Bigtable armazena dados em grupos de colunas. O GoogleSQL retorna essas famílias como listas de nomes e valores conhecidos como mapas de qualificadores e valores de coluna. Ao ler um grupo de colunas, o Bigtable retorna o tipo SqlType.Map. Ao ler um grupo de colunas de uma tabela com histórico, como SELECT column FROM my_table(with_history=>true), o Bigtable retorna um tipo SqlType.historicalMap(), que é um mapa de qualificadores de coluna e uma matriz de carimbos de data/hora e valores de célula. Para mais exemplos de SqlType tipos, consulte SqlType.

O exemplo a seguir mostra como, em vez de receber uma coluna por vez, você pode receber um grupo inteiro de colunas de uma só vez. O código armazena o grupo de colunas em um mapa para que você possa fazer um loop em todos os nomes e valores:

Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("keyPrefix", SqlType.bytes());
String sql = String.format("SELECT weather FROM %s WHERE STARTS_WITH(_key, @keyPrefix)", TABLE_NAME);
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);
BoundStatement boundStatement = preparedStatement.bind()
   .setBytesParam("keyPrefix", ByteString.copyFromUtf8(key))
   .build();


try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
   while (resultSet.next()) {
     SqlType.Map<ByteString, ByteString> mapType = SqlType.mapOf(SqlType.bytes(), SqlType.bytes());
     Map<ByteString, ByteString> weatherValues = resultSet.getMap("weather", mapType);
     for (Map.Entry<ByteString, ByteString> entry : weatherValues.entrySet()) {
       System.out.printf("%s = %s\n",
         entry.getKey().toStringUtf8(),
         entry.getValue().toStringUtf8());
   }
 }
}

A seguir