Ir al contenido principal
Ingeniería de datos

Escalado automático de Lakebase Postgres

Un análisis profundo sobre cómo escalamos Postgres en tiempo real

por Carlota Soto

  • Requisito de arquitectura de escalado automático
  • Cuándo aumentar y reducir la capacidad
  • Cómo ajustar la capacidad sin detener PostgreSQL

Elegir el tamaño de una instancia de base de datos antes de conocer la carga de trabajo es un patrón de diseño antiguo. El proceso suele ser inestable y se siente como un gran desperdicio de cómputo, especialmente ahora que el cómputo se está convirtiendo en un lujo.

Lakebase Postgres elimina por completo la necesidad de elegir un tamaño gracias al escalado automático. La capacidad de respuesta del escalado automático proviene de la redistribución de recursos de la VM en el mismo lugar y de un algoritmo que realiza un seguimiento de la CPU, la memoria y el conjunto de trabajo de la base de datos.

image9.png

Cómo se ve el escalado automático para una muestra aleatoria de bases de datos de Lakebase Postgres. Tenga en cuenta que esto es solo una hora.

El requisito arquitectónico

El Postgres tradicional se ejecuta como un proceso con estado vinculado a una máquina y sus discos; reemplazar o cambiar el tamaño de esa máquina es una operación de base de datos porque la máquina posee tanto la ejecución como el estado duradero. Pero la arquitectura de Lakebase Postgres separa esas responsabilidades:

  • La capa de cómputo ejecuta Postgres y las consultas. Utiliza RAM y NVMe local para un acceso de baja latencia, y no posee un estado duradero.
  • La capa de almacenamiento posee la durabilidad y el historial. El WAL es replicado por "safekeepers" que se ejecutan en SSD, los "pageservers" (también SSD) reconstruyen las versiones de las páginas y el almacenamiento de objetos conserva el registro inmutable a largo plazo. (Esta publicación de blog se centra en el cómputo, pero escribimos un análisis profundo sobre la parte de almacenamiento si también le interesa).

Por lo tanto, un nodo de cómputo puede iniciarse, detenerse, moverse o cambiar de tamaño sin mover la base de datos subyacente. Esta es una base esencial.

image8.png

Ahora bien, cuando se trata de implementar el escalado automático, la historia tiene dos partes: primero, hay que determinar cuándo ajustar la capacidad hacia arriba y hacia abajo, y segundo, cómo hacerlo sin detener Postgres.

Abordemos ambos puntos en orden.

Parte I: El algoritmo

Las tres señales de escalado automático

Para deducir cuándo cambiar el tamaño, el algoritmo de escalado automático de Lakebase Postgres realiza un seguimiento de tres señales, y cada una produce su propio tamaño de cómputo objetivo:

  1. Carga de la CPU: cpuGoalCU
  2. Uso de memoria: memGoalCU
  3. Tamaño del conjunto de trabajo de la caché de cómputo: lfcGoalCU

El objetivo de escalado final es el mayor de los tres, limitado a los tamaños de cómputo mínimo y máximo que el usuario haya configurado para esa base de datos (los límites de escalado automático):

CPU (cpuGoalCU)

La CPU es la más sencilla de las tres señales. El algoritmo vigila de cerca la intensidad con la que trabaja el procesador:

  • Cada cinco segundos, el autoscaler-agent lee el promedio de carga de la CPU de un minuto de la VM.
  • El objetivo de la CPU es mantener esa carga en o por debajo del 90% de la capacidad disponible de la CPU.
  • Cuando la carga supera ese objetivo, cpuGoalCU aumenta. Cuando la carga sostenida disminuye, el objetivo disminuye con ella.

El uso de un promedio de un minuto filtra las fluctuaciones muy cortas y, al mismo tiempo, responde a cambios significativos en la demanda. El intervalo de sondeo de cinco segundos permite al sistema actualizar el objetivo a medida que cambia ese promedio.

Sin embargo, la CPU por sí sola no es suficiente para escalar Postgres correctamente. Una consulta que espera a que lleguen datos a través de la red puede mostrar un uso bajo de la CPU y, al mismo tiempo, tener un rendimiento deficiente. El algoritmo también debe tener en cuenta la presión de la memoria y de la caché.

Memoria (memGoalCU)

La memoria tiene un modo de fallo diferente al de la CPU. Si la demanda supera brevemente la CPU disponible, las consultas se vuelven más lentas; pero si Postgres asigna más memoria de la que tiene la VM, el kernel puede finalizar procesos. Por lo tanto, el escalador automático necesita una señal mucho más rápida que la CPU para el agotamiento de la memoria.

