Introdução ao Cloud Bigtable

1. Introdução

Neste codelab, você vai aprender a usar o Cloud Bigtable com o cliente HBase Java.

Você aprenderá como realizar as seguintes tarefas:

  • Evitar erros comuns no design de esquema
  • Importar dados em um arquivo sequencial
  • Consultar dados

Quando terminar, você terá vários mapas que mostram os dados de ônibus da cidade de Nova York. Por exemplo, você vai criar este mapa de calor de viagens de ônibus em Manhattan:

7349d94f7d41f1d1.png

Como você classificaria sua experiência com o uso do Google Cloud Platform?

Iniciante Intermediário Proficiente

Como você usará este tutorial?

Apenas leitura Leitura e exercícios

2. Sobre o conjunto de dados

Você vai analisar um conjunto de dados sobre os ônibus da cidade de Nova York. Há mais de 300 rotas de ônibus e 5.800 veículos seguindo essas rotas. Nosso conjunto de dados é um registro que inclui nome do destino, ID do veículo, latitude, longitude, horário de chegada esperado e horário de chegada programado. O conjunto de dados é composto por snapshots tirados a cada 10 minutos em junho de 2017.

3. Design de esquema

Para conseguir o melhor desempenho do Cloud Bigtable, é preciso ter cuidado ao projetar o esquema. Os dados no Cloud Bigtable são classificados automaticamente em ordem lexicográfica. Portanto, se você projetar bem o esquema, a consulta de dados relacionados será muito eficiente. O Cloud Bigtable permite consultas usando pesquisas pontuais por chave de linha ou verificações de intervalo de linhas que retornam um conjunto contíguo de linhas. No entanto, se o esquema não for bem elaborado, você pode precisar juntar várias pesquisas de linha ou, pior, fazer verificações completas da tabela, que são operações extremamente lentas.

Planejar as consultas

Nossos dados têm várias informações, mas, para este codelab, você vai usar o local e o destino do ônibus.

Com essas informações, você pode executar estas consultas:

  • Confira a localização de um único ônibus em uma determinada hora.
  • Receba dados de um dia para uma linha de ônibus ou um ônibus específico.
  • Encontra todos os ônibus em um retângulo em um mapa.
  • Receba os locais atuais de todos os ônibus (se você estiver ingerindo esses dados em tempo real).

Não é possível executar todas essas consultas juntas de maneira ideal. Por exemplo, se você estiver classificando por tempo, não será possível fazer uma verificação com base em um local sem fazer uma verificação completa da tabela. Priorize com base nas consultas que você executa com mais frequência.

Neste codelab, você vai se concentrar em otimizar e executar o seguinte conjunto de consultas:

  • Receba os locais de um veículo específico em uma hora.
  • Confira os locais de uma linha de ônibus inteira em uma hora.
  • Encontre os locais de todos os ônibus em Manhattan em uma hora.
  • Receba os locais mais recentes de todos os ônibus em Manhattan em uma hora.
  • Receba os locais de uma linha de ônibus inteira ao longo do mês.
  • Receba os locais de uma linha de ônibus inteira com um determinado destino em uma hora.

Projetar a chave de linha

Neste codelab, você vai trabalhar com um conjunto de dados estático, mas vai criar um esquema para escalonabilidade. Você vai criar um esquema que permite transmitir mais dados de ônibus para a tabela e ainda ter um bom desempenho.

Este é o esquema proposto para a chave de linha:

[Empresa de ônibus/Linha de ônibus/Carimbo de data/hora arredondado para a hora/ID do veículo]. Cada linha tem uma hora de dados, e cada célula contém várias versões dos dados com carimbo de data/hora.

Neste codelab, você vai usar um grupo de colunas para simplificar. Confira um exemplo de como os dados aparecem. Os dados são classificados por chave de linha.

Chave de linha

cf:VehicleLocation.Latitude

cf:VehicleLocation.Longitude

MTA/M86-SBS/1496275200000/NYCT_5824

40.781212 @20:52:54.0040.776163 @20:43:19.0040.778714 @20:33:46.00

-73.961942 @20:52:54.00-73.946949 @20:43:19.00-73.953731 @20:33:46.00

MTA/M86-SBS/1496275200000/NYCT_5840

40.780664 @20:13:51.0040.788416 @20:03:40.00

-73.958357 @20:13:51.00 -73.976748 @20:03:40.00

MTA/M86-SBS/1496275200000/NYCT_5867

40.780281 @20:51:45.0040.779961 @20:43:15.0040.788416 @20:33:44.00

-73.946890 @20:51:45.00-73.959465 @20:43:15.00-73.976748 @20:33:44.00

...

...

...

4. Criar instância, tabela e família

Em seguida, você vai criar uma tabela do Cloud Bigtable.

Primeiro, crie um projeto. Use o Cloud Shell integrado, que pode ser aberto clicando no botão "Ativar o Cloud Shell" no canto superior direito.

a74d156ca7862b28.png

