Estás empezando un nuevo proyecto — o empresa — y has decidido que vas a utilizar Postgres. La parte difícil (elegir tu base de datos) ha terminado, y ahora empieza la divertida: asegurarte de que no tendrás que volver a pensar en ello hasta dentro de unos años.

Este post repasará algunos consejos menos conocidos para configurar Postgres a largo plazo, desde el agrupamiento de conexiones hasta los principios básicos de seguridad, pasando por las extensiones y los índices.

Configurar el Pooling de Conexiones para Postgres con PGBouncer

Por defecto, Postgres crea un proceso independiente para cada conexión de cliente a partir del proceso principal del sistema operativo. Con poco volumen de tráfico, el tiempo que se tarda en crear y destruir esos procesos – además del hecho de que nunca se reutilizan- no importará. Puedes establecer un número de max_connections manualmente, pero con el tiempo, a medida que aumentes la escala, es probable que te encuentres con problemas en este punto. Un Pooler de Conexiones te ayuda esencialmente a «almacenar en caché» estos procesos y reutilizarlos a medida que los clientes se conectan y desconectan de tu base de datos.

Aunque puedes integrar el pooler de conexiones en la lógica de tu aplicación, la mayoría opta por una herramienta de terceros, que en el caso de Postgres es PGBouncer. Es un pooler de conexiones ligero y de código abierto que puedes instalar tanto en tu servidor de base de datos o en tu servidor de aplicaciones. Puedes elegir entre 3 niveles de pooling:

  • Pooling de sesiones: se mantiene fiel al modelo de «las conexiones de los clientes son indefinidas» y mantiene una conexión abierta durante todo el tiempo que un cliente está conectado.
  • Pooling de transacciones: las conexiones duran una sola transacción, tras la cual se devuelven al pool.
  • Pooling de sentencias: las conexiones duran sólo una consulta, por lo que si tienes varias como parte de una transacción, no funcionaría en absoluto.

La mayoría elige pooling de sesiones – es lo más conservador y lo que menos riesgo entraña de que se caigan las conexiones – pero cada aplicación es diferente, y tendrás que averiguar cuál es el modo adecuado para tus limitaciones.

Impacto del Pool de Conexiones en el Rendimiento

La pregunta del millón: ¿funciona realmente? Percona ejecutó una serie de pruebas comparativas para averiguar cómo afecta PGBouncer al rendimiento. Con un número reducido de clientes simultáneos (<60), PGBouncer degrada bastante las transacciones por segundo (TPS) debido a la sobrecarga del pooling. Pero cuando aumentas a >100, empiezas a ver ventajas significativas en el rendimiento.

PGBouncer
PGBouncer

Entonces, ¿necesitas un pooler de conexiones de inmediato para dar soporte a tus primeros usuarios? Probablemente no. Pero utilizar PGBouncer te ayudará una vez que alcances incluso un tráfico bajo/moderado.

Seguridad Postgres para Dummies

Durante las primeras semanas que trabajas en tu proyecto, normalmente son sólo uno o dos desarrolladores trabajando en una base de datos vacía; la seguridad no es lo más importante. Pero cuando lances tu aplicación al mundo, tendrá que serlo. Y con las bases de datos, a veces parece que hay un millón de formas distintas de bloquear las cosas.

Restringir el Acceso a Nivel de Host o de Usuario

Empecemos por el acceso. Postgres restringe el acceso de dos formas:

  1. A nivel de host – definiendo direcciones IP y dominios con derechos de acceso
  2. A nivel de usuario – definiendo los usuarios de la base de datos y sus permisos

El archivo pg_hba.conf del directorio PGDATA es donde se define quién puede conectarse a qué bases de datos. Si no tienes una entrada para un cliente ahí, no podrá acceder a la base de datos. Suponiendo que tu servidor de aplicaciones se esté ejecutando en otro lugar, así es como puedes permitir que acceda a la base de datos:

# Trust any connection via TCP/IP from this machine
host all 127.0.0.1 255.255.255.255 trust

Aparte de simplemente «confiar en cualquier conexión desde esta máquina», hay montones de formas diferentes de autenticar a tu cliente con el servidor de la base de datos, desde la contraseña a la identificación, pasando por los certificados. Y si has renunciado a las grandes comodidades de RDS (o Kinsta) y estás ejecutando tu backend en el mismo servidor que tu base de datos, puedes conectarte mediante sockets Unix en lugar de TCP/IP.

Autorización y Privilegios

Una vez que tu cliente esté autenticado, tienes que abordar la cuestión de la autorización. El estándar SQL define un sistema de privilegios, y cada objeto de Postgres (como una tabla, una fila, etc.) tiene diferentes privilegios relacionados con él que pueden asignarse a los usuarios: cosas como SELECT y UPDATE, pero también TRUNCATE, REFERENCES, TRIGGER, etc. Puedes otorgar privilegios a los usuarios con el comando GRANT.

