Introducción a la API de BigQuery Storage Write

La API de BigQuery Storage Write es una API de transferencia de datos unificada para BigQuery. Combina la transferencia de transmisión y la carga por lotes en una sola API de alto rendimiento. Puedes usar la API de Storage Write para transmitir registros a BigQuery en tiempo real o procesar por lotes una gran cantidad arbitraria de registros y confirmarlos en una sola operación atómica.

Ventajas de usar la API de Storage Write

Semántica de entrega exacta una vez. La API de Storage Write admite una semántica de tipo “solo una vez” a través del uso de compensaciones de transmisión. A diferencia del método tabledata.insertAll, la API de Storage Write nunca escribe dos mensajes que tienen la misma compensación dentro de una transmisión, si el cliente proporciona compensaciones de transmisión cuando agrega registros.

Transacciones a nivel de transmisión. Puedes escribir datos en una transmisión y confirmarlos como una sola transacción. Si la operación de confirmación falla, puedes reintentar la operación de forma segura.

Transacciones en las transmisiones. Varios trabajadores pueden crear sus propias transmisiones para procesar datos de forma independiente. Cuando todos los trabajadores finalizan, puedes confirmar todas las transmisiones como una transacción.

Protocolo eficiente. La API de Storage Write es más eficiente que el método insertAll anterior porque usa transmisión de gRPC en lugar de REST a través de HTTP. La API de Storage Write también admite formatos de objetos binarios en forma de búferes de protocolo, que son un formato de conexión más eficiente que JSON. Las solicitudes de escritura son asíncronas con orden garantizado.

Detección de actualización del esquema. Si el esquema de la tabla subyacente cambia mientras el cliente está transmitiendo, la API de Storage Write notifica al cliente. El cliente puede decidir si quiere volver a conectarse a través del esquema actualizado o continuar escribiendo en la conexión existente.

Costo menor. La API de Storage Write tiene un costo mucho menor que la API de transmisión insertAll anterior. Además, puedes transferir hasta 2 TiB por mes de forma gratuita.

Permisos necesarios

Para usar la API de Storage Write, debes tener los permisos bigquery.tables.updateData.

Las siguientes funciones predefinidas de Identity and Access Management (IAM) incluyen los permisos bigquery.tables.updateData:

  • bigquery.dataEditor
  • bigquery.dataOwner
  • bigquery.admin

Para obtener más información sobre las funciones de IAM y los permisos en BigQuery, consulta Funciones y permisos predefinidos.

Permisos de autenticación

El uso de la API de Storage Write requiere uno de los siguientes permisos de OAuth:

  • https://www.googleapis.com/auth/bigquery
  • https://www.googleapis.com/auth/cloud-platform
  • https://www.googleapis.com/auth/bigquery.insertdata

Para obtener más información, consulta Descripción general de la autenticación.

Descripción general de la API de Storage Write

La abstracción principal en la API de Storage Write es una transmisión. Una transmisión escribe datos en una tabla de BigQuery. Más de una transmisión puede escribir de forma simultánea en la misma tabla.

Transmisión predeterminada

La API de Storage Write proporciona una transmisión predeterminada, diseñada para situaciones de transmisión en las que tienes datos que llegan de forma continua. Tiene las siguientes características:

  • Los datos escritos en la transmisión predeterminada están disponibles de inmediato para realizar consultas.
  • La transmisión predeterminada admite semántica de al menos una vez.
  • No necesitas crear de forma explícita la transmisión predeterminada.

Si migras desde la API tabledata.insertall heredada, considera usar la transmisión predeterminada. Tiene una semántica de escritura similar, con mayor resiliencia de datos y menos restricciones de escalamiento.

Flujo de la API:

  1. AppendRows (bucle)

Para obtener más información y el código de ejemplo, consulta Usa la transmisión predeterminada para la semántica de al menos una vez.

Transmisiones creadas por la aplicación

Puedes crear una transmisión de forma explícita si necesitas uno de los siguientes comportamientos:

  • Escribe semánticas de escritura de tipo “solo una vez” a través del uso de desplazamientos de transmisión.
  • Asistencia con propiedades ACID adicionales.

En general, las transmisiones creadas por la aplicación brindan un mayor control sobre la funcionalidad a costa de una complejidad adicional.