Por lo tanto, el sistema vigila la memoria en dos frecuencias:

  • Cada cinco segundos, el autoscaler-agent lee las métricas generales de memoria de la VM.
  • Cada 100 milisegundos, el vm-monitor comprueba la memoria utilizada por Postgres.

El objetivo de memoria mantiene el uso por debajo del 75% de la RAM asignada. Ese margen de maniobra le da espacio al sistema para responder a nuevas asignaciones y deja memoria para el sistema operativo invitado y otros procesos.

El vm-monitor también comprueba cada propuesta de reducción de escala. La memoria no se puede eliminar si al hacerlo se dejara a los procesos en ejecución sin suficiente espacio.

Un poco de historia: Este enfoque de sondeo reemplazó a un diseño anterior basado en el evento memory.high de cgroup. Cruzar memory.high hacía que Linux reclamara memoria y limitara los procesos dentro de cgroup. El sondeo demostró ser más predecible y estable, al tiempo que seguía ofreciendo al sistema una vista de 100 milisegundos de la memoria de Postgres.

La caché de cómputo (lfcGoalCU)

La tercera señal mide si los datos activos de la carga de trabajo caben cerca de Postgres. La historia a grandes rasgos es la siguiente:

Lakebase Postgres separa el almacenamiento y el cómputo; cuando una página no está disponible localmente, el cómputo la solicita al pageserver; la página devuelta se almacena en caché para lecturas posteriores. La caché de cómputo, que originalmente llamamos Local File Cache o (LFC), es una caché respaldada por disco dimensionada para caber en la caché de páginas del kernel. Actúa como una extensión de tamaño variable de los búferes compartidos de Postgres. Cuando un cómputo crece, el vm-monitor expande la caché para utilizar parte de la memoria añadida.

Para muchas cargas de trabajo OLTP, el rendimiento cambia drásticamente una vez que el conjunto de trabajo cabe en la memoria local. Esto expone un punto ciego en el escalado automático basado únicamente en la CPU: los fallos de caché dejan a las consultas esperando solicitudes de red, lo que reduce el uso de la CPU. Por lo tanto, el sistema puede ver una baja presión de la CPU en el momento exacto en que una caché más grande mejoraría el rendimiento. Así que en Lakebase Postgres, hay una tercera señal de escalado automático que estima directamente el conjunto de trabajo de Postgres.

Esta es la parte más interesante del algoritmo, así que veamos cómo funciona esa estimación.

En detalle: cómo estimamos el conjunto de trabajo de Postgres

El conjunto de trabajo de una carga de trabajo es el conjunto de páginas de bases de datos e índices a las que accede repetidamente durante un período determinado. Contar exactamente cada página para el escalado automático requeriría demasiada memoria, por lo que la forma clásica de resolver esto es confiar en HyperLogLog, un estimador de cardinalidad probabilístico que puede estimar la cantidad de elementos distintos en un conjunto utilizando una cantidad pequeña y fija de estado.

Para cada acceso a una página de Postgres, una implementación estándar de HyperLogLog:

  1. Aplica una función hash al identificador de la página.
  2. Utiliza los primeros bits del hash para seleccionar un registro.
  3. Cuenta los ceros iniciales en los bits restantes.
  4. Actualiza el registro seleccionado si esta observación supera su valor anterior.

La distribución de esos valores de registro proporcionaría una estimación de cuántas páginas distintas se han observado.

image10.png

Sin embargo, hay un problema con el simple uso de HyperLogLog para el escalado automático: un HyperLogLog estándar solo crece. Una vez que un registro ha observado un valor, no puede determinar qué elemento lo produjo ni cuándo se vio ese elemento por última vez.

Eso lo hace bueno para responder: "¿A cuántas páginas distintas ha accedido este cómputo desde que se inició Postgres?". Pero el escalado automático necesita una respuesta diferente, más cercana a: "¿Cuántas páginas distintas pertenecen a la carga de trabajo que se está ejecutando ahora?".

Sin un límite de tiempo, una importación antigua o una consulta analítica permanecerían en la estimación y mantendrían el cómputo sobredimensionado mucho después de que finalizara ese trabajo. Por lo tanto, cambiamos lo que almacenan los registros de HyperLogLog.

Añadir tiempo a HyperLogLog

Así es como funcionan realmente las cosas en Lakebase Postgres:

En lugar de establecer un bit cuando se observa un hash, el estimador almacena la marca de tiempo actual en esa posición. Para estimar la cardinalidad desde el momento T, trata las posiciones actualizadas después de T como establecidas y las posiciones más antiguas como no establecidas.

