pg_repack compacta PostgreSQL en Mastodon, elimina filas muertas, recupera espacio y mejora el rendimiento.


Si administras una instancia de Mastodon, tarde o temprano te encontrarás con un fenómeno bastante típico en PostgreSQL: la base de datos crece más de lo que "deberia" y acabas recibiendo avisos de Zabbix de límite de disco. Esto ocurre porque, cuando se actualizan filas (por ejemplo en la tabla statuses), PostgreSQL no "borra" físicamente la fila antigua en el mismo momento, sino que la marca como obsoleta (dead row). Con el tiempo, esas filas muertas se acumulan, ocupan espacio en disco y además pueden degradar el rendimiento, porque el motor tiene que navegar por más páginas y más basura interna para responder a las consultas.

pg_repack sirve precisamente para esto: reescribe tablas e índices compactándolos, eliminando el espacio desperdiciado por filas muertas y devolviendo espacio real al sistema de archivos. Dicho de forma simple, pg_repack crea una copia limpia de la tabla (sin el desperdicio), intercambia la tabla antigua por la nueva y repite el proceso tabla por tabla. La consecuencia práctica es clara: puedes recuperar gigas y mejorar la salud general de la base de datos.

Antes de empezar: lo más importante es el espacio libre

Aquí viene el punto crítico, y conviene tomárselo en serio: pg_repack no es una herramienta para usar cuando ya estás a cero de disco. Como el proceso necesita crear estructuras nuevas mientras la tabla original aún existe, necesitas tener suficiente espacio libre durante la operación. Una regla de seguridad bastante razonable es esta: si tu espacio libre es menor que el tamaño de tu tabla más grande, es muy probable que tengas problemas.

Por eso, antes de tocar nada, tenemos que comprobar dos cosas: cuánto disco libre tienes y cuánto ocupa la tabla más grande.

Entra por SSH al servidor de Mastodon y ejecuta el siguiente comando:

df -h

Quédate con el valor de "Avail" en la partición donde está el sistema (normalmente /) o donde resida PostgreSQL. Si ves que tienes, por ejemplo, 40 GB libres, aún no cantes victoria: falta comparar con el tamaño de la tabla más grande.

Ahora abre psql contra la base de datos de producción de Mastodon. En la mayoría de instalaciones se llama mastodon_production:

psql -d mastodon_production

Dentro de psql, ejecuta esta consulta para listar tablas ordenadas por tamaño total (tabla + índices), de mayor a menor:

SELECT
  table_schema || '.' || table_name AS table_full_name,
  pg_size_pretty(pg_total_relation_size('"' || table_schema || '"."' || table_name || '"')) AS size
FROM information_schema.tables
ORDER BY
  pg_total_relation_size('"' || table_schema || '"."' || table_name || '"') DESC;

La primera fila del resultado te dirá cuál es la tabla más grande (en Mastodon suele ser public.statuses). En mi caso esa tabla ocupa 37 GB, por lo tanto, necesito tener libres 40GB libres en disco como mínimo.

💡
Si tu espacio libre es menor, no continúes: primero libera espacio borrando logs antiguos, backups viejos, archivos temporales olvidados, o moviendo datos a otro volumen. Ejecutar pg_repack sin margen puede empeorar la situación.

Si no sabes como liberar espacio, revisa este artículo que publicamos hace un tiempo que te da algunos trucos para liberar algo de espacio en disco.

Liberar espacio en tu servidor Cloud VPS
El otro día en mi servidor de producción, donde realmente tengo este blog funcionando, me llegaron unas notificaciones al correo informándome de que la partición /dev/sda1 se estaba acercando al 80% de espacio ocupado. Esto lo supe gracias a un programa de monitorización llamado Munit. Seguramente de este programa

Cuando termines, sal de psql:

\q

Instalación de pg_repack en Debian/Ubuntu

Asumiendo un despliegue típico en Debian/Ubuntu, la instalación es directa desde repositorios, pero debes instalar el paquete que corresponda con la versión de PostgreSQL que uses.

Instala el paquete de pg_repack para tu versión de PostgreSQL. Por ejemplo, si usas PostgreSQL 15:

apt install postgresql-15-repack

Si estás en PostgreSQL 16, el paquete sería el equivalente para 16:

apt install postgresql-16-repack

Crea copias de seguridad

