Ir al contenido principal
Lakebase

Cargue terabytes de datos en minutos en Lakebase Postgres

Cargas masivas más rápidas y cargas de trabajo OLTP más seguras con la arquitectura LTAP

por Yecheng Yang, Nolan Biscaro, Szu-Po Wang y Pranav Aurora

  • La arquitectura LTAP (Lake Transactional/Analytical Processing) descarga las operaciones masivas pesadas del cómputo principal de Lakebase Postgres a motores distribuidos como Spark.
  • Al permitir que Spark construya páginas e índices válidos de Postgres en paralelo y escriba directamente en el almacenamiento, Lakebase Postgres logra cargas de datos hasta 147 veces más rápidas sin consumir recursos de la aplicación en producción.
  • El nodo primario de Lakebase Postgres publica el manifiesto final mediante un registro WAL compacto, lo que garantiza que las consultas OLTP en activo no se vean afectadas y se mantenga un alto rendimiento.

Las bases de datos operacionales como Postgres están diseñadas para ejecutar de manera confiable consultas con un alto nivel de concurrencia y baja latencia en subconjuntos de datos. Lo que suele afectar esta confiabilidad son las operaciones masivas, como cargar terabytes de datos o ejecutar consultas analíticas que escanean una tabla completa. Estas operaciones compiten por los mismos recursos que las cargas de trabajo de sus aplicaciones, lo que arriesga la degradación del rendimiento o el tiempo de inactividad.

Nuestro objetivo es hacer que Lakebase Postgres sea el lugar más seguro y confiable para sus cargas de trabajo operacionales. Lo logramos al descargar las operaciones por lotes pesadas del cómputo primario a motores distribuidos como Spark, que están diseñados específicamente para estas tareas. Esto es posible gracias a la arquitectura LTAP (Lake Transactional/Analytical Processing), que permite que tanto los motores transaccionales como los analíticos trabajen exactamente sobre los mismos datos en el lake.

Sin este aislamiento, los equipos a menudo se ven obligados a ser extremadamente cuidadosos con las operaciones masivas. Sacrifican la frescura de los datos, ejecutan cargas con poca frecuencia fuera del horario habitual y gestionan de forma manual rellenos históricos y puntos de control complejos. Hoy, al aprovechar la arquitectura LTAP, descargan las tuberías de ingesta por completo en Spark, lo que garantiza que las aplicaciones obtengan datos frescos sin comprometer el rendimiento del sistema en vivo.

Veamos los números

Durante nuestra versión beta, un cliente utilizaba Synced Tables para cargar alrededor de 1000 millones de filas en Lakebase todos los días. Al aprovechar la arquitectura LTAP, aceleramos esas cargas drásticamente mientras mantuvimos sus cargas de trabajo operacionales completamente inalteradas.

image1.png

Anteriormente, su carga masiva tardaba más de 8 horas y saturaba por completo su CPU y memoria. Incluso con el escalado automático de Lakebase, se veían obligados a sobredimensionar en exceso sus recursos OLTP solo para sobrevivir a la sincronización. Esto sucede porque las arquitecturas tradicionales de Postgres convierten a la instancia primaria en el único guardián del estado duradero. Las cargas masivas se ven forzadas a pasar por este cuello de botella único, lo que significa que cada fila importada genera páginas de heap, actualiza índices y escribe registros de WAL en la misma instancia que atiende el tráfico activo de sus aplicaciones.

La arquitectura LTAP alivió por completo la presión sobre su instancia primaria, protegiendo el tráfico activo de sus aplicaciones. Obtiene toda la potencia de un motor distribuido, escalando el rendimiento de carga casi de forma lineal a medida que sus datos crecen. Nuestras pruebas de rendimiento internas muestran que cargar 1 TB ahora toma menos de 5 minutos.

image6.png

Nota: Esta prueba de rendimiento mide el tiempo de carga de datos (creación de páginas de heap). También estamos trabajando activamente en paralelizar la creación de índices para estos datos cargados.

image7.png

En el resto de esta publicación, profundizamos en los desafíos de la carga masiva de datos en una base de datos OLTP como Postgres y exploramos cómo aprovechamos la arquitectura LTAP para resolverlos.

El problema con las cargas masivas a escala en Postgres

El comando nativo de Postgres COPY es eficiente para ingestas estándar y más pequeñas.