Cuando creas una transmisión, especificas un tipo. El tipo controla cuándo los datos escritos en la transmisión se vuelven visibles en BigQuery para su lectura.

Tipo pendiente

En tipo pendiente, los registros se almacenan en búfer en un estado pendiente hasta que confirmes la transmisión. Cuando confirmas una transmisión, todos los datos pendientes pasan a estar disponibles para su lectura. La confirmación es una operación atómica. Usa este tipo para las cargas de trabajo por lotes como alternativa a los trabajos de carga de BigQuery. Para obtener más información, consulta Carga datos por lotes con la API de Storage Write.

Flujo de la API:

  1. CreateWriteStream
  2. AppendRows (bucle)
  3. FinalizeWriteStream
  4. BatchCommitWriteStreams

Tipo de compromiso

En el tipo de compromiso, los registros están disponibles para su lectura inmediata a medida que los escribes en la transmisión. Usa este tipo para las cargas de trabajo de transmisión que necesitan una latencia mínima de lectura. La transmisión predeterminada usa al menos una forma del tipo confirmado. Para obtener más información, consulta Usa el tipo de confirmación para la semántica de tipo "solo una vez".

Flujo de la API:

  1. CreateWriteStream
  2. AppendRows (bucle)
  3. FinalizeWriteStream (opcional)

Tipo de almacenamiento en búfer

El tipo de almacenamiento en búfer es un tipo avanzado que, por lo general, no se debe usar con el conector de E/S de BigQuery de Apache Beam. Si tienes lotes pequeños que deseas garantizar que aparezcan juntos, usa el tipo de confirmación y envía cada lote en una solicitud. En este tipo, se proporcionan confirmaciones a nivel de fila, y los registros se almacenan en búfer hasta que las filas se confirmen a través de la limpieza de la transmisión.

Flujo de la API:

  1. CreateWriteStream
  2. AppendRowsFlushRows (bucle)
  3. FinalizeWriteStream (opcional)

Selecciona un tipo

Usa el siguiente diagrama de flujo para decidir qué tipo es mejor para tu carga de trabajo:

imagen

Detalles de la API

Ten en cuenta lo siguiente cuando uses la API de Storage Write:

AppendRows

El método AppendRows agrega uno o más registros a la transmisión. La primera llamada a AppendRows debe contener un nombre de transmisión junto con el esquema de datos, especificado como DescriptorProto. Como práctica recomendada, envía un lote de filas en cada llamada a AppendRows. No envíes una fila a la vez.

Manejo de búferes de Proto

Los búferes de protocolo proporcionan un mecanismo extensible, con un formato de lenguaje y plataforma neutros, para serializar datos estructurados de manera compatible con versiones anteriores y futuras. Son excelentes porque proporcionan almacenamiento de datos compacto a través de un análisis rápido y eficiente. Para obtener más información sobre los búferes de protocolo, consulta Descripción general del búfer de protocolo.

Si vas a consumir la API de forma directa con un mensaje de búfer de protocolo predefinido, el mensaje de búfer de protocolo no puede usar un especificador package, y todos los tipos anidados o de enumeración deben definirse en la parte superior nivel superior. No se permiten referencias a mensajes externos. Para ver un ejemplo, consulta sample_data.proto.

Los clientes Java y Go admiten búferes de protocolo arbitrarios, ya que la biblioteca cliente normaliza el esquema de búfer de protocolo.

FinalizeWriteStream

El método FinalizeWriteStream finaliza la transmisión para que no se puedan agregar datos nuevos. Este método es obligatorio en el tipo Pending y opcional en los tipos Committed y Buffered. La transmisión predeterminada no es compatible con este método.

Manejo de errores

Si se produce un error, el google.rpc.Status que se muestra puede incluir un StorageError en los detalles del error. Revisa StorageErrorCode para encontrar el tipo de error específico. Para obtener más información sobre el modelo de error de la API de Google, consulta Errores.

Conexiones

La API de Storage Write es una API de gRPC que usa conexiones bidireccionales. El método AppendRows crea una conexión a una transmisión. Puedes abrir varias conexiones en la transmisión predeterminada. Estos agregados son asíncronos, lo que te permite enviar una serie de operaciones de escritura de forma simultánea. Los mensajes de respuesta en cada conexión bidireccional llegan en el mismo orden en que se enviaron las solicitudes.