Antes de continuar con este procedimiento, asegurate de tener una copia de seguridad de la base de datos. Si no tienes espacio suficiente en disco para hacer una copia de la base d e datos, comprueba que tu proveedor de hosting tenga opción de backup del servidor o la opción de crear un snapshot completo por si algo no fuera bien poder volver a un estado anterior.

💡
De verdad, haz un backup!

Activar la extensión dentro de la base de datos

Instalar el paquete no basta: pg_repack usa una extensión que debe estar habilitada en la base de datos. Para hacerlo, cambia al usuario postgres:

su - postgres

Entra en la base de datos:

psql -d mastodon_production

Y crea la extensión:

CREATE EXTENSION pg_repack;

Con esto ya tienes la parte de PostgreSQL lista.

Ejecutar pg_repack con seguridad: primero haz una simulación

pg_repack debe ejecutarse como superusuario de PostgreSQL (por eso estabas como postgres). Lo recomendable es empezar con un dry run para comprobar que todo está bien configurado y ver qué pretende hacer sin tocar nada. Ejecuta:

pg_repack -d mastodon_production --dry-run

Este comando no repaqueta nada; simplemente te muestra qué tablas se procesarían y valida que puede conectarse, que la extensión está disponible, etc. Si aquí falla, es mejor corregir el problema antes de hacer un repack real.

Salida del comando

$ pg_repack -d mastodon --dry-run
INFO: Dry run enabled, not executing repack
INFO: repacking table "public.account_aliases"
INFO: repacking table "public.account_conversations"
INFO: repacking table "public.account_deletion_requests"
INFO: repacking table "public.account_domain_blocks"
INFO: repacking table "public.account_migrations"
INFO: repacking table "public.account_moderation_notes"
INFO: repacking table "public.account_notes"
INFO: repacking table "public.account_pins"
INFO: repacking table "public.account_relationship_severance_events"
INFO: repacking table "public.accounts"
INFO: repacking table "public.accounts_tags"
INFO: repacking table "public.account_stats"
INFO: repacking table "public.account_statuses_cleanup_policies"
INFO: repacking table "public.account_warning_presets"
INFO: repacking table "public.account_warnings"
INFO: repacking table "public.admin_action_logs"
INFO: repacking table "public.announcement_mutes"
INFO: repacking table "public.announcement_reactions"
INFO: repacking table "public.announcements"
INFO: repacking table "public.annual_report_statuses_per_account_counts"
INFO: repacking table "public.appeals"
INFO: repacking table "public.ar_internal_metadata"
INFO: repacking table "public.backups"
INFO: repacking table "public.blocks"
INFO: repacking table "public.bookmarks"
INFO: repacking table "public.bulk_import_rows"
INFO: repacking table "public.bulk_imports"
INFO: repacking table "public.canonical_email_blocks"
INFO: repacking table "public.conversation_mutes"
INFO: repacking table "public.conversations"
INFO: repacking table "public.custom_emoji_categories"
INFO: repacking table "public.custom_emojis"
INFO: repacking table "public.custom_filter_keywords"
INFO: repacking table "public.custom_filters"
INFO: repacking table "public.custom_filter_statuses"
INFO: repacking table "public.domain_allows"
INFO: repacking table "public.domain_blocks"
INFO: repacking table "public.email_domain_blocks"
INFO: repacking table "public.fasp_backfill_requests"
INFO: repacking table "public.fasp_debug_callbacks"
INFO: repacking table "public.fasp_follow_recommendations"
INFO: repacking table "public.fasp_providers"
INFO: repacking table "public.fasp_subscriptions"
INFO: repacking table "public.favourites"
INFO: repacking table "public.featured_tags"
INFO: repacking table "public.follow_recommendation_mutes"
INFO: repacking table "public.follow_recommendation_suppressions"
INFO: repacking table "public.follow_requests"
INFO: repacking table "public.follows"
INFO: repacking table "public.generated_annual_reports"
INFO: repacking table "public.identities"
INFO: repacking table "public.instance_moderation_notes"
INFO: repacking table "public.invites"
INFO: repacking table "public.ip_blocks"
INFO: repacking table "public.list_accounts"
INFO: repacking table "public.lists"
INFO: repacking table "public.login_activities"
INFO: repacking table "public.markers"
INFO: repacking table "public.media_attachments"
INFO: repacking table "public.mentions"
INFO: repacking table "public.mutes"
INFO: repacking table "public.notification_permissions"
INFO: repacking table "public.notification_policies"
INFO: repacking table "public.notification_requests"
INFO: repacking table "public.notifications"
INFO: repacking table "public.oauth_access_grants"
INFO: repacking table "public.oauth_access_tokens"
INFO: repacking table "public.oauth_applications"
INFO: repacking table "public.pghero_space_stats"
INFO: repacking table "public.polls"
INFO: repacking table "public.poll_votes"
INFO: repacking table "public.preview_card_providers"
INFO: repacking table "public.preview_cards"
INFO: repacking table "public.preview_cards_statuses"
INFO: repacking table "public.preview_card_trends"
INFO: repacking table "public.quotes"
INFO: repacking table "public.relationship_severance_events"
INFO: repacking table "public.relays"
INFO: repacking table "public.report_notes"
INFO: repacking table "public.reports"
INFO: repacking table "public.rules"
INFO: repacking table "public.rule_translations"
INFO: repacking table "public.scheduled_statuses"
INFO: repacking table "public.schema_migrations"
INFO: repacking table "public.session_activations"
INFO: repacking table "public.settings"
INFO: repacking table "public.severed_relationships"
INFO: repacking table "public.site_uploads"
INFO: repacking table "public.software_updates"
INFO: repacking table "public.status_edits"
INFO: repacking table "public.statuses"
INFO: repacking table "public.statuses_tags"
INFO: repacking table "public.status_pins"
INFO: repacking table "public.status_stats"
INFO: repacking table "public.status_trends"
INFO: repacking table "public.tag_follows"
INFO: repacking table "public.tags"
INFO: repacking table "public.tag_trends"
INFO: repacking table "public.terms_of_services"
INFO: repacking table "public.tombstones"
INFO: repacking table "public.unavailable_domains"
INFO: repacking table "public.user_invite_requests"
INFO: repacking table "public.username_blocks"
INFO: repacking table "public.user_roles"
INFO: repacking table "public.users"
INFO: repacking table "public.webauthn_credentials"
INFO: repacking table "public.webhooks"
INFO: repacking table "public.web_push_subscriptions"
INFO: repacking table "public.web_settings"

