NubliVaultby Nublify
Blog

· 7 min de lectura

Respaldamos 510 mil archivos y la base de datos no creció ni una fila

Una prueba de escala con números, método y límites declarados — incluido lo que no funcionaría en tu caso.

Quien respalda almacenamiento de objetos con muchos archivos pequeños choca con dos costos, y el segundo es el que arruina el mes: el dinero que se va en solicitudes, y la base de datos que crece junto con la cantidad de archivos hasta convertirse en el cuello de botella.

Este texto es el relato de una prueba que hicimos para resolver el segundo problema. Tiene los números, el método y — la parte que más importa — lo que no funcionaría en otro contexto.

El problema del índice

Para recuperar un archivo individual dentro de un paquete archivado hay que saber en qué paquete está y en qué posición. Ese índice tiene una entrada por archivo. Con 510 mil archivos son 510 mil entradas. Con mil millones, mil millones.

La salida obvia es una tabla en la base de datos. Funciona, y la usamos durante bastante tiempo: con Postgres bien dimensionado medimos 16 a 17 mil inserciones por segundo de forma estable hasta 30 millones de filas. El detalle que casi se nos escapa es que la estabilidad dependía de la RAM — con 15 GB la tasa se degradaba a lo largo de la carga; con 32 GB se mantenía constante. Es decir: funciona, pero te obliga a hacer crecer la base junto con el acervo del cliente, para siempre.

La alternativa que probamos fue sacar el índice de la base de datos. El manifiesto de cada paquete pasa a ser un objeto en el propio almacenamiento, al lado de los datos, y la base guarda solo lo que es pequeño y cambia poco: clientes, trabajos, paquetes. La búsqueda lee el manifiesto bajo demanda y mantiene una caché local.

Lo que se midió

Una VM de 4 vCPU y 32 GB de RAM en Oracle Cloud, leyendo de buckets OCI y escribiendo en AWS. Sin simulación en ninguna punta — origen real, destino real.

Masa sintética de 510 mil archivos de ~150 KB, distribuida en tres clientes y tres buckets, con uno de ellos repartido a propósito entre dos buckets distintos, para probar el aislamiento.

Empaquetado

Archivos procesados 510.000
Paquetes generados 16
Fallos 0
Cliente de 300 mil archivos 40min52s (~122 archivos/s)
Cliente de 150 mil archivos 21min (~118 archivos/s)

La base de datos durante todo eso

CPU de Postgres 0,06%
Filas en la tabla de índice 1.227, antes y después
Tamaño de la tabla 16 MB, antes y después

Ese es el número del título. Medio millón de archivos entraron en la bóveda y la tabla de índice no recibió ni una fila. La base quedó prácticamente parada mientras la máquina empaquetaba, comprimía y subía.

Recuperación

Sacar el índice de la base solo vale la pena si la recuperación sigue siendo rápida. Medimos los dos casos:

  • Un archivo específico, dentro de un cliente con 300 mil archivos y ninguna fila en la base: respuesta en segundos.
  • Cliente entero: 30.000 de 30.000 archivos en 2min20s, unos 260 archivos por segundo, todos confirmados en el destino.

Ensayo de desastre

La prueba que interesa no es el respaldo, es el día en que el origen ya no existe. Restauramos solo a partir del archivo en AWS, sin tocar la nube de origen, y comparamos el sha256 de cada archivo restaurado con el checksum grabado en el manifiesto. Idénticos, bit a bit.

Cuando un servidor muere a mitad de camino

El empaquetado corre en una flota de máquinas prescindibles (preemptible) — las que la nube puede apagar en cualquier momento, y que cuestan una fracción del precio. Matamos dos a propósito durante la carga. El vigía lo detectó y lanzó reemplazos en 35 segundos, y el trabajo siguió desde donde estaba.

Hasta dónde escala esto

A ~120 archivos por segundo por worker, mil millones de archivos llevarían cerca de 96 días con un worker, o 10 días con diez. En una campaña aparte, con 1 TB y ~7 millones de archivos, medimos ~110 archivos/s por proceso y ~330/s con cuatro procesos en la misma máquina de 4 núcleos — el cuello de botella ahí es CPU en la concatenación, no red ni disco.

Lo que esto no demuestra

Esta es la parte que suele faltar en los benchmarks publicados, y sin ella los números de arriba valen poco.

Los archivos eran sintéticos y de tamaño parecido (~150 KB). Un acervo real tiene distribución irregular, y los archivos muy grandes cambian el comportamiento del empaquetado. Espera números distintos.

Sacar el índice de la base tiene un precio, y aparece en la primera búsqueda. Si el manifiesto no está en la caché local, hay que traerlo del almacenamiento antes de responder. Con caché fría, la primera consulta es más lenta que un SELECT. En una revisión de código de este mismo trabajo encontramos un defecto exactamente ahí — la búsqueda con caché fría no rehidrataba el manifiesto — que fue corregido y revalidado antes de dar la prueba por válida. Si tu patrón de uso es búsqueda constante y dispersa, la tabla en la base puede ser la elección correcta.

La tasa por worker es la que es. ~120 archivos/s no es rápido en términos absolutos; la ganancia viene de correr muchos workers baratos en paralelo, no de que cada uno sea veloz. Si necesitas una ventana corta con pocos nodos, esta arquitectura no te sirve.

No medimos costo en dinero en esta prueba. El ahorro en solicitudes viene de la agregación — 510 mil objetos se convirtieron en 16 — y esa cuenta es aritmética directa, pero no es lo que esta prueba midió. No confundas una cosa con la otra.

Cómo reproducirlo

El camino es el mismo para cualquier herramienta que agregue archivos en paquetes:

  1. Genera masa sintética con tamaño y cantidad conocidos, en más de un bucket, con al menos un cliente repartido entre dos — es donde aparecen las fallas de aislamiento.
  2. Mide la base antes y después: conteo de filas, tamaño en disco y CPU durante la carga. Sin el "antes", el "después" no significa nada.
  3. Prueba la recuperación en los dos extremos: un solo archivo y el acervo entero.
  4. Haz el ensayo de desastre apagando el origen, y compara checksums. Un respaldo que nunca se restauró no es un respaldo.
  5. Mata un nodo a mitad de la carga, a propósito.

El paso 5 es el que casi nadie hace, y es el que separa un número de laboratorio de una arquitectura que aguanta producción.


Esta prueba se hizo con NubliVault, de Nublify. Los números son de nuestro laboratorio, no de un cliente — justamente para que puedan ser cuestionados y rehechos. Si corres algo parecido y llegas a números distintos, queremos saberlo.