Defina as seguintes variáveis de ambiente para facilitar a cópia e colagem dos comandos do codelab:

INSTANCE_ID="bus-instance"
CLUSTER_ID="bus-cluster"
TABLE_ID="bus-data"
CLUSTER_NUM_NODES=3
CLUSTER_ZONE="us-central1-c"

O Cloud Shell já vem com as ferramentas que você vai usar neste codelab: a ferramenta de linha de comando gcloud, a interface de linha de comando cbt e o Maven.

Ative as APIs do Cloud Bigtable executando este comando.

gcloud services enable bigtable.googleapis.com bigtableadmin.googleapis.com

Crie uma instância executando o seguinte comando:

gcloud bigtable instances create $INSTANCE_ID \
    --cluster=$CLUSTER_ID \
    --cluster-zone=$CLUSTER_ZONE \
    --cluster-num-nodes=$CLUSTER_NUM_NODES \
    --display-name=$INSTANCE_ID

Depois de criar a instância, preencha o arquivo de configuração do cbt e crie uma tabela e um grupo de colunas executando os seguintes comandos:

echo project = $GOOGLE_CLOUD_PROJECT > ~/.cbtrc
echo instance = $INSTANCE_ID >> ~/.cbtrc

cbt createtable $TABLE_ID
cbt createfamily $TABLE_ID cf

5. Importar dados

Importe um conjunto de arquivos de sequência para este codelab de gs://cloud-bigtable-public-datasets/bus-data seguindo estas etapas:

Ative a API Dataflow executando este comando.

gcloud services enable dataflow.googleapis.com

Execute os comandos a seguir para importar a tabela.

NUM_WORKERS=$(expr 3 \* $CLUSTER_NUM_NODES)
gcloud beta dataflow jobs run import-bus-data-$(date +%s) \
--gcs-location gs://dataflow-templates/latest/GCS_SequenceFile_to_Cloud_Bigtable \
--num-workers=$NUM_WORKERS --max-workers=$NUM_WORKERS \
--parameters bigtableProject=$GOOGLE_CLOUD_PROJECT,bigtableInstanceId=$INSTANCE_ID,bigtableTableId=$TABLE_ID,sourcePattern=gs://cloud-bigtable-public-datasets/bus-data/*

Monitorar a importação

É possível monitorar o job na interface do Cloud Dataflow. Também é possível conferir a carga na sua instância do Cloud Bigtable com a interface de monitoramento. A importação completa leva cerca de 5 minutos.

6. Acessar o código

git clone https://github.com/googlecodelabs/cbt-intro-java.git
cd cbt-intro-java

Mude para o Java 11 executando os seguintes comandos:

sudo update-java-alternatives -s java-1.11.0-openjdk-amd64 && export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/

7. Fazer uma pesquisa

A primeira consulta que você vai fazer é uma simples pesquisa de linha. Você vai receber os dados de um ônibus da linha M86-SBS em 1º de junho de 2017, das 0h às 1h. Um veículo com o ID NYCT_5824 está na linha de ônibus nesse horário.

Com essas informações e sabendo o design do esquema (empresa de ônibus/linha de ônibus/carimbo de data/hora arredondado para a hora/ID do veículo), é possível deduzir que a chave de linha é:

MTA/M86-SBS/1496275200000/NYCT_5824

BusQueries.java

private static final byte[] COLUMN_FAMILY_NAME = Bytes.toBytes("cf");
private static final byte[] LAT_COLUMN_NAME = Bytes.toBytes("VehicleLocation.Latitude");
private static final byte[] LONG_COLUMN_NAME = Bytes.toBytes("VehicleLocation.Longitude");

String rowKey = "MTA/M86-SBS/1496275200000/NYCT_5824";
Result getResult =
    table.get(
        new Get(Bytes.toBytes(rowKey))
            .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
            .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME));

O resultado deve conter a localização mais recente do ônibus dentro dessa hora. Mas você quer ver todos os locais, então defina o número máximo de versões na solicitação GET.

BusQueries.java

Result getResult =
    table.get(
        new Get(Bytes.toBytes(rowKey))
            .setMaxVersions(Integer.MAX_VALUE)
            .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
            .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME));

No Cloud Shell, execute o comando a seguir para receber uma lista de latitudes e longitudes do ônibus ao longo da hora:

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=lookupVehicleInGivenHour

É possível copiar e colar as latitudes e longitudes no app MapMaker para visualizar os resultados. Depois de algumas camadas, ele vai pedir para você criar uma conta sem custo financeiro. Você pode criar uma conta ou apenas excluir as camadas atuais. Este codelab inclui uma visualização para cada etapa, caso você queira apenas acompanhar. Este é o resultado da primeira consulta:

f1a1fac6051c6210.png

8. Fazer uma verificação

Agora vamos conferir todos os dados da linha de ônibus para esse horário. O código de verificação é muito parecido com o código de recebimento. Você informa ao scanner uma posição inicial e indica que quer apenas as linhas da linha de ônibus M86-SBS dentro da hora indicada pelo carimbo de data/hora 1496275200000.