La mejor práctica es seguir el principio del menor privilegio, de modo que el usuario de la base de datos que crees para tu(s) cliente(s) sólo debe poder acceder a lo que necesite acceder.

Seguridad a Nivel de Fila

Lo último que hay que tratar aquí es la seguridad a nivel de fila. La RLS (row level security, seguridad a nivel de fila) existe desde la perspectiva de la tabla (no del usuario) y restringe a qué filas se puede acceder, actualizar, etc. Por defecto, las tablas no tienen activada la RLS, por lo que tu usuario podrá hacer lo que dicten sus políticas de acceso. Para habilitar RLS para una tabla, empezarías con:

ALTER TABLE [table_name] ENABLE ROW LEVEL SECURITY

Y luego añade una política. Supongamos que quieres restringir el acceso de lectura a tu tabla sable_luz_interna a personas de confianza, que ya están definidas en el grupo de usuarios jedi, de forma que sólo el propietario de un sable láser pueda ver sus detalles internos. Así es como lo harías:

ALTER TABLE sable_luz_interna ENABLE ROW LEVEL SECURITY
CREATE POLICY jedi_only ON sable_luz_interna TO jedi
    USING (jedi = sable_luz_interna);

Las políticas de RLS como ésta son útiles cuando necesitas seguridad a un nivel más granular que el de las tablas (situaciones con PII, etc.).

Prepárate para los Problemas de Escalado

En todas las startups en las que he trabajado, ha habido cierto grado de escalado manual en lo que se refiere a la base de datos. Un día te despertarás, y Datadog se está volviendo loco porque tu servidor Postgres se ha quedado completamente sin espacio. Investigarás, actualizarás la página de incidencias y, finalmente, aumentarás el tamaño del disco hasta que vuelva a ocurrir (aunque la próxima vez puede que sea un problema de RAM). ¡Adelantarse a estas cosas puede ayudar! Algunas sugerencias:

1. Configura la Supervisión de la Base de Datos

La mayoría de las empresas en las que he trabajado utilizan Datadog para la monitorización de sus bases de datos. Si utilizas un servicio de base de datos gestionada, probablemente puedas arreglártelas un poco utilizando su material nativo. Datadog tiene un buen artículo en su blog sobre las principales métricas que debes vigilar, como el rendimiento de lectura y escritura, los escaneos secuenciales, los datos escritos en disco, etc.

2. Elabora Directrices para Escalar Verticalmente

Cuando tu equipo reciba un aviso – y  ocurrirá – , lo último que quieres es que todos tengan que ponerse manos a la obra para resolver el problema, cuando en la mayoría de los casos, una simple ampliación resuelve el problema. Es bueno elaborar un plan básico para tu equipo sobre lo que está dentro del alcance cuando te estás quedando sin espacio o computación.

3. Limpieza con Vacuum y Ajuste de tu Autovacuum

Cuando DELETE datos en Postgres o UPDATE datos (que es funcionalmente equivalente a borrar e insertar), Postgres no borra realmente esos datos de inmediato (😱). En lugar de eso, se «marcan» como borrados almacenando el ID de transacción del borrado en una cabecera xmax; la razón de esto es que hace que MVCC en Postgres sea más sencillo. Pero si estas filas no se borran realmente con el tiempo, empezarán a malgastar espacio en disco y te darán problemas.

La forma más sencilla de deshacerte de estas filas es utilizar el comando VACUUM. Podrías ejecutar un vacuum manualmente cada vez que se acumulen filas muertas o incluso configurarlo para que se ejecute cada x minutos, pero una estrategia mejor es hacer un autovacuum en función de cuántas filas muertas se hayan acumulado. Ajustar tu autovacuum es un tema lleno de matices que va más allá del alcance de este post: Te recomiendo encarecidamente que leas el artículo de 2ndQuadrant al respecto.

4. Configura una Réplica de Lectura (o Dos)

Esto es fácil. Si prevés un aumento significativo del tráfico (un lanzamiento próximo, etc.), puedes crear fácilmente réplicas de sólo lectura (o al menos una); te ayudarán a descargar algo de trabajo de la instancia principal de la BD.

Si optas por varias réplicas, obtendrás la ventaja añadida de mejorar la disponibilidad si alguna de ellas se cae por cualquier motivo. Añadir réplicas es bastante sencillo en la mayoría de los proveedores de DBaaS; sólo tienes que estar atento al coste: a menudo tienen el mismo precio que una instancia de BD principal a pesar de ser de sólo lectura.

