Un análisis profundo sobre cómo escalamos Postgres en tiempo real
por Carlota Soto
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.

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

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.
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:
cpuGoalCUmemGoalCUlfcGoalCUEl 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):
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:
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é.
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:
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 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.
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:
La distribución de esos valores de registro proporcionaría una estimación de cuántas páginas distintas se han observado.

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

HyperLogLog modificado en el autoescalado de Lakebase Postgres.
Esto genera una estimación para cualquier ventana que finalice en el presente, incluyendo
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?
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.

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.

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.

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.

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

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:
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.
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.
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:
Así es como una base de datos de producción puede cambiar de tamaño más de 32 000 veces al mes.

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.
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
Suscríbete a nuestro blog y recibe las últimas publicaciones directamente en tu bandeja de entrada.