image6.png

HyperLogLog modificado en el autoescalado de Lakebase Postgres.

Esto genera una estimación para cualquier ventana que finalice en el presente, incluyendo

  • Páginas distintas a las que se ha accedido en el último minuto
  • Páginas distintas a las que se ha accedido en los últimos cinco minutos
  • Páginas distintas a las que se ha accedido en la última hora

Así que, volviendo al algoritmo, así es como funciona realmente la granularidad: cada 20 segundos, el autoscaler-agent recopila estimaciones del conjunto de trabajo para ventanas de uno a 60 minutos.

Pero la historia no termina aquí. Como seguramente habrá notado, esta es una ventana de tiempo amplia. ¿Cómo la elegimos realmente?

Elección de la ventana de tiempo del conjunto de trabajo

El problema es este: no existe una ventana universal que describa el conjunto de trabajo actual de una base de datos. Si elegimos una ventana corta, el motor de autoescalado responde rápidamente cuando finaliza una carga de trabajo, pero descartaría la caché de forma demasiado agresiva entre ráfagas. Si elegimos una ventana larga, el algoritmo protegería la caché, pero también mantendría la memoria asignada para un trabajo que ya no se está ejecutando.

El algoritmo resuelve esto analizando cómo cambia el conjunto de trabajo a lo largo del tiempo. Por ejemplo: para una carga de trabajo estable, el número estimado de páginas crece inicialmente y luego se estabiliza. Ampliar la ventana añade tiempo, pero se añaden pocas páginas nuevas, porque se accede repetidamente al mismo conjunto de trabajo.

image5.png

Ahora, considere una carga de trabajo pesada que finalizó recientemente. Las ventanas cortas contienen solo la carga de trabajo actual, más ligera; pero una vez que la ventana se remonta lo suficiente en el pasado como para incluir la carga de trabajo anterior, la estimación da un salto. El algoritmo busca ese salto, que marca el final de la meseta actual.

image4.png

En resumen:

La implementación comienza su búsqueda después de cinco minutos. Esto evita que el cómputo se reduzca inmediatamente durante una breve pausa y luego vuelva a crecer para la siguiente ráfaga. Pero si el algoritmo no encuentra un aumento drástico, utiliza la estimación de 60 minutos; ese es el resultado esperado para una carga de trabajo estable cuyo conjunto de trabajo permanece activo durante toda la hora.

image1.png

Proyección del crecimiento de la caché

Falta una última pieza. Medir el conjunto de trabajo actual llega un poco tarde: supongamos que una carga de trabajo comienza a escanear un nuevo conjunto de páginas. Si la caché de cómputo crece solo después de que se hayan leído esas páginas, es posible que las primeras páginas ya se hayan desalojado para dejar espacio a las posteriores. La caché tendría entonces que volver a recuperar algunos de los mismos datos.

Por lo tanto, el algoritmo también proyecta el crecimiento del conjunto de trabajo hacia adelante. Examina cómo aumenta la estimación de una duración a la siguiente y asigna suficiente caché para el conjunto de trabajo previsto para el próximo intervalo de control.

Debido a que las métricas de la caché se obtienen cada 20 segundos, la proyección cubre solo una fracción de minuto. Las proyecciones más largas reaccionarían antes, pero también amplificarían los picos breves y harían que el cómputo oscilara.

image2.png

El tamaño proyectado (¡por fin!) se convierte en lfcGoalCU. Y el objetivo algorítmico es ajustar el conjunto de trabajo dentro de la porción de memoria disponible para la caché de cómputo, hasta el 75% de la RAM del cómputo.

Parte II: Redimensionamiento del cómputo en ejecución

Para resumir, el objetivo de escalado era:

Esas tres señales le indican al sistema qué tamaño debe buscar. Aplicar ese tamaño significa cambiar la CPU y la memoria en una VM en ejecución sin interrumpir Postgres.

Cada instancia de Postgres en Lakebase Postgres se ejecuta dentro de su propia máquina virtual en un clúster de Kubernetes. Usamos VMs porque proporcionan un límite de aislamiento sólido y, a diferencia de una asignación de contenedores convencional, permiten agregar o eliminar CPU y memoria de un sistema huésped en ejecución.