Si el resultado te cuadra, ejecuta el repack real. Para ser más "amable" con una instancia en producción y evitar que mate conexiones, puedes añadir --no-kill-backend, que le dice a pg_repack que no intente terminar otras sesiones concurrentes. En ese modo, si se encuentra bloqueos o conflictos, preferirá abortar él mismo antes que echar a otros:

pg_repack -d mastodon_production --no-kill-backend

Durante el proceso es normal que haya consumo de CPU y cierta actividad de disco. En una instancia activa puedes notar un rendimiento algo peor mientras dura la operación. Aun así, bien planificado, suele merecer la pena si vas justo de disco o si quieres mejorar el estado de tablas grandes.

Programarlo para mantenimiento periódico (cron)

Si tu instancia crece y está viva, esto no es una vez y ya, sino un mantenimiento que conviene repetir. Puedes programarlo con cron para que se ejecute automáticamente, por ejemplo una vez al mes. Lo ideal es hacerlo en horas de baja actividad.

Asegúrate de estar como usuario postgres y edita su crontab:

su - postgres
crontab -e

Si el editor te pregunta, puedes elegir nano si quieres algo simple. Añade una línea como esta para ejecutarlo el día 1 de cada mes a medianoche:

0 0 1 * * pg_repack -d mastodon_production --no-kill-backend

Guarda y cierra el editor. A partir de ese momento, el repack se ejecutará de forma automática con esa periodicidad.

💡
Es importante que para añadir este cron, es necesario tener siempre espacio libre en disco, porque si durante el proceso te quedas sin espacio el servidor puede colgarse.

Conclusión

Cuando pg_repack termina, deberías ver dos beneficios claros: recuperación de espacio real en disco (especialmente si había mucha basura acumulada) y, en muchos casos, una mejora de rendimiento en consultas que sufrían por bloat, porque hay menos páginas que leer y menos trabajo para llegar a los datos válidos.


Más sobre ./voidNull

Haz que cada palabra cuente: tu donación nos inspira a seguir creando contenido. Accede al apartado de Donación para hacer tu aportación