BusQueries.java

Scan scan;
scan = new Scan();
scan.setMaxVersions(Integer.MAX_VALUE)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME)
    .withStartRow(Bytes.toBytes("MTA/M86-SBS/1496275200000"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/1496275200000"));
ResultScanner scanner = table.getScanner(scan);

Execute o comando a seguir para receber os resultados.

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanBusLineInGivenHour

c18a4ac6522d08a2.png

O app Map Maker pode mostrar várias listas ao mesmo tempo. Assim, você pode ver qual dos ônibus é o veículo da primeira consulta que você fez.

234c1b51e3b201e.png

Uma modificação interessante nessa consulta é conferir o mês inteiro de dados da linha de ônibus M86-SBS, o que é muito fácil de fazer. Remova o carimbo de data/hora da linha inicial e o filtro de prefixo para receber o resultado.

BusQueries.java

scan.withStartRow(Bytes.toBytes("MTA/M86-SBS/"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/"));

// Optionally, reduce the results to receive one version per column
// since there are so many data points.
scan.setMaxVersions(1);

Execute o comando a seguir para receber os resultados. (Vai aparecer uma longa lista de resultados.)

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanEntireBusLine

Se você copiar os resultados para o Map Maker, poderá ver um mapa de calor do trajeto do ônibus. As manchas laranjas indicam as paradas, e as manchas vermelhas brilhantes são o início e o fim do trajeto.

346f52e61b3d8902.png

9. Apresentar filtros

Em seguida, você vai filtrar os ônibus que seguem para leste e para oeste e criar um mapa de calor separado para cada um.

BusQueries.java

Scan scan;
ResultScanner scanner;
scan = new Scan();
SingleColumnValueFilter valueFilter =
    new SingleColumnValueFilter(
        COLUMN_FAMILY_NAME,
        Bytes.toBytes("DestinationName"),
        CompareOp.EQUAL,
        Bytes.toBytes("Select Bus Service Yorkville East End AV"));

scan.setMaxVersions(1)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME);
scan.withStartRow(Bytes.toBytes("MTA/M86-SBS/"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/"));
scan.setFilter(valueFilter);
scanner = table.getScanner(scan);

Execute o comando a seguir para receber os resultados dos ônibus que vão para o leste.

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=filterBusesGoingEast

Para que os ônibus sigam para o oeste, mude a string em valueFilter:

BusQueries.java

SingleColumnValueFilter valueFilter =
    new SingleColumnValueFilter(
        COLUMN_FAMILY_NAME,
        Bytes.toBytes("DestinationName"),
        CompareOp.EQUAL,
        Bytes.toBytes("Select Bus Service Westside West End AV"));

Execute o comando a seguir para receber os resultados dos ônibus que vão para o oeste.

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=filterBusesGoingWest

Ônibus na direção leste

76f6f62096a6847a.png

Ônibus na direção oeste

2b5771ee9046399f.png

Ao comparar os dois mapas de calor, você pode ver as diferenças nos trajetos e no ritmo. Uma interpretação dos dados é que, na rota em direção ao oeste, os ônibus estão sendo parados com mais frequência, principalmente ao entrar no Central Park. E nos ônibus que seguem para o leste, não há muitos pontos de estrangulamento.

10. Fazer uma verificação de vários intervalos

Para a consulta final, você vai abordar o caso em que se importa com muitas linhas de ônibus em uma área:

BusQueries.java

private static final String[] MANHATTAN_BUS_LINES = {"M1","M2","M3",...

Scan scan;
ResultScanner scanner;
List<RowRange> ranges = new ArrayList<>();
for (String busLine : MANHATTAN_BUS_LINES) {
  ranges.add(
      new RowRange(
          Bytes.toBytes("MTA/" + busLine + "/1496275200000"), true,
          Bytes.toBytes("MTA/" + busLine + "/1496275200001"), false));
}
Filter filter = new MultiRowRangeFilter(ranges);
scan = new Scan();
scan.setFilter(filter);
scan.setMaxVersions(Integer.MAX_VALUE)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME);
scan.withStartRow(Bytes.toBytes("MTA/M")).setRowPrefixFilter(Bytes.toBytes("MTA/M"));
scanner = table.getScanner(scan);

Execute o comando a seguir para receber os resultados.

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanManhattanBusesInGivenHour

7349d94f7d41f1d1.png

11. Concluir

Faça uma limpeza para evitar cobranças

Para evitar cobranças na sua conta do Google Cloud Platform pelos recursos usados neste codelab, exclua a instância.

gcloud bigtable instances delete $INSTANCE_ID

O que vimos

  • Design de esquema
  • Configurar uma instância, uma tabela e um grupo familiar
  • Como importar arquivos sequenciais com o Dataflow
  • Consultar com uma pesquisa, uma verificação, uma verificação com um filtro e uma verificação de vários intervalos

Próximas etapas