Cuatro componentes coordinan cada redimensionamiento de cómputo:

  1. El autoscaler-agent se ejecuta en cada nodo de Kubernetes. Recopila métricas de las VMs de Postgres en ese nodo, calcula los tamaños objetivo e inicia el escalado.
  2. El vm-monitor se ejecuta dentro de cada VM. Supervisa de cerca la memoria de Postgres, valida las solicitudes de reducción de escala y redimensiona la caché de cómputo.
  3. Un programador de Kubernetes modificado mantiene la vista global de los recursos disponibles. Cada aumento de escala debe ser aprobado por el programador antes de asignar la memoria.
  4. NeonVM aplica el cambio. Es un recurso y controlador personalizado de Kubernetes, creado con QEMU y KVM, que puede agregar o eliminar CPU y memoria de una VM en ejecución. (Descargo de responsabilidad: la arquitectura de Lakebase Postgres comenzó en Neon y el nombre del recurso/controlador sigue siendo el mismo).
image3.png

Escalado ascendente

Como acabamos de ver, el escalado ascendente ocurre cuando uno de los tres objetivos requiere más cómputo del que tiene actualmente la VM. Un aumento de escala sigue esta secuencia:

  1. El autoscaler-agent calcula el nuevo objetivo a partir de las metas de CPU, memoria y conjunto de trabajo.
  2. El programador de Kubernetes comprueba si el nodo puede satisfacer la solicitud sin sobreasignar memoria.
  3. Una vez aprobado, el autoscaler-agent actualiza el recurso NeonVM.
  4. El controlador NeonVM agrega CPU y memoria a la VM en ejecución.
  5. El vm-monitor expande la caché de cómputo para utilizar la nueva capacidad.

El programador es la única fuente de verdad para la asignación. Ve tanto la programación ordinaria de Kubernetes como las solicitudes de autoescalado. Sin esa coordinación, el programador podría colocar una nueva carga de trabajo en un nodo en el mismo momento en que el autoescalador asigna la memoria restante a una VM de Postgres.

Si un nodo está demasiado lleno para crecer en el sitio, NeonVM puede migrar en vivo la VM a otro nodo. La VM conserva su dirección IP, por lo que las conexiones existentes permanecen abiertas. Los cómputos de Lakebase Postgres tienen poco estado local duradero que mover, por lo que la migración consiste principalmente en la memoria de la VM y el estado de tiempo de ejecución.

Escalado descendente

Una reducción de escala utiliza exactamente los mismos componentes, con una comprobación adicional dentro de la VM. El vm-monitor confirma que eliminar memoria aún dejará suficiente para Postgres y el resto del sistema huésped. Si no fuera así, la reducción de escala no se lleva a cabo.

Advertencia: El escalado descendente cuenta tanto como el escalado ascendente. Algunos sistemas de autoescalado se apresuran a agregar capacidad pero tardan en devolverla, lo que deja a las bases de datos sobredimensionadas mucho después de que haya pasado un pico. Lakebase Postgres trata ambas direcciones de la misma manera. El objetivo es realizar un seguimiento de la carga de trabajo lo más de cerca posible momento a momento, para que deje de pagar por la capacidad tan pronto como deje de necesitarla.

Resumen

Lakebase Postgres supervisa la carga de trabajo a medida que se ejecuta y redimensiona el cómputo para adaptarlo en tiempo real. La arquitectura de Lakebase lo hace posible: dado que el almacenamiento está desacoplado y es duradero por sí mismo, el cómputo es libre de moverse sin preocuparse por los datos.

El sistema resultante escala en ambas direcciones, en una base de datos activa, sin perder conexiones. Lo más importante es que va más allá de la señal obvia: el seguimiento de la CPU por sí solo pasaría por alto una carga de trabajo estancada por fallos de caché, por lo que el algoritmo también realiza un seguimiento de la presión de la memoria y de una estimación del conjunto de trabajo que tiene en cuenta el tiempo.

El bucle final se ejecuta en tres escalas de tiempo:

  • 100 milisegundos: el vm-monitor comprueba la memoria de Postgres para detectar una asignación rápida
  • 5 segundos: el autoscaler-agent lee la CPU y la memoria general
  • 20 segundos: el autoscaler-agent evalúa las estimaciones del conjunto de trabajo en ventanas de uno a 60 minutos

Así es como una base de datos de producción puede cambiar de tamaño más de 32 000 veces al mes.

image7.png

A medida que el cómputo se vuelve más costoso y más disputado, pagar por un pico que rara vez se alcanza es un patrón de diseño que podría no ser viable muy pronto. El autoescalado prepara a Postgres para cargas de trabajo donde el desperdicio de cómputo no es una opción.

Ejecútelo

Pídele a tu agente que despliegue Lakebase Postgres y pon a prueba su escalado automático. Comienza aquí.

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

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