Ir al contenido principal
Ingeniería de datos

Mejora de la caché de cómputo de Lakebase Postgres

Parte 1: Cómo los nodos de cómputo grandes de Postgres funcionan hasta 2 veces más rápido con una menor latencia

por David Wein, Sunil Kamath y Haoyu Huang

  • La caché estándar de Postgres frente a la caché de Postgres de Lakebase
  • Cómo creamos una caché de escalado automático que funciona en conjunto con búferes compartidos y mantiene la mayor cantidad de datos posible en el cómputo
  • Resultados de producción que incluyen un rendimiento 2x, menos lecturas de la capa de almacenamiento y una menor latencia

El modelo de almacenamiento desagregado de Lakebase Postgres proporciona una plataforma rica en funciones, flexible y de bajo costo. El almacenamiento en caché eficiente de los datos es fundamental para proporcionar un alto rendimiento y una baja latencia mientras los datos se respaldan en un almacén de objetos como S3.

Este almacenamiento en caché se realiza en dos capas: en el almacenamiento distribuido, donde las páginas de Postgres se materializan para un alto rendimiento de escritura y servicio de lectura; y en el propio cómputo de Postgres para servir las páginas de acceso frecuente desde la DRAM para un acceso ultrarrápido.

Hemos estado trabajando arduamente para realizar mejoras en el almacenamiento en caché del lado del cómputo, y en este blog presentaremos nuestros planes a corto plazo y profundizaremos en lo que ya se ha entregado a los clientes.

Primero, un poco de contexto sobre cómo llegamos aquí.

La caché estándar de Postgres

Las bases de datos son famosas por su gran consumo de DRAM (memoria). Utilizan principalmente esta memoria como caché de datos y esperan que el acceso a las filas de la caché se mida en nanosegundos, órdenes de magnitud más rápido que incluso las unidades NVMe más rápidas.

Postgres organiza los datos en filas en páginas, y las páginas a las que se accede activamente deben cargarse en un área de memoria conocida como "shared buffers". Debido a que Postgres tradicionalmente almacena páginas utilizando el sistema de archivos del sistema operativo, el núcleo del OS también utilizará su caché de páginas flexible para proporcionar almacenamiento en caché entre los shared buffers de Postgres y el disco.

Este esquema de shared buffers + caché de páginas funciona razonablemente bien, pero presenta algunas desventajas y desafíos.

Desventajas

  1. Doble almacenamiento en búfer (double buffering), lo que reduce la cantidad de datos que puede almacenar de manera efectiva en el cómputo. Considere un cómputo con 4 GB de RAM que utiliza 1 GB para shared buffers. A medida que lee páginas del disco para llenar el 1 GB de shared buffers, las lecturas pasan por la caché de páginas del OS, que también contiene esos datos. Ahora está consumiendo 2 GB de RAM para almacenar en caché 1 GB de datos.
  2. La caché de páginas del OS no sabe nada sobre los shared buffers ni los aspectos internos de Postgres, por lo que no puede tomar decisiones inteligentes sobre qué páginas reemplazar.

Desafíos técnicos

  1. En un sistema de almacenamiento desagregado como Lakebase Postgres, los datos leídos del almacenamiento no viajan a través del sistema de archivos del OS ni de la caché de páginas.
  2. Shared buffers es un parámetro estático, lo que significa que se establece antes de iniciar Postgres y no se puede cambiar sin reiniciar la base de datos. Este es un desafío importante para un sistema de escalado automático sin servidor (serverless autoscaling) como Lakebase.
  3. Postgres utiliza un proceso de sistema operativo independiente para cada conexión activa, por lo que cuanto más grandes sean los shared buffers (es decir, cuanta más memoria le asigne a Postgres), más gestión de memoria debe realizar el OS para cada una de las conexiones, lo que a su vez consume memoria.

La ruta de la caché de Lakebase

image3.png

Ahora que hemos proporcionado un poco de contexto, hablemos de cómo los estamos resolviendo en Databricks.

Nuestro estado final deseado es hacer el uso más eficiente de la DRAM en su cómputo a través de shared buffers dinámicos de Postgres que se escalen automáticamente con su carga de trabajo y utilicen hasta el 75% de la memoria disponible.

Eventualmente necesitamos ajustar nuestra plataforma de cómputo para aprovechar los shared buffers con escalado automático, pero también queremos ofrecer mejoras incrementales sensatas a nuestros clientes a medida que estén disponibles. Cada entrega incremental nos permite enviar con confianza una o más piezas de la hoja de ruta al tiempo que brinda un beneficio real a los clientes. Por lo tanto, incluso si el objetivo son los cómputos con escalado automático, comenzamos con cómputos fijos, como se describe en la siguiente sección.

Esto es lo que implementamos.

Shared buffers más grandes

image1.png