Las transmisiones creadas por la aplicación solo pueden tener una única conexión activa. Como práctica recomendada, limita la cantidad de conexiones activas y usa una conexión para la mayor cantidad posible de escrituras de datos. Cuando usas la transmisión predeterminada en Java o Go, puedes usar la multiplexación de la API de Storage Write para escribir en varias tablas de destino con conexiones compartidas.

En general, una sola conexión admite al menos 1 Mbps de capacidad de procesamiento. El límite superior depende de varios factores, como el ancho de banda de la red, el esquema de los datos y la carga del servidor. Cuando una conexión alcanza el límite de capacidad de procesamiento, las solicitudes entrantes se pueden rechazar o poner en cola hasta que disminuya la cantidad de solicitudes en tránsito. Si necesitas más capacidad de procesamiento, crea más conexiones.

BigQuery cierra la conexión de gRPC si la conexión permanece inactiva por mucho tiempo. Si esto sucede, el código de respuesta es HTTP 409. La conexión de gRPC también se puede cerrar en caso de reinicio del servidor o por otros motivos. Si se produce un error de conexión, crea una conexión nueva. Las bibliotecas cliente de Java y Go se vuelven a conectar de forma automática si se cierra la conexión.

Compatibilidad de la biblioteca cliente

Las bibliotecas cliente para la API de Storage Write existen en varios lenguajes de programación y exponen las construcciones subyacentes de la API basada en gRPC. Esta API aprovecha funciones avanzadas como la transmisión bidireccional, lo que puede requerir trabajo de desarrollo adicional para la compatibilidad. En este sentido, hay una serie de abstracciones de nivel superior disponibles para esta API, lo que simplifica esas interacciones y reduce los problemas de los desarrolladores. Recomendamos aprovechar estas otras abstracciones de biblioteca cuando sea posible.

En esta sección, se proporcionan detalles adicionales sobre los lenguajes y las bibliotecas en los que se proporcionaron a los desarrolladores capacidades adicionales más allá de la API generada.

Para ver muestras de código relacionadas con la API de Storage Write, consulta aquí.

Cliente de Java

La biblioteca cliente de Java proporciona dos objetos de escritor:

  • StreamWriter: Acepta datos en formato de búfer de protocolo.

  • JsonStreamWriter: acepta datos en formato JSON y los convierte en búferes de protocolo antes de enviarlos por el cable. JsonStreamWriter también admite actualizaciones automáticas de esquemas. Si el esquema de la tabla cambia, el escritor se vuelve a conectar de manera automática con el esquema nuevo, lo que permite que el cliente envíe datos con el esquema nuevo.

El modelo de programación es similar para ambos escritores. La diferencia principal es la forma en que le das formato a la carga útil.

El objeto de escritor administra una conexión de la API de Storage Write. El objeto de escritor limpia las solicitudes de forma automática, agrega los encabezados de enrutamiento regionales a las solicitudes y se vuelve a conectar después de los errores de conexión. Si usas la API de gRPC de forma directa, debes manejar estos detalles.

Cliente de Go

El cliente de Go usa una arquitectura cliente-servidor para codificar los mensajes en formato del búfer de protocolo usando proto2. Consulta la documentación de Go para obtener detalles sobre cómo usar el cliente de Go con código de ejemplo.

Cliente de Python

El cliente de Python es un cliente de nivel inferior que une la API de gRPC. Para usar este cliente, debes enviar los datos como búferes de protocolo, como se describe en Flujo de API.

Para obtener más información sobre el uso de búferes de protocolo con Python, lee el instructivo sobre los conceptos básicos del búfer de protocolo en Python.

Cliente de NodeJS

La biblioteca cliente de NodeJS acepta la entrada JSON y proporciona asistencia automática para la reconexión. Consulta la documentación para obtener detalles sobre cómo usar el cliente.

Conversiones de tipos de datos

En la siguiente tabla, se muestran los tipos de búfer de protocolo admitidos para cada tipo de datos de BigQuery:

Tipo de datos de BigQuery Tipos de búfer de protocolo admitidos
BOOL bool, int32, int64, uint32, uint64, google.protobuf.BoolValue
BYTES bytes, string, google.protobuf.BytesValue
DATE int32 (recomendado), int64

El valor es la cantidad de días transcurridos desde el tiempo Unix (1970-01-01). El rango válido es `-719162` (0001-01-01) a `2932896` (9999-12-31).

DATETIME, TIME string

El valor debe ser DATETIME o un literal de TIME .

int64

Usa la clase CivilTimeEncoder para realizar la conversión.

FLOAT double, float, google.protobuf.DoubleValue, google.protobuf.FloatValue
GEOGRAPHY string

El valor es una geometría en formato WKT o GeoJson.

INTEGER int32, int64, uint32, enum, google.protobuf.Int32Value, google.protobuf.Int64Value, google.protobuf.UInt32Value
JSON string
NUMERIC, BIGNUMERIC int32, int64, uint32, uint64, double, float, string
bytes, google.protobuf.BytesValue

Usa la clase BigDecimalByteStringEncoder para realizar la conversión.

STRING string, enum, google.protobuf.StringValue
TIME string

El valor debe ser un literal de TIME.

TIMESTAMP int64 (recomendado), int32, uint32, google.protobuf.Timestamp

El valor se proporciona en microsegundos desde el tiempo Unix (1970-01-01).

INTERVAL string, google.protobuf.Duration

El valor de cadena debe ser un literal de INTERVAL.

RANGE<T> (vista previa) message

Un tipo de mensaje anidado en el proto con dos campos, start y end, en el que ambos campos deben ser del mismo tipo de búfer de protocolo compatible que corresponde a un tipo de datos de BigQuery T. T debe ser DATE, DATETIME o TIMESTAMP. Si un campo (start o end) no está configurado en el mensaje proto, representa un límite no delimitado. En el siguiente ejemplo, f_range_date representa una columna RANGE en una tabla. Debido a que el campo end no está configurado en el mensaje proto, el límite final de este rango es ilimitado.


{
  f_range_date: {
    start: 1
  }
}
REPEATED FIELD array

Un tipo de array en el proto corresponde a un campo repetido en BigQuery.

RECORD message

Un tipo de mensaje anidado en el proto corresponde a un campo de registro en BigQuery.

Controla la falta de disponibilidad

Si vuelves a intentarlo con una retirada exponencial puedes mitigar errores aleatorios y períodos breves de falta de disponibilidad del servicio, pero debes estar más pendiente para evitar quitar filas durante la falta de disponibilidad extendida. En particular, si un cliente no puede insertar una fila de forma persistente, ¿qué debe hacer?

La respuesta depende de tus requisitos. Por ejemplo, si BigQuery se usa para estadísticas operativas en las que algunas filas faltantes son aceptables, el cliente puede renunciar después de unos pocos reintentos y descartar los datos. Si, en cambio, cada fila es crucial para la empresa, como con los datos financieros, debes tener una estrategia para conservar los datos hasta que se puedan insertar más adelante.

Una forma común de tratar errores persistentes es publicar las filas en un tema de Pub/Sub para una evaluación posterior y una posible inserción. Otro método común es conservar de forma temporal los datos en el cliente. Ambos métodos pueden mantener a los clientes bloqueados y, al mismo tiempo, garantizar que todas las filas se puedan insertar una vez que se restablezca la disponibilidad.

Partición de columnas por unidad de tiempo

Puedes transmitir datos a una tabla particionada en una columna DATE, DATETIME o TIMESTAMP que esté entre 5 años en el pasado y 1 año en el futuro. Los datos fuera de este rango se rechazan.

Cuando los datos se transmiten, al principio se colocan en la partición __UNPARTITIONED__. Una vez que se recopilan suficientes datos no particionados, BigQuery vuelve a particionarlos y los coloca en la partición adecuada. Sin embargo, no existe un Acuerdo de Nivel de Servicio(ANS) que defina cuánto tiempo les tomará a esos datos salir de la partición __UNPARTITIONED__.

La API de Storage Write no admite el uso de decoradores de partición.

Métricas de la API de Storage Write

Para que las métricas supervisen la transferencia de datos con la API de Storage Write, como la latencia del nivel de solicitud del servidor, las conexiones simultáneas, los bytes subidos y las filas subidas, consulta Métricas de Google Cloud.

Usa el lenguaje de manipulación de datos (DML) con datos transmitidos de forma reciente

Puedes usar el lenguaje de manipulación de datos (DML), como las declaraciones UPDATE, DELETE o MERGE, para modificar las filas que la API de Storage Write de BigQuery escribió de forma reciente en una tabla de BigQuery. Las operaciones de escritura recientes son aquellas que ocurrieron en los últimos 30 minutos.

Si deseas obtener más información sobre el uso de DML para modificar tus datos transmitidos, consulta Usa el lenguaje de manipulación de datos.

Limitaciones

Cuotas de la API de Storage Write

Para obtener información sobre las cuotas y los límites de la API de Storage Write, consulta Cuotas y límites de la API de BigQuery Storage Write.

Puedes supervisar las conexiones simultáneas y el uso de cuotas de capacidad de procesamiento en la página Cuotas de la consola de Google Cloud.

Calcula la capacidad de procesamiento

Supongamos que tu objetivo es recopilar registros de 100 millones de extremos y crear un registro de 1,500 por minuto. Luego, puedes estimar la capacidad de procesamiento como 100 million * 1,500 / 60 seconds = 2.5 GB per second. Debes asegurarte de antemano de tener una cuota adecuada para entregar esta capacidad de procesamiento.

Precios de la API de Storage Write

Para obtener información sobre los precios, consulta Precios de transferencia de datos.

Ejemplo de caso de uso

Supongamos que hay una canalización que procesa datos de eventos de registros de extremos. Los eventos se generan de forma continua y deben estar disponibles para realizar consultas en BigQuery lo antes posible. Dado que la actualidad de los datos es fundamental para este caso de uso, la API de Storage Write es la mejor opción para transferir datos a BigQuery. Una arquitectura recomendada para mantener estos extremos eficientes es enviar eventos a Pub/Sub, desde donde los consume una canalización de Dataflow de transmisión que se transmite de forma directa a BigQuery.

Una preocupación principal de confiabilidad de esta arquitectura es cómo lidiar con el fallo de insertar un registro en BigQuery. Si cada registro es importante y no se puede perder, los datos se deben almacenar en búfer antes de intentar insertarlos. En la arquitectura recomendada anterior, Pub/Sub puede desempeñar la función de un búfer con sus capacidades de retención de mensajes. La canalización de Dataflow debe estar configurada para reintentar las inserciones de transmisión de BigQuery con una retirada exponencial truncada. Después de que se agota la capacidad de Pub/Sub como un búfer, por ejemplo, en el caso de una falta de disponibilidad prolongada de BigQuery o de una falla de la red, los datos se deben conservar en el cliente y el cliente necesita un mecanismo para reanudar la inserción de registros persistentes una vez que se restablece la disponibilidad. Para obtener más información sobre cómo manejar esta situación, consulta la entrada de blog de la Guía de confiabilidad de Google Pub/Sub.

Otro caso de falla que se debe controlar es el de un registro tóxico. Un registro tóxico es un registro que BigQuery rechazó porque no se puede insertar con un error que no se puede reintentar o un registro que no se insertó de forma correcta después de la cantidad máxima de reintentos. Ambos tipos de registros se deben almacenar en una “cola de mensajes no entregados” a través de la canalización de Dataflow para una investigación más detallada.

Si se requiere una semántica de una y solo una vez, crea una transmisión de escritura en tipo confirmado, con compensaciones de registros proporcionadas por el cliente. Esto evita los duplicados, ya que la operación de escritura solo se realiza si el valor de desplazamiento coincide con el desplazamiento de anexo siguiente. Si no se proporciona un desplazamiento, se agregan los registros al extremo actual de la transmisión y, si se reintenta una operación fallida, es posible que el registro aparezca más de una vez en la transmisión.

Si no se requieren garantías de una sola vez, escribir en la transmisión predeterminada permite una capacidad de procesamiento mayor y no se considera en el límite de cuota sobre la creación de transmisiones de escritura.

Estima la capacidad de procesamiento de tu red y asegúrate con anticipación de que tengas una cuota adecuada para entregar la capacidad de procesamiento.

Si tu carga de trabajo genera o procesa datos a una tasa muy desigual, intenta disminuir los picos de carga en el cliente y transmitir a BigQuery con una capacidad de procesamiento constante. Esto puede simplificar la planificación de capacidad. Si eso no es posible, asegúrate de estar preparado para controlar errores 429 (recursos agotados) en caso de que tu capacidad de procesamiento supere la cuota durante aumentos repentinos cortos.

¿Qué sigue?