Pero a medida que los clientes unifican sus entornos operacionales y analíticos, la escala cambia. Una carga de trabajo cada vez más común implica servir tablas de Lakehouse masivas de nivel gold a aplicaciones con patrones de consulta operacionales. Enviar datos a ese volumen extremo a una base de datos de producción expone dos límites fundamentales:

  1. La carga es intrínsecamente lenta porque cada fila importada debe pasar a través de un único escritor primario.
  2. Ese mismo cómputo que se utiliza para la carga también atiende las consultas de sus aplicaciones. Las cargas masivas exigen un uso intensivo de CPU, I/O, conexiones y ancho de banda de WAL, compitiendo con su tráfico OLTP.

Esto sigue siendo cierto incluso si inicia la carga como un trabajo distribuido de Spark. Spark puede leer particiones de origen en paralelo, pero cada fila aún debe pasar por un escritor de Postgres:

  1. Los workers envían filas a Postgres a través de COPY
  2. La instancia primaria convierte esas filas en páginas de heap e índice
  3. La instancia primaria registra los cambios en el registro de escritura anticipada (WAL)
  4. El WAL se debe vaciar al almacenamiento duradero antes de confirmar la carga

Las cargas masivas tradicionales se ven limitadas por el cuello de botella de un único escritor

El lado de origen puede escalar horizontalmente, pero el lado de destino no. Agregar ejecutores de Spark acelera el escaneo, pero no elimina el cuello de botella del escritor único. Además, esta misma instancia primaria de Postgres atiende las transacciones de sus aplicaciones en línea. Una importación grande compite con ellas por CPU, memoria, I/O, conexiones y ancho de banda de WAL. La latencia aumenta, los equipos programan cargas en momentos de poca actividad y, a menudo, aprovisionan la instancia primaria para la importación más grande en lugar de para el tráfico diario.

Lo que cambia LTAP

La arquitectura LTAP abre una vía para solucionar esto, porque la instancia primaria ya no es la única forma de crear un estado duradero de Postgres. El cómputo transaccional no tiene estado en lakebase: el estado duradero reside en una capa de almacenamiento distribuido, no en el disco local de la instancia primaria. En cierto modo, Postgres es un cliente del almacenamiento: atiende consultas y transacciones, pero no tiene que ser el proceso que materializa cada nueva página.

image2.png

Para la carga masiva, esto significa que Spark puede construir el estado de Postgres y escribirlo en el almacenamiento, mientras que la instancia primaria solo publica el resultado.

Las consecuencias operacionales son:

  • Las cargas masivas no compiten con el OLTP en la instancia primaria. Esto se ejecuta fuera del extremo de cómputo en vivo. Las cargas de trabajo de las aplicaciones mantienen la CPU, I/O, conexiones y ancho de banda de WAL que necesitan.
  • Cargas grandes en el mismo destino se pueden ejecutar de forma concurrente. Cada importación finaliza escribiendo un único registro de WAL en la instancia primaria, por lo que las cargas ya no se ponen en cola detrás de las transmisiones de COPY de otras.
  • La instancia primaria no necesita escalar con el tamaño de la carga. Una instancia primaria de 1 CU puede seguir atendiendo el tráfico mientras Spark carga miles de millones de filas en un cómputo independiente, y el cómputo de Spark finaliza tan pronto como termina la carga.

Creación de páginas válidas de Postgres de forma segura y en paralelo

Existen diferentes optimizaciones para crear archivos de Postgres y construir el índice de clave primaria.

Construcción del heap en paralelo

Las páginas que genera Spark deben ser válidas para la base de datos de destino, tal como si las hubiera construido su instancia primaria.

Cada ejecutor de Spark inicia una instancia aislada de Postgres en modo binary-upgrade, el mismo mecanismo que utiliza pg_upgrade para conservar los OID del catálogo en las actualizaciones de versiones principales. Lo utilizamos para trasplantar los OID del catálogo de destino a cada entorno aislado, lo que garantiza que los OID integrados en las páginas generadas coincidan con los del destino. El driver también asigna rangos de OID que no se superponen a los entornos aislados para que los objetos creados simultáneamente no colisionen.

Dentro de cada entorno aislado, un archivo binario COPY con FREEZE crea la fracción del heap correspondiente a ese ejecutor. El congelamiento marca las tuplas importadas como ya confirmadas, por lo que Postgres puede tratarlas como visibles sin consultar el historial de transacciones del entorno aislado.

A continuación, cada worker calcula las sumas de comprobación para cada página generada. Los servidores de páginas de Lakebase validan esas sumas de comprobación cuando ingieren los archivos, detectando la corrupción antes de que las páginas importadas se vuelvan autoritativas.

