Introducción a Cloud Bigtable

1. Introducción

En este codelab, se te presentará el uso de Cloud Bigtable con el cliente de HBase de Java.

Obtendrás información para hacer las siguientes acciones

  • Evita errores comunes en el diseño de esquemas
  • Importa datos en un archivo de secuencia
  • Consulta tus datos

Cuando termines, tendrás varios mapas que mostrarán los datos de los autobuses de la ciudad de Nueva York. Por ejemplo, crearás este mapa de calor de los viajes en autobús en Manhattan:

7349d94f7d41f1d1.png

¿Cómo calificarías tu experiencia con el uso de Google Cloud Platform?

Principiante Intermedio Avanzado

¿Cómo usarás este instructivo?

Solo lo leeré Lo leeré y completaré los ejercicios

2. Acerca del conjunto de datos

Verás un conjunto de datos sobre los autobuses de la ciudad de Nueva York. Hay más de 300 rutas de autobús y 5,800 vehículos que las siguen. Nuestro conjunto de datos es un registro que incluye el nombre del destino, el ID del vehículo, la latitud, la longitud, la hora de llegada esperada y la hora de llegada programada. El conjunto de datos se compone de instantáneas tomadas cada 10 minutos aproximadamente durante junio de 2017.

3. Diseño de esquemas

Para obtener el mejor rendimiento de Cloud Bigtable, debes ser cuidadoso cuando diseñes tu esquema. Los datos en Cloud Bigtable se ordenan automáticamente de forma lexicográfica, por lo que, si diseñas bien tu esquema, la consulta de datos relacionados es muy eficiente. Cloud Bigtable permite realizar consultas con búsquedas de puntos por clave de fila o análisis de rangos de filas que devuelven un conjunto contiguo de filas. Sin embargo, si tu esquema no está bien pensado, es posible que debas unir varias búsquedas de filas o, peor aún, realizar análisis de tablas completas, que son operaciones extremadamente lentas.

Planifica las consultas

Nuestros datos contienen una gran variedad de información, pero, para este codelab, usarás la ubicación y el destino del autobús.

Con esa información, podrías realizar las siguientes búsquedas:

  • Obtén la ubicación de un solo autobús durante una hora determinada.
  • Obtén datos de un día para una línea de autobús o un autobús específico.
  • Encuentra todos los autobuses que se encuentran dentro de un rectángulo en un mapa.
  • Obtener las ubicaciones actuales de todos los autobuses (si estuvieras transfiriendo estos datos en tiempo real)

Este conjunto de consultas no se puede realizar de forma óptima en conjunto. Por ejemplo, si ordenas por hora, no puedes realizar un análisis basado en una ubicación sin realizar un análisis de tabla completo. Debes priorizar en función de las consultas que ejecutas con mayor frecuencia.

En este codelab, te enfocarás en optimizar y ejecutar el siguiente conjunto de consultas:

  • Obtén las ubicaciones de un vehículo específico durante una hora.
  • Obtener las ubicaciones de toda una línea de autobús durante una hora
  • Obtener las ubicaciones de todos los autobuses de Manhattan en una hora
  • Obtener las ubicaciones más recientes de todos los autobuses de Manhattan en una hora
  • Obtener las ubicaciones de toda una línea de autobús durante el mes
  • Obtener las ubicaciones de toda una línea de autobús con un destino determinado durante una hora

Diseña la clave de fila

En este codelab, trabajarás con un conjunto de datos estático, pero diseñarás un esquema para la escalabilidad. Diseñarás un esquema que te permita transmitir más datos de autobuses a la tabla y seguir teniendo un buen rendimiento.

A continuación, se muestra el esquema propuesto para la clave de fila:

[Empresa de autobús/Línea de autobús/Marca de tiempo redondeada a la hora/ID del vehículo]. Cada fila tiene una hora de datos, y cada celda contiene varias versiones de los datos con marca de tiempo.

En este codelab, usarás una familia de columnas para simplificar las cosas. A continuación, se muestra un ejemplo de cómo se ven los datos. Los datos se ordenan por clave de fila.

Clave de fila

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. Crea una instancia, una tabla y una familia

A continuación, crearás una tabla de Cloud Bigtable.

Primero, crea un proyecto nuevo. Usa Cloud Shell integrado, que puedes abrir haciendo clic en el botón "Activar Cloud Shell" en la esquina superior derecha.

a74d156ca7862b28.png

Configura las siguientes variables de entorno para facilitar la copia y el pegado de los comandos del codelab:

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

Cloud Shell ya viene instalado con las herramientas que usarás en este codelab, la herramienta de línea de comandos de gcloud, la interfaz de línea de comandos de cbt y Maven.

Ejecuta este comando para habilitar las APIs de Cloud Bigtable.

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

Ejecuta el siguiente comando para crear una instancia:

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

Después de crear la instancia, propaga el archivo de configuración de cbt y, luego, crea una tabla y una familia de columnas ejecutando los siguientes comandos:

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

