5 puntos por GN⁺ 2025-06-07 | 1 comentarios | Compartir por WhatsApp
  • GitLab detectó un problema de lentitud al respaldar repositorios de gran tamaño y realizó mejoras para resolverlo
  • La causa principal era la complejidad O(N²) de una función de Git con 15 años de antigüedad, y mediante una optimización del algoritmo logró mejorar drásticamente el rendimiento
  • Como resultado de la optimización, el tiempo de respaldo del repositorio más grande se redujo de 48 horas a 41 minutos
  • El método mejorado ofrece eficiencia de recursos y confiabilidad, y al mismo tiempo tiene un impacto positivo en otras herramientas basadas en Git y en la comunidad
  • A partir de GitLab 18.0, todos los usuarios podrán obtener estos beneficios sin configuración adicional

La importancia y los desafíos del respaldo de repositorios

  • El respaldo de repositorios es un elemento clave de una estrategia de recuperación ante desastres
  • A medida que crece el tamaño del repositorio, aumenta la complejidad de contar con un proceso de respaldo confiable
  • El propio repositorio de Rails de GitLab tardaba 48 horas en respaldarse, lo que obligaba a elegir entre la frecuencia de los respaldos y el rendimiento del sistema
  • En los repositorios a gran escala existen diversos problemas relacionados con tiempo, recursos, riesgo de fallos y condiciones de carrera
    • Esto llevaba a estrategias poco consistentes entre organizaciones, como dificultad para programar respaldos, dependencia de herramientas externas o reducción en la cantidad de respaldos

Análisis del cuello de botella de rendimiento e identificación de la causa

  • La función de respaldo de GitLab genera instantáneas del repositorio usando el comando git bundle create
  • En la implementación de este comando surgía un cuello de botella de rendimiento a medida que aumentaba la cantidad de references (referencias)
  • En algunos casos, respaldar repositorios grandes con millones de referencias tomaba más de 48 horas

Análisis de la causa

  • Se identificó la zona del cuello de botella mediante análisis con Flame Graph durante la ejecución del comando
  • Con 10,000 referencias, alrededor del 80% del tiempo de ejecución se consumía en la función object_array_remove_duplicates()
  • Esta función fue introducida en 2009 en este commit para procesar referencias duplicadas
    • Evitaba problemas cuando se incluían referencias duplicadas al usar git bundle create
    • Sin embargo, la detección de duplicados estaba implementada con bucles for anidados, lo que generaba una complejidad O(N²)
    • Cuantas más referencias había, más severo se volvía el cuello de botella

De O(N²) a un mapeo eficiente

  • GitLab contribuyó a Git con un parche que usa una estructura de datos tipo mapa en lugar de bucles anidados
    • Cada referencia se agrega al mapa, eliminando duplicados automáticamente y procesándolos de forma eficiente
  • Con este cambio, el rendimiento de git bundle create mejoró de forma drástica y ahora puede escalar incluso en entornos con grandes volúmenes de referencias
  • Según los benchmarks, con 100,000 referencias se confirmó una mejora de rendimiento de más de 6 veces

Reducción drástica del tiempo de respaldo y sus efectos

  • El tiempo máximo de respaldo del repositorio se redujo de 48 horas a 41 minutos (hasta un 1.4% del tiempo original)
  • Ahora es posible ofrecer un rendimiento consistente sin importar el tamaño del repositorio
  • También se obtuvieron beneficios adicionales, como menor carga del servidor y mejor rendimiento general en comandos de Git relacionados con respaldos
  • El parche correspondiente se aplicó en upstream Git, y GitLab lo adoptó de inmediato para que los clientes pudieran experimentar la mejora cuanto antes

Cambios reales para los clientes de GitLab

  • Los clientes empresariales ahora pueden ejecutar respaldos nocturnos consecutivos sin conflictos con el flujo de trabajo de desarrollo
  • Gracias a la reducción del objetivo de punto de recuperación (RPO), el riesgo de pérdida de datos en situaciones de desastre se redujo de forma importante
  • También disminuyeron el consumo de recursos, el tiempo de mantenimiento y los costos operativos, incluidos los costos en la nube
  • Incluso si el tamaño del repositorio sigue creciendo, ya no es necesario preocuparse por elegir entre la frecuencia de respaldo y el rendimiento del sistema
  • Ahora todos los usuarios de GitLab pueden disfrutar de estos beneficios sin cambios de configuración