Juntos, el modo binary-upgrade, las tuplas congeladas, los OID coordinados y la validación de sumas de comprobación garantizan que Spark produzca páginas que el destino pueda leer como páginas ordinarias de Postgres. Implementamos esto a través de extensiones de Postgres y un método de acceso a tablas, sin modificar el núcleo de Postgres.

Los entornos aislados desechables también permiten optimizaciones del rendimiento, como las tablas auxiliares UNLOGGED. Estas son optimizaciones, no mecanismos de seguridad: evitan disputas innecesarias de bloqueo y de WAL porque ningún tráfico de aplicación comparte el entorno aislado.

¿Sigue siendo "simplemente Postgres"?
Sí. Los archivos que escribe Spark son páginas de Postgres, no un formato de importación que la instancia primaria traduce más adelante. Agregamos extensiones y un método de acceso a tablas en torno a esa ruta, lo cual es uno de los superpoderes de Postgres que nos permite añadir capacidades sin modificar el código fuente.

Construcción del índice sin escanear el heap

Un árbol B es una estructura ordenada en todo el espacio de claves, y sus entradas de hoja contienen pares de clave e ID de tupla que apuntan de nuevo al heap.

Normalmente, Postgres obtiene esos pares escaneando el heap. Sin embargo, aquí el heap se distribuye a través de fragmentos subidos, y descargar todo a un worker de creación de índices anularía gran parte del beneficio de crearlo en paralelo.

El creador de índices realmente no necesita el contenido del heap. Necesita el flujo de pares (key, ctid) que produciría un escaneo del heap. Por lo tanto, cada worker del heap del paso anterior exporta sus columnas de clave e ID de tupla a un archivo de almacenamiento de objetos independiente mientras crea su fragmento.

Introducimos esos registros en una tabla auxiliar que solo contiene las columnas de clave exportadas y el ID de tupla en lugar de las filas originales. Durante CREATE INDEX, un método personalizado de acceso a tablas escanea la tabla auxiliar de forma idéntica a heapam, pero escribe el ctid en cada entrada de índice en lugar del ID de tupla físico de la propia fila auxiliar. A medida que se cargan los registros, cada ctid local del fragmento se desplaza según el tamaño acumulado de los fragmentos de heap anteriores, haciendo que apunte a la ubicación final de la tupla en el heap concatenado. En resumen, el creador estándar de árboles B de Postgres puede escanear esta tabla auxiliar sin modificaciones y producir un índice sobre un heap que nunca descargó. Esto reemplaza el movimiento de todo el heap por el movimiento de la representación de clave e ID de tupla, mucho más pequeña.

Entrega del resultado a Postgres

Una vez que los fragmentos de heap e índice están en el almacenamiento de objetos, el driver de Spark escribe un manifiesto e invoca una función SQL en el destino. El primario registra la importación como un registro WAL compacto. Un COPY convencional enviaría todo el volumen de datos a través del quórum de safekeeper. Nosotros solo enviamos la descripción de la importación.

Los pageservers reclaman los archivos subidos como páginas autoritativas y validan sus sumas de comprobación. También precalientan las páginas en la SSD local para que las lecturas iniciales no impliquen búsquedas frías en el almacenamiento de objetos. Finalmente, el primario intercambia atómicamente los datos preparados con la tabla sincronizada visible para el usuario.

Hasta que esa transacción se confirme, la tabla importada permanece aislada. Después, el primario ve páginas ordinarias de heap y árbol B producidas a través de formatos e interfaces estándar de Postgres.

Comience hoy mismo.

Usamos la arquitectura LTAP para impulsar Synced Tables, lo que permite sincronizaciones mucho más rápidas al servir datasets gold desde su Lakehouse. Si está ejecutando trabajos manuales de ReverseETL desde el Lakehouse, con Lakebase, debería considerar usar Lakebase.

Nos entusiasma generalizar este protocolo para gestionar operaciones y mantenimiento a gran escala, como la creación de índices o incluso migraciones. Lakebase con la arquitectura LTAP es el mejor lugar para ejecutar cargas de trabajo OLTP.

Si aún no ha probado Lakebase y Synced Tables, comience hoy mismo.

(Esta entrada del blog ha sido traducida utilizando herramientas basadas en inteligencia artificial) Publicación original

Recibe las últimas publicaciones en tu bandeja de entrada

Suscríbete a nuestro blog y recibe las últimas publicaciones directamente en tu bandeja de entrada.