cbt createtable $TABLE_ID
cbt createfamily $TABLE_ID cf

5. Importar datos

Importa un conjunto de archivos de secuencia para este codelab desde gs://cloud-bigtable-public-datasets/bus-data con los siguientes pasos:

Ejecuta este comando para habilitar la API de Cloud Dataflow.

gcloud services enable dataflow.googleapis.com

Ejecuta los siguientes comandos para importar la tabla.

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/*

Supervisa la importación

Puedes supervisar el trabajo en la IU de Cloud Dataflow. Además, puedes ver la carga en tu instancia de Cloud Bigtable con su IU de supervisión. La importación completa debería tardar 5 minutos.

6. Obtén el código

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

Ejecuta los siguientes comandos para cambiar a Java 11:

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

7. Cómo realizar una búsqueda

La primera consulta que realizarás es una simple búsqueda de filas. Obtendrás los datos de un autobús de la línea M86-SBS el 1 de junio de 2017, de las 12:00 a.m. a la 1:00 a.m. En ese momento, un vehículo con el ID NYCT_5824 se encuentra en la línea de autobús.

Con esa información y conociendo el diseño del esquema (Empresa de autobús/Línea de autobús/Marca de tiempo redondeada a la hora/ID del vehículo), puedes deducir que la clave de fila es la siguiente:

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

El resultado debe contener la ubicación más reciente del autobús dentro de esa hora. Sin embargo, quieres ver todas las ubicaciones, por lo que debes establecer la cantidad máxima de versiones en la solicitud 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));

En Cloud Shell, ejecuta el siguiente comando para obtener una lista de las latitudes y longitudes de ese autobús durante la hora:

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

Puedes copiar y pegar las latitudes y longitudes en la app de MapMaker para visualizar los resultados. Después de algunas capas, te pedirá que crees una cuenta gratuita. Puedes crear una cuenta o simplemente borrar las capas existentes que tengas. Este codelab incluye una visualización para cada paso, en caso de que solo quieras seguir el proceso. Este es el resultado de la primera búsqueda:

f1a1fac6051c6210.png

8. Cómo realizar un análisis

Ahora, veamos todos los datos de la línea de autobús para esa hora. El código de escaneo se parece bastante al código de obtención. Le indicas al escáner una posición inicial y, luego, le indicas que solo quieres filas para la línea de autobús M86-SBS dentro de la hora indicada por la marca de tiempo 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);

Ejecuta el siguiente comando para obtener los resultados.

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

c18a4ac6522d08a2.png

La app de Map Maker puede mostrar varias listas a la vez, por lo que puedes ver cuáles de los autobuses son el vehículo de la primera búsqueda que realizaste.

234c1b51e3b201e.png

Una modificación interesante de esta consulta es ver el mes completo de datos de la línea de autobús M86-SBS, lo que es muy fácil de hacer. Quita la marca de tiempo del filtro de prefijo y de la fila inicial para obtener el 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);

Ejecuta el siguiente comando para obtener los resultados. (Aparecerá una larga lista de resultados).

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

Si copias los resultados en Map Maker, puedes ver un mapa de calor de la ruta de autobús. Las manchas naranjas indican las paradas, y las manchas rojas brillantes son el inicio y el final de la ruta.

346f52e61b3d8902.png

9. Presentamos los filtros

A continuación, filtrarás los autobuses que se dirigen hacia el este y los que se dirigen hacia el oeste, y crearás un mapa de calor independiente para cada uno.

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

Ejecuta el siguiente comando para obtener los resultados de los autobuses que van hacia el este.

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

Para obtener los autobuses que van hacia el oeste, cambia la cadena en valueFilter:

BusQueries.java

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

Ejecuta el siguiente comando para obtener los resultados de los autobuses que van hacia el oeste.

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

Autobuses con dirección al este

76f6f62096a6847a.png

Autobuses con dirección al oeste

2b5771ee9046399f.png

Si comparas los dos mapas de calor, puedes ver las diferencias en las rutas y en el ritmo. Una interpretación de los datos es que, en la ruta hacia el oeste, los autobuses se detienen más, especialmente cuando ingresan a Central Park. Y en los autobuses que se dirigen hacia el este, no se ven muchos puntos de congestión.

10. Realiza un análisis de varios rangos

Para la consulta final, abordarás el caso en el que te interesan muchas líneas de autobús en un á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);

Ejecuta el siguiente comando para obtener los resultados.

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

7349d94f7d41f1d1.png

11. Finaliza la tarea

Realiza una limpieza para evitar cargos

Para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este codelab, debes borrar la instancia.

gcloud bigtable instances delete $INSTANCE_ID

Temas abordados

  • Diseño de esquemas
  • Cómo configurar una instancia, una tabla y una familia
  • Cómo importar archivos de secuencias con Dataflow
  • Consultas con una búsqueda, un análisis, un análisis con un filtro y un análisis de varios rangos

Próximos pasos