Si recuerda los desafíos técnicos anteriores, un sistema desagregado como Lakebase no ruta sus lecturas a través del sistema de archivos estándar del OS y su caché de páginas. Recuerde también que los shared buffers de Postgres son estáticos y no se pueden escalar automáticamente.

Para solucionar esto, creamos una capa que llamamos caché de archivos locales (LFC). La LFC actuó como un sustituto, creando una caché de escalado automático que funcionaba en conjunto con los shared buffers y mantenía la mayor cantidad de datos posible almacenada en caché en el cómputo. Esta fue una solución inteligente y pragmática que permitió a Lakebase Postgres lanzar el escalado automático y se ha utilizado en todos los cómputos desde el lanzamiento.

Aunque se expone a los usuarios como una única caché de cómputo de alta velocidad, la arquitectura subyacente admite hasta dos niveles:

  • Shared buffers: el búfer compartido en memoria de Postgres, que representa la ruta de acceso de menor latencia.
  • Local file cache: una caché secundaria ampliada que reside en el NVMe local del nodo de cómputo, que ofrece una mayor capacidad que la memoria pero requiere I/O de disco para acceder a una página.

Los shared buffers se ajustaron de manera conservadora para que no consumieran demasiada memoria cuando se ejecutaban con la CU mínima configurada, con el tamaño máximo jamás configurado en 1 GB de shared buffers y la LFC consumiendo el resto de la capacidad total de la caché de cómputo (hasta el 75% de la DRAM). Cualquier solicitud que resulte en un fallo (miss) en ambos niveles se enruta desde el nodo de cómputo a la capa de almacenamiento distribuido.

En conjuntos de trabajo más grandes, limitar los shared buffers a 1 GB obligaba a que la mayoría de los aciertos de caché (cache hits) pasaran por el nivel de LFC más lento. La LFC nos ha servido bien, pero nuestra intención es retirar su forma actual a medida que avanzamos hacia shared buffers totalmente dinámicos.

Nota: Los cómputos fijos fueron lo primero

Nuestra primera entrega de shared buffers más grandes se dirige a cómputos de tamaño fijo, ya que los shared buffers aún no son dinámicos. En estos, ahora desactivamos la LFC y configuramos los shared buffers al 75% de la DRAM. Esto ya está disponible para cómputos de tamaño fijo con CU >= 80. La eliminación del límite de búfer de ~1 GB mantiene las páginas activas (hot pages) en la capa de memoria más rápida en lugar de descender en cascada al almacenamiento de archivos local.

Para ver si los shared buffers grandes están habilitados para su cómputo, ejecute show shared_buffers dentro de una conexión de Postgres. Un endpoint de Lakebase de 80 CU en Databricks debería ver un valor de 15278640.

Mantener los datos activos en los shared buffers en lugar de en la caché de páginas del OS también aborda las desventajas descritas anteriormente. No hay doble almacenamiento en búfer (double buffering), por lo que 1 GB de datos almacenados en caché consume 1 GB de RAM en lugar de 2 GB. Y debido a que la caché reside dentro de Postgres en lugar de en el núcleo (kernel), las decisiones de desalojo se pueden tomar con conocimiento del estado de la base de datos, lo que nos posiciona para buscar políticas de reemplazo más inteligentes que las que puede ofrecer el OS.

Dimensionar los shared buffers al 75% de la DRAM en cómputos de tamaño fijo no fue tan simple como realizar un cambio de configuración. Esto se debe al tercer desafío técnico: la arquitectura de proceso por backend.

La siguiente sección describe nuestra solución.

Abordar la sobrecarga de memoria y traducción con huge pages

Postgres utiliza una estructura basada en procesos en la que cada backend asigna los shared buffers a su propio espacio de direcciones, lo que requiere sus propias entradas de tabla de páginas (page table entries), las estructuras mantenidas por el núcleo por las que pasa el hardware para traducir las direcciones virtuales a memoria física. Por defecto, Linux realiza esta asignación a través de páginas de 4 KB.

Algunas cifras sencillas: cada 1 GB de shared buffers corresponde a 262,144 entradas de tabla de páginas por proceso. Con 32 GB de shared buffers y 512 backends, eso representa aproximadamente 4,300 millones de entradas, o alrededor de 32 GB de tablas de páginas para asignar 32 GB de caché.

Este conjunto de trabajo también supera con creces la capacidad del Translation Lookaside Buffer (TLB), una caché en la unidad de gestión de memoria de la CPU que acelera la traducción de virtual a físico. Incluso un acierto en el shared buffer incurre en una penalización por fallos de TLB y recorridos de tablas de páginas (page table walks).

Para mitigar esto, la comunidad de Postgres aconseja utilizar un mecanismo del OS llamado huge pages (de 2 MB cada una) con shared buffers grandes. Cambiar a huge pages reduce el tamaño de las tablas de páginas en un factor de 512 y disminuye significativamente las tasas de fallos de TLB.