Próximos pasos y significado

  • Esta mejora forma parte del esfuerzo continuo de GitLab por construir una infraestructura Git empresarial altamente escalable
  • Los cambios aportados por GitLab también se reflejaron en el proyecto Git upstream, generando un efecto positivo en toda la industria y la comunidad de código abierto
  • Este tipo de mejoras de infraestructura se está convirtiendo en un modelo para otros trabajos clave de optimización de rendimiento

Una historia más profunda sobre el enfoque de rendimiento de GitLab puede verse en el evento de lanzamiento virtual de GitLab 18

1 comentarios

 
GN⁺ 2025-06-07
Comentarios en Hacker News
  • Se comparte la información de que el código de mejora de rendimiento que GitLab aportó a Git está previsto para lanzarse en la v2.50.0 enlace al commit relacionado

  • A partir de experiencia directa, se enfatiza que eliminar operaciones n^2 en código escrito por uno mismo siempre resultó ser la decisión correcta. También sorprende lo fácil que estos problemas salen a la luz incluso con valores de n muy pequeños, sin necesidad de escribir algoritmos especiales

    • Se cita la primera ley de la computación de Bruce Dawson: O(n^2) es el punto donde surgen los peores problemas de escalabilidad. En producción parece lo bastante rápido, pero una vez desplegado provoca una degradación de rendimiento letal enlace al texto relacionado

    • Se comparte la experiencia personal de haber visto muchas veces casos donde la complejidad temporal O(N^2) parecía rápida en pruebas, pero explotaba gravemente en producción

    • Según su experiencia, en la mayoría de los problemas (80~90%), si hace falta un algoritmo complejo, eso es señal de que el propio modelo de datos no es el correcto. Considera que solo en algunos casos especiales, como compiladores, internals de bases de datos o planificación de rutas, los algoritmos complejos son intrínsecamente necesarios

    • Se menciona como única excepción los casos limitados a hardware pequeño con n menor que 10 (como interfaces CAN). Si n puede crecer sin cambiar el hardware, hay que evitar sí o sí las operaciones n^2, y se recomienda imponer límites de diseño o detectar el problema de antemano para reconocer la necesidad de rediseñar

    • Expresa la frustración de estar lidiando con una operación n^3 que ya genera un cuello de botella con apenas 10,000 elementos, sin haber encontrado todavía una solución

  • Se evalúa como un hallazgo interesante, aunque también se comenta con algo de pesar que el artículo habría comunicado igual de bien si hubiera tenido solo una décima parte de su longitud. Aun así, se menciona como punto positivo que, al no ser contenido en video, fue fácil de hojear

    • Como tip para quienes aún no leyeron el artículo, se sugiere leer solo hasta después del flame graph y detenerse antes de la mención del backport, ya que esa es la forma más eficiente de captar lo esencial

    • Se comenta que el estilo completo del artículo se sintió como si lo hubiera generado un LLM (modelo de lenguaje grande), y que eso fue lo más memorable

    • Se dice que no habría molestado aunque el artículo fuera todavía más largo, y que sigue quedando la duda de por qué el bundle de respaldo se hizo con dos refs

  • Se percibe como mucho tiempo tanto las 48 horas para comprimir la carpeta de git (varios GB) como incluso los 41 minutos. Se cuestiona si hay alguna razón especial para usar git bundle en vez de simplemente hacer un snapshot/archivo del repo completo, y qué ventaja tendría git bundle frente a respaldos frecuentes con ZFS

    • En el FAQ oficial de git se advierte que este método es riesgoso porque omite los procedimientos normales de verificación de integridad de Git. En estos casos se recomienda git fsck para validar la integridad de la colección. A nivel personal, con Syncthing y snapshots de Btrfs ya suele bastar por velocidad y estabilidad. documentación relacionada

    • Se menciona que hacer respaldos offsite de snapshots de ZFS hacia destinos no basados en ZFS, como S3, tiene limitaciones. Como función menos conocida de git bundle, se puede especificar una ubicación en git clone --bundle-uri, y si el servidor informa al cliente dónde está el bundle, el cliente puede descargarlo, descomprimirlo rápidamente y luego recibir del servidor solo las actualizaciones delta, reduciendo mucho la carga al clonar repos grandes

    • En resumen, parece que simplemente se añadió caché donde hacía falta. Dado que los repos de git son sistemas distribuidos, surge la duda de si no bastaría con espejarlos en otro repositorio y administrarlos con herramientas de snapshot/respaldo del sistema de archivos. Se enfatiza que la información crítica de control de versiones debe estar distribuida

  • No tiene mucha experiencia haciendo respaldos de git, pero le resulta curioso por qué surgiría una race condition al crear el respaldo directamente desde un repo local

  • Si esto se vuelve tan complicado por culpa del protocolo de datos de capas superiores, se cuestiona por qué no usar snapshots a nivel de bloque. El hecho de que Git no tenga algo como WAL (Write Ahead Logging) es un obstáculo, pero con SQLite se aprovecha en entornos reales una estrategia de snapshots de bloque de forma bastante simple solo agregando modo WAL. Se piensa que, si Git adoptara una arquitectura así, sería posible una estrategia de respaldo mucho más confiable

    • Se coincide en que la falta de un mecanismo similar a WAL en Git causa este tipo de problemas, y se comparte una experiencia crítica donde confiar solo en snapshots llevó a que el repositorio quedara roto al restaurarlo. Se añade que es recuperable, pero extremadamente engorroso

    • Se comparte como dato que recientemente apareció una mejor solución para SQLite: sqlite3_rsync

    • Se explica la perspectiva de GitLab: no es solo un servicio administrado simple, sino un producto que puede instalarse por cuenta propia y usarse en entornos muy diversos, por lo que cada usuario tiene condiciones distintas de sistema de archivos, soporte de snapshots y sistema operativo. Es decir, GitLab quiere un sistema de respaldo independiente que funcione de forma general en todos esos entornos

  • Al ver la expresión "redujo exponencialmente el tiempo de respaldo con un cambio de algoritmo", surge la duda de si eso significaría haber pasado de O(n^2) a O(n^2/2^n). Se sospecha que en realidad no puede ser eso

    • Se explica que la complejidad algorítmica de la función modificada en realidad se volvió 6 veces más rápida, y que en su contexto de uso el tiempo operativo total cayó al 1%, así que en este caso "mejora exponencial" podría interpretarse como una forma de marketing razonable. La clasificación exacta de complejidad no tendría tanta importancia aquí

    • Se aclara que en la conversación cotidiana "exponential" no se usa con un sentido matemático estricto, sino más bien como una forma figurada de decir "mejoró muchísimo"

    • Según su propia interpretación, podría ser que n se redujera hasta log(n). Se menciona una situación parecida a cuando suele decirse que la transformada cuántica de Fourier es exponencialmente más rápida que la DFT tradicional, como analogía para un cambio de complejidad de n^2 a nlogn

    • Si se reemplaza un algoritmo n^2 por uno con búsquedas log(n), sí podría decirse que la mejora es exponencial, pero en la práctica muchas veces se llega incluso a O(1), como con búsquedas en hashmap, así que sería aún más rápido

    • Se opina que toda esta discusión es una objeción improductiva

  • Se comenta que este es un buen ejemplo de que escribir en C no garantiza un gran rendimiento por sí solo, y que las estructuras de datos y los algoritmos importan más

    • Como en C es muy difícil implementar contenedores adecuados, este tipo de problemas de rendimiento aparece con más frecuencia. En C++ o Rust, gracias a estructuras integradas como unordered_set/HashSet, los desarrolladores tienden a optimizar de manera más natural en lugar de dejarlo pasar con un simple bucle for. En este caso también hay un string set en Git, pero como no es algo estándar, se analiza que el autor original quizá ni supiera que existía
  • Se menciona como lección la necesidad de encontrar un equilibrio entre la optimización prematura y la optimización anticipatoria. En general se advierte contra optimizar demasiado pronto, pero se propone como regla aplicar por adelantado optimizaciones obvias y fáciles en funciones que se llaman con muchísima frecuencia

    • Se supone que, si el lenguaje fuente hubiera tenido incorporado un set-of-strings cuando se implementó, el desarrollador original habría podido aplicar fácilmente esta optimización. Al final, se opina que este problema proviene de una limitación estructural de lenguajes pobres en contenedores como C
  • Se comparte directamente el enlace al commit relacionado (mejora del algoritmo) enlace al commit relacionado