Prácticas recomendadas para la API de BigQuery Storage Write

En este documento, se proporcionan prácticas recomendadas para usar la API de BigQuery Storage Write. Antes de leer este documento, consulta la Descripción general de la API de BigQuery Storage Write.

Limita la frecuencia de creación de transmisiones

Antes de crear una transmisión, considera si puedes usar la transmisión predeterminada. Para situaciones de transmisión, la transmisión predeterminada tiene menos limitaciones de cuota y puede escalar mejor que las transmisiones creadas por aplicaciones. Si usas una transmisión creada por la aplicación, asegúrate de usar la capacidad de procesamiento máxima en cada transmisión antes de crear transmisiones adicionales. Por ejemplo, usa escrituras asíncronas.

Para las transmisiones creadas por la aplicación, evita llamar a CreateWriteStream con una frecuencia alta. En general, si superas las 40 o 50 llamadas por segundo, la latencia de las llamadas a la API aumenta de manera considerable (>25 s). Asegúrate de que tu aplicación pueda aceptar un inicio en frío y aumentar la cantidad de transmisiones de forma gradual, y limita la frecuencia de las llamadas a CreateWriteStream. También puedes establecer un plazo mayor para esperar a que se complete la llamada, de modo que no falle con un error DeadlineExceeded. También hay una cuota a largo plazo en la tasa máxima de llamadas a CreateWriteStream. Crear transmisiones es un proceso que consume muchos recursos, por lo que reducir la tasa de creación de transmisiones y usar las transmisiones existentes es la mejor manera de no sobrepasar este límite.

Administración de grupos de conexiones

El método AppendRows crea una conexión a una transmisión. Puedes abrir varias conexiones en la transmisión predeterminada, pero solo una conexión activa en las transmisiones creadas por la aplicación.

Cuando usas la transmisión predeterminada, puedes usar la multiplexación de la API de Storage Write para escribir en varias tablas de destino con conexiones compartidas. La multiplexación agrupa las conexiones para mejorar la capacidad de procesamiento y el uso de los recursos. Si tu flujo de trabajo tiene más de 20 conexiones simultáneas, te recomendamos que uses la multiplexación. La multiplexación está disponible en Java y Go. Para obtener detalles sobre la implementación de Java, consulta Usa la multiplexación. Para obtener detalles sobre la implementación de Go, consulta Uso compartido de la conexión (multiplexación).

Para obtener el mejor rendimiento, utiliza una conexión para la mayor cantidad posible de escrituras de datos. No uses una conexión solo para una escritura única, ni abras y cierres transmisiones para muchas escrituras pequeñas.

Hay una cuota para la cantidad de conexiones simultáneas que se pueden abrir al mismo tiempo por proyecto. Sobre el límite, las llamadas a AppendRows fallan. Sin embargo, la cuota para las conexiones simultáneas se puede aumentar y, por lo general, no debe ser un factor limitante para el escalamiento.

Cada llamada a AppendRows crea un objeto de escritor de datos nuevo. Por lo tanto, cuando se usa una transmisión creada por la aplicación, la cantidad de conexiones corresponde a la cantidad de transmisiones creadas. 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, pero puede exceder los 10 MBps.

También existe una cuota para la capacidad de procesamiento total por proyecto. Esto representa los bytes por segundo en todas las conexiones que fluyen a través del servicio de la API de Storage Write. Si tu proyecto excede esta cuota, puedes solicitar un límite de cuota más alto. Por lo general, esto implica aumentar las cuotas complementarias, como la cuota de conexiones simultáneas, en una proporción equivalente.

Administra desplazamientos de transmisión para lograr una semántica de “exactamente una vez”

La API de Storage Write solo permite escrituras en el extremo actual de la transmisión, que se mueve a medida que se agregan datos. La posición actual en la transmisión se especifica como una compensación desde el inicio de la transmisión.

Cuando escribes en una transmisión creada por la aplicación, puedes especificar la compensación de transmisión para lograr una semántica de escritura de “exactamente una vez”.