En nuestras pruebas de referencia (benchmark), la configuración de Postgres con huge pages redujo la latencia de lectura de cola (tail read latency) hasta en un ~40% y disminuyó la utilización de la CPU hasta en un ~30%.

Soporte de huge pages en entornos virtualizados

Lakebase Postgres se ejecuta dentro de máquinas virtuales invitadas (guest VMs) ligeras en hosts bare-metal. La traducción de direcciones de memoria implica dos capas virtualizadas. Aprovechar las huge pages requiere una implementación coherente en toda la pila: desde la reserva a nivel de host, pasando por el hipervisor que respalda la memoria de la VM, hasta el núcleo invitado (guest kernel). Una falla en cualquier nivel degrada los beneficios de rendimiento resultantes.

Recientemente introdujimos un soporte dedicado para huge pages en toda nuestra infraestructura de VM. Decidimos utilizar páginas HugeTLB explícitas de 2 MB en lugar de confiar en las transparent huge pages de mejor esfuerzo. Ahora, las VM asignadas para cómputos grandes de tamaño fijo se inicializan con un volumen predeterminado de huge pages suficiente para el inicio de Postgres. Para optimizar los recursos del sistema, el inicio del cómputo libera automáticamente cualquier excedente de huge pages más allá de las requeridas por Postgres.

Consejo: Para ver si las huge pages explícitas grandes están habilitadas para su cómputo, ejecute show huge_pages dentro de una conexión de Postgres. Un endpoint de Lakebase de 80 CU debería ver un valor de "on"

Resultados de producción

El despliegue comenzó región por región hace unas semanas. Los siguientes ejemplos se midieron en endpoints de producción grandes después del reinicio que habilitó la nueva configuración.

Ejemplo 1: ~2 veces más rendimiento, 5 veces menos lecturas de almacenamiento

En un endpoint grande, el cambio se activó alrededor de las 06:10 UTC del 11 de agosto. Los bloques de Postgres accedidos por segundo se duplicaron, lo que usamos aquí como un indicador del rendimiento. El cliente informó una menor latencia p50 y p99 en comparación con el día, la semana y el mes anteriores.

image2.png

Este endpoint configuró una gran caché de archivos locales. Con búferes compartidos más grandes, las solicitudes GetPage/s de almacenamiento disminuyeron de aproximadamente 8K por segundo a aproximadamente 1.5K.

image4.png

Ejemplo 2: 1.3 veces más rendimiento

En otro endpoint grande, el cambio se activó alrededor de las 01:30 UTC del 14 de agosto. El rendimiento aumentó aproximadamente un 43%.

image7.png

La tasa de aciertos de caché de cómputo alcanzó casi el 100%, y las solicitudes se atendieron casi en su totalidad desde los búferes compartidos.

image8.png

Ejemplo 3: Uso de CPU 5 veces menor, rendimiento 2 veces mayor

En esta carga de trabajo, el uso de CPU disminuyó de 20 núcleos a 4 después del despliegue del 15 de agosto. La tasa de aciertos de caché de cómputo aumentó a casi el 100% y el rendimiento medido se duplicó.

image5.png

image6.png

Parte 2: escalado automático

Actualmente estamos trabajando para llevar búferes compartidos más grandes a los cómputos de Postgres con escalado automático. El escalado automático introduce una complejidad adicional: debemos expandir dinámicamente los búferes compartidos al escalar hacia arriba y reducirlos al escalar hacia abajo, todo mientras asignamos el volumen exacto requerido de páginas gigantes.

Para ir más allá de los cómputos de tamaño fijo, hemos desarrollado un protocolo para el escalado automático de páginas gigantes proporcionadas al invitado. Las páginas gigantes se escalan en conjunto con los búferes compartidos dinámicos, lo que garantiza que mantengamos una traducción de direcciones eficiente incluso con una alta concurrencia y tamaños de memoria elevados. Nuestra próxima publicación (parte 2) entrará en los detalles técnicos de esta implementación de búferes compartidos dinámicos, incluido el estado actual de Postgres de código abierto y las áreas que hemos elegido para avanzar aún más en la función y contribuir al proyecto original.

Pruébalo

Todas estas mejoras de rendimiento provienen de la arquitectura de Lakebase Postgres. La capa de almacenamiento actúa como el sistema de registro autorizado, un nodo de cómputo no tiene estado y su memoria sirve como capa de caché.

Implementa Lakebase Postgres y pon a prueba el rendimiento. Comienza aquí.

Lakebase Postgres se puede utilizar como una base de datos independiente, y también puedes integrarlo con el resto de Databricks Data + AI Platform: gobernanza de Unity Catalog, análisis de lakehouse, notebooks y flujos de trabajo de AI.

(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.