Añade Índices a tus Tablas Más Grandes (Previstas)

Los índices de base de datos ayudan a acelerar las consultas de lectura creando estructuras de datos auxiliares que hacen más rápidos tus escaneos. Para muchos casos de uso, añadir un índice a una tabla o dos es básicamente una obviedad. En Postgres, puedes crear un índice con el comando CREATE INDEX (duh). Cuando consultes una tabla, la base de datos comprobará si existe un índice, y lo utilizará en caso afirmativo ((por cierto, puedes comprobarlo con EXPLAIN).

El tipo de índice más popular en Postgres – y el predeterminado cuando se utiliza CREATE INDEX – es el Índice B-Tree. Esencialmente, toma la columna sobre la que quieres crear un índice, la ordena y almacena punteros a las filas ordenadas. De este modo, puedes obtener la eficiencia de la búsqueda binaria en cualquier columna que desees, no sólo en la columna en la que está ordenada la tabla real (si es que existe). Puedes leer más detalladamente cómo se implementan estos árboles en la documentación de Postgres aquí.

Aunque son útiles, los índices no son todo diversión y juegos; ocupan espacio, y si no tienes cuidado con cuántos y de qué tipo creas, pueden empezar a degradar el rendimiento de la base de datos. Nadie lo dice mejor que la propia documentación de Postgres:

«Los índices se utilizan principalmente para mejorar el rendimiento de la base de datos (aunque un uso inadecuado puede provocar un rendimiento más lento)»

Internamente, cuando creas un índice, Postgres materializa una tabla de consulta que tiene el índice y un puntero al registro del índice. Demasiadas de esas tablas consumen espacio en disco, hacen que las consultas INSERT tarden más y obligan al motor de consultas a considerar más opciones antes de elegir cómo ejecutar una consulta.

Bonus: Añade Algunas Extensiones Postgres

Algo que hace único a Postgres es la compatibilidad nativa con extensiones de terceros. Puedes crearlas a partir de SQL y C, y pueden ser tan pequeñas como un par de sentencias o tan grandes como toda una biblioteca de software. Utilizar extensiones disponibles públicamente / de código abierto te ayuda del mismo modo que utilizar un paquete de software; ¿por qué escribir tu propio código cuando puedes utilizar el de otro? Aquí tienes algunas de las extensiones Postgres más populares:

Timescale

Timescale es una extensión de Postgres para trabajar con datos de series temporales. En pocas palabras, hace que tus consultas sean (mucho) más rápidas y almacena los datos de series temporales de forma muy eficiente. Puedes encontrar las instrucciones de instalación aquí, o considerar la opción de Timescale alojada en la nube si realmente diriges tu negocio con datos de series temporales (aunque probablemente ya seas consciente de ello si lo eres).

PostGIS

PostGIS añade soporte a Postgres para almacenar, indexar y consultar datos geográficos (piensa en líneas, polígonos, ubicaciones, etc.). Si utilizas un proveedor en la nube, la mayoría preinstalan PostGIS. Pero si necesitas instalarlo tú mismo, puedes encontrar instrucciones de instalación aquí.

pg_stat_staements

pg_stat_statements crea una vista en tu base de datos Postgres con estadísticas sobre cada consulta ejecutada en la base de datos. Puedes ver estadísticas como cuánto tiempo tarda en ejecutarse la consulta (media, mediana, promedio, etc.), quién ejecuta la consulta, accesos a la caché de bloques, número de bloques escritos, y muchas más (44 columnas en total en esa vista). Para instalarlo, sólo tienes que añadirlo a tu archivo .conf y reiniciar el servidor.

pg_audit

pg_audit ayuda a las empresas que pueden estar sujetas a auditorías detalladas (por ejemplo, gubernamentales, financieras, etc.). Puedes hacer que Postgres registre todas y cada una de las sentencias contra la base de datos configurando `log_statement=all`, pero eso no significa que la información que necesitas vaya a ser fácil de buscar y encontrar. pg_audit utiliza las funciones de registro internas de Postgres para que sea más fácil encontrar y trabajar con esos registros que un auditor podría necesitar. Puedes encontrar las instrucciones de instalación aquí.

Resumen

Postgres es una opción estupenda (y muy popular) sobre la que construir tu empresa y que estamos orgullosos de soportar en Kinsta. Esperamos que estos consejos te ayuden a ponerte en marcha y a prepararte para escalar. ¿Otros consejos o reflexiones sobre tu experiencia? Háznoslo saber aquí.

Justin Gage

Justin is a technical writer and author of the popular Technically newsletter. He did his B.S. in Data Science before a stint in full-stack engineering and now focuses on making complex technical concepts accessible to everyone.