Cuando especificas una compensación, la operación de escritura es idempotente, lo que hace que sea seguro reintentar debido a errores de red o a la falta de respuesta del servidor. Maneja los siguientes errores relacionados con las compensaciones:

  • ALREADY_EXISTS (StorageErrorCode.OFFSET_ALREADY_EXISTS): La fila ya se escribió. Puedes ignorar este error con seguridad.
  • OUT_OF_RANGE (StorageErrorCode.OFFSET_OUT_OF_RANGE): Una operación de escritura anterior falló. Vuelve a intentarlo desde la última escritura exitosa.

Ten en cuenta que estos errores también pueden ocurrir si estableces un valor de compensación incorrecto, por lo que debes administrar las compensaciones con cuidado.

Antes de usar compensaciones de transmisión, considera si necesitas una semántica de “exactamente una vez”. Por ejemplo, si tu canalización de datos ascendente solo garantiza escrituras al menos una vez o si puedes detectar fácilmente duplicados después de la transferencia de datos, es posible que no requieras operaciones de escritura de “exactamente una vez”. En ese caso, recomendamos usar la transmisión predeterminada, que no requiere realizar un seguimiento de las compensaciones de filas.

No bloquees en llamadas AppendRows.

El método AppendRows es asíncrono. Puedes enviar una serie de operaciones de escritura sin bloquear una respuesta para cada operación de escritura de forma individual. Los mensajes de respuesta en la conexión bidireccional llegan en el mismo orden en que se pusieron en cola las solicitudes. Para obtener el rendimiento más alto, llama a AppendRows sin bloquear para esperar la respuesta.

Administra actualizaciones de esquemas

Para los casos de transmisión de datos, los esquemas de tabla se suelen administrar fuera de la canalización de transmisión. Es común que el esquema evolucione con el tiempo, por ejemplo, mediante la adición de nuevos campos anulables. Una canalización sólida debe manejar actualizaciones de esquema fuera de banda.

La API de Storage Write admite esquemas de tablas de la siguiente manera:

  • La primera solicitud de escritura incluye el esquema.
  • Debes enviar cada fila de datos como un búfer de protocolo binario. BigQuery asigna los datos al esquema.
  • Puedes omitir los campos con valores nulos, pero no puedes incluir los que no estén presentes en el esquema actual. Si envías filas con campos adicionales, la API de Storage Write muestra un StorageError con StorageErrorCode.SCHEMA_MISMATCH_EXTRA_FIELD.

Si deseas enviar campos nuevos en la carga útil, primero debes actualizar el esquema de la tabla en BigQuery. La API de escritura de Storage detecta los cambios de esquema después de un período breve, en cuestión de minutos. Cuando la API de escritura de almacenamiento detecta el cambio de esquema, el mensaje de respuesta AppendRowsResponse contiene un objeto TableSchema que describe el esquema nuevo.

Para enviar datos con el esquema actualizado, debes cerrar las conexiones existentes y abrir conexiones nuevas con el esquema nuevo.

Cliente de Java. La biblioteca cliente de Java proporciona algunas funciones adicionales para las actualizaciones del esquema, a través de la clase JsonStreamWriter. Después de una actualización del esquema, JsonStreamWriter se vuelve a conectar de manera automática con el esquema actualizado. No necesitas cerrar y volver a abrir la conexión de forma explícita. Para verificar los cambios en el esquema de manera programática, llama a AppendRowsResponse.hasUpdatedSchema después de que se complete el método append.

También puedes configurar JsonStreamWriter para que ignore los campos desconocidos en los datos de entrada. Para establecer este comportamiento, llama a setIgnoreUnknownFields. Este comportamiento es similar a la opción ignoreUnknownValues cuando se usa la API heredada tabledata.insertAll. Sin embargo, puede provocar una pérdida de datos involuntaria, ya que los campos desconocidos se descartan en silencio.