Restic: Comparación de enfoques para abordar el uso de memoria de índice

Creado en 20 dic. 2019  ·  55Comentarios  ·  Fuente: restic/restic

NOTA PARA LOS REVISORES DEL CÓDIGO / CONTRIBUYENTES PRINCIPALES

Lea este comentario para obtener un resumen de los cambios e información estructurada sobre cada uno de esos cambios. Este podría ser un mejor punto de partida que leer todos los comentarios de este número.

Describa el problema

Como se indicó en #1988, el consumo de memoria del índice es uno de los mayores problemas con el consumo de memoria de restic. El punto principal es la implementación real del almacenamiento de índices en memoria en internal/repository/index.go

Por lo tanto, me gustaría comenzar un nuevo número para discutir estrategias que podrían remediar el alto consumo de memoria.

Como punto de partida abrí una rama index-alternatives que implementa las siguientes estrategias:

  • Estándar: índice como se usa realmente en restic
  • Recargar: No guarde nada. Para cualquier operación de índice, vuelva a cargar el archivo y use la implementación real para el índice creado temporalmente.
  • Índice de memoria baja: almacene solo datos de índice completos para blobs de árbol. Para los blobs de datos, use solo un IDSet para guardar qué blobs de datos están presentes. Esto permite la mayoría de las operaciones de índice. Para los que faltan, haga lo mismo que en Recargar (al menos para los blobs de datos)
  • Bolt: use una base de datos en el disco a través de bbolt

La estrategia del índice puede ser elegida por
restic -i <default|reload|low-mem|bolt>

La sucursal se considera WIP. Agregué una interfaz general FileIndex que debería cumplirse con nuevas estrategias de índice. Además, la implementación real solo carga archivos de índice (archivo por archivo) en el estándar Index struct y luego los vuelve a cargar en una nueva estructura de datos que permite que el GC se limpie. Los archivos de índice recién creados no se ven afectados, por lo que solo debería haber un efecto para los repositorios que contienen datos.

Tenga en cuenta que la mayoría de las implementaciones son rápidas y sucias y pierden el manejo de errores graves, la limpieza, etc.

Se pueden agregar fácilmente más estrategias de índice agregando una nueva implementación para FileIndex y agregándolas en repository.go (donde se realiza la carga) y cmd/restic/global.go (donde la bandera -i se interpreta)

Agradezco recibir comentarios sobre las estrategias de indexación propuestas.
En esta tarea también me gustaría recopilar buenas configuraciones de prueba donde se puedan comparar diferentes estrategias de índice entre sí.

optimization

Comentario más útil

@rawtaz ¡ Gracias por tu respuesta!

Tienes razón en que durante el desarrollo traté de moverme rápido y ser capaz de probar qué cosas funcionan y qué no. Por ahora, parece que el cambio de código es lo suficientemente maduro como para discutir cómo se puede integrar mejor en el maestro.

Creo que ayuda a la discusión dar una visión general de los cambios que he hecho y las razones por las que hice cada cambio. La implementación cubre los siguientes cambios:

Cambios principales:

  • la estructura del código base, es decir, qué funcionalidad reside en internal/repository y internal/index
  • estructura de definiciones de interfaz en internal/restic
  • estructura interna de MasterIndex, incluido el manejo de archivos de índice "completos"
  • la estructura de datos relacionada con el archivo de índice
  • soporte para archivos de índice de formato antiguo ( index_old.go )
  • la nueva estructura de datos del índice en memoria ( index_new.go )
  • cambio de índice de acceso donde sea necesario

Cambios adicionales menores:

  • cambió Lookup y agregó LookupAll
  • reemplazado 'Tienda' por StorePack EDITAR: se implementa en #2773
  • agregar CheckSetKnown para manejar blobs conocidos durante la copia de seguridad EDIT: se implementa en #2773
  • elimine el blob IDSet de checker.go y reemplácelo por la funcionalidad de índice
  • use la nueva funcionalidad de índice en 'list blobs'

Los principales cambios son, en mi opinión, la clave para lograr un bajo consumo de memoria, un buen rendimiento y un diseño de código más limpio (en comparación con la implementación real). Los cambios menores son ganancias rápidas que también podrían ser relaciones públicas independientes. Sin embargo, si se supone que son buenas mejoras, preferiría mantenerlas en el PR principal, de lo contrario, probablemente deban implementarse dos veces o deban posponerse.

Estructura del código base

Moví la mayoría de las funciones relacionadas con el índice de internal/repository a repository/index . En mi opinión, esto hace que la estructura base del código sea más clara y también separa claramente qué código está en qué directorio (a diferencia de la implementación real.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Estructura de la interfaz

También separé las definiciones de interfaz en internal/restic para que quede claro qué está relacionado con Index y qué con Repository . Esto también permite cambios más claros de las interfaces en el futuro.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Estructura de MasterIndex

Actualmente, MasterIndex combina estructuras de datos relacionadas con archivos de índice y, por lo general, itera sobre todas estas subestructuras para operaciones de índice. Cada una de las estructuras de datos relacionadas con el archivo de índice puede estar llena (es decir, lo suficientemente llena como para guardarla como un archivo de índice completo en el repositorio) o terminada (es decir, ya presente como archivo de índice).

Cambié completamente este comportamiento. Ahora MasterIndex usa un índice principal en memoria donde la mayoría de las entradas del índice deberían estar presentes, especialmente todas aquellas que también están presentes como archivos de índice en el repositorio, y una segunda estructura de datos "inacabada" que solo se usa para guardar un nuevo índice. entradas y se inserta en el índice principal en memoria una vez que el contenido se escribe como archivo de índice en el repositorio.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Estructura de datos relacionada con el archivo de índice

Actualmente, hay tres definiciones de estructuras de datos relacionadas con el archivo de índice: una definida por JSON en internal/index , una definida por JSON en internal/repository y una optimizada para búsqueda en internal/repository .
Decidí usar la estructura de datos JSON de internal/index y agregué los métodos necesarios para usarla en MasterIndex (y eliminé por completo las implementaciones de internal/repository ). Esto elimina por completo la conversión de estructuras de datos internas para guardar archivos de índice durante la copia de seguridad. Por otro lado, las operaciones de búsqueda deben recorrer todas las entradas del índice. Por lo tanto, es fundamental que esta estructura de datos no contenga muchas entradas durante la búsqueda, es decir, se inserta periódicamente en el índice principal.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Compatibilidad con el formato de archivo de índice antiguo

La compatibilidad con nuevos formatos de archivo de índice ahora se subcontrata a index_old_format.go .

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Nueva estructura de datos de índice en memoria ( index_new.go )

Este es obviamente el mayor cambio. En principio, se mantiene una gran lista de blobs por tipo de blob. La lista está ordenada por ID de blob, de modo que la búsqueda se puede realizar mediante la búsqueda binaria. Dado que la optimización apunta a muchas entradas en esta lista, el tamaño de cada entrada se minimiza tanto como sea posible. Esto significa que la identificación del paquete se almacena en una tabla separada y solo se guarda una referencia y el desplazamiento y la longitud son uint32 (limitando efectivamente el tamaño máximo del paquete a 4 GB). EDITAR: estas dos optimizaciones se implementan en #2781.
Una matriz ordenada simple ya ahorra la sobrecarga de memoria que se encuentra dentro de map pero aún genera cierta sobrecarga de memoria y rendimiento dentro append : se reserva un "búfer" adicional para adiciones futuras y los datos deben ser copiado. Por lo tanto, implementé una matriz "paginada" con una sobrecarga de memoria pequeña y limitada y sin necesidad de copiar para agregar.

Las pruebas que se han realizado indican que este índice es un poco más rápido que la implementación estándar de MasterIndex y ahorra más del 75% de la memoria.

Pregunta a los mantenedores: ¿La implementación de este índice cumple con sus expectativas?

Cambió Lookup y agregó LookupAll

Lookup en realidad devuelve más de un resultado (pero no si están distribuidos en muchos archivos de índice, por cierto). En muchos casos, solo se utiliza el primer resultado. Por lo tanto, cambié Lookup para devolver solo un resultado y agregué LookupAll para usar cuando se usa más de un resultado. Hace que el código sea un poco más claro y también ahorra algunos ciclos de CPU innecesarios.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Se reemplazó 'Tienda' por StorePack

Store almacena solo un blob en el índice, pero solo se llama para el contenido de un paquete completo. Por lo tanto, lo cambié a una funcionalidad de StorePack donde se guarda todo el contenido del paquete. Esto hace que el código sea más claro, el almacenamiento en formato JSON sea mucho más fácil y también más eficaz.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

agregue CheckSetKnown para manejar blobs conocidos durante la copia de seguridad

Las pruebas mostraron que para muchos archivos nuevos, backup usa bastante memoria para almacenar qué blobs se agregaron durante esta ejecución de copia de seguridad. Esto es para no guardar blobs dos veces mediante protectores paralelos. Moví esta funcionalidad al índice ya que esta información de "blob conocido" se puede eliminar una vez que se agrega la entrada correspondiente al índice. Con este cambio, solo se "conocen" muy pocos blobs pero no (todavía) en el índice.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Elimine el blob IDSet de checker.go y reemplácelo por la funcionalidad de índice

Checker crea su propio índice en memoria y, además, mantiene todos los ID de blob en un IDSet adicional. Como el índice ya conoce todos los blobs, esto se puede reemplazar por el método Each del índice.
Esto ahorra memoria adicional dentro check .

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Use la nueva funcionalidad de índice en 'blobs de lista'

En lugar de leer todo el índice en la memoria y luego recorrer todas las entradas para imprimir la lista de blobs, cada archivo de índice se puede cargar por separado en la memoria y recorrer estos contenidos. Hace que list blobs consuma menos memoria.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Todos 55 comentarios

Me gusta comenzar con la configuración que usé para probar durante la implementación:

Creé datos de prueba con el siguiente script

#!/bin/sh
mkdir data
for i in `seq 1 1000`; do
        echo $i
        mkdir data/$i
        for j in `seq 1 100`; do
                echo $i$j > data/$i/$j
        done
done

Esto crea 100.000 pequeños archivos diferentes en 1000 directorios. Por lo tanto, esto debería generar muchos blobs de datos y aún bastantes blobs de árbol. Se puede cambiar para simular aún más archivos.

Ejecuté la copia de seguridad inicial

restic -r /path/to/repo init
restic -r /path/to/repo backup /path/to/data

e hizo una copia de seguridad repetida con datos sin cambios para todas las estrategias de índice:

/usr/bin/time restic -r /path/to/repo -i <strategy> backup /path/to/data

además de medir con el tiempo, también agregué perfiles de memoria.
Aquí están los resultados:

Índice estándar

Files:           0 new,     0 changed, 100000 unmodified
Dirs:            0 new,     0 changed,     2 unmodified
Added to the repo: 0 B  

processed 100000 files, 567.676 KiB in 0:08
snapshot d7a3f88b saved
13.14user 1.71system 0:11.16elapsed 133%CPU (0avgtext+0avgdata 90564maxresident)k
0inputs+64outputs (0major+1756minor)pagefaults 0swaps

(pprof) top  
Showing nodes accounting for 15.21MB, 97.56% of 15.59MB total
Dropped 84 nodes (cum <= 0.08MB)
Showing top 10 nodes out of 59
      flat  flat%   sum%        cum   cum%
   12.52MB 80.26% 80.26%    12.52MB 80.26%  github.com/restic/restic/internal/repository.(*Index).store
    1.01MB  6.46% 86.72%     1.01MB  6.50%  github.com/restic/chunker.NewWithBoundaries
    0.39MB  2.52% 89.23%     0.39MB  2.52%  reflect.New
    0.36MB  2.29% 91.53%     0.37MB  2.34%  github.com/restic/restic/internal/restic.NodeFromFileInfo
    0.29MB  1.89% 93.41%     1.10MB  7.05%  github.com/restic/restic/internal/archiver.(*Archiver).SaveDir
    0.28MB  1.80% 95.22%     0.28MB  1.80%  bytes.makeSlice
    0.15MB  0.94% 96.15%     0.15MB  0.94%  github.com/restic/restic/internal/archiver.(*TreeSaver).Save
    0.09MB   0.6% 96.76%     0.09MB   0.6%  strings.(*Builder).grow
    0.08MB   0.5% 97.26%     0.08MB   0.5%  github.com/restic/restic/internal/restic.BlobSet.Insert
    0.05MB   0.3% 97.56%     0.38MB  2.46%  encoding/json.Marshal

Aquí, el índice ya es la parte más grande de la memoria. También puede ver que el tiempo informa que se usaron 90 MB. En mi opinión, esta es la configuración del recolector de basura de la que ya se ha hablado.

Índice de recarga

Esto parece funcionar. Sin embargo, mostró una ETA de aproximadamente 20 minutos mientras usaba constantemente el 100% de la CPU... Esto se puede explicar fácilmente ya que constantemente vuelve a leer (¡y descifra!) los archivos de índice...
Sin embargo, considero que esta estrategia de índice no merece una mirada más profunda....

Índice de memoria baja

<snip: same output as above>
processed 100000 files, 567.676 KiB in 0:08
snapshot f8822cee saved
13.47user 2.03system 0:11.90elapsed 130%CPU (0avgtext+0avgdata 109584maxresident)k
0inputs+72outputs (0major+1945minor)pagefaults 0swaps

(pprof) top
Showing nodes accounting for 7.68MB, 94.71% of 8.11MB total
Dropped 88 nodes (cum <= 0.04MB)
Showing top 10 nodes out of 63
      flat  flat%   sum%        cum   cum%
    4.84MB 59.68% 59.68%     4.84MB 59.68%  github.com/restic/restic/internal/restic.IDSet.Insert
    1.01MB 12.50% 72.18%     1.02MB 12.58%  github.com/restic/chunker.NewWithBoundaries
    0.41MB  5.05% 77.23%     0.41MB  5.05%  reflect.New
    0.35MB  4.26% 81.48%     0.35MB  4.26%  github.com/restic/restic/internal/restic.NodeFromFileInfo
    0.28MB  3.47% 84.95%     0.28MB  3.47%  bytes.makeSlice
    0.27MB  3.31% 88.26%     1.01MB 12.46%  github.com/restic/restic/internal/archiver.(*Archiver).SaveDir
    0.19MB  2.33% 90.60%     0.19MB  2.33%  github.com/restic/restic/internal/archiver.(*TreeSaver).Save
    0.18MB  2.22% 92.81%     4.94MB 60.93%  github.com/restic/restic/internal/repository.(*IndexLowMem).store
    0.08MB  0.96% 93.78%     0.08MB  0.96%  github.com/restic/restic/internal/restic.BlobSet.Insert
    0.08MB  0.93% 94.71%     0.40MB  4.92%  encoding/json.Marshal

¡La memoria total informada por el generador de perfiles se reduce a la mitad! Además, el conjunto de datos del índice de 12,5 MB se reemplaza por un IDSet de menos de 5 MB. ¡Estimaría que con este enfoque el requisito de memoria de índice se puede reducir a aproximadamente un 40% !
La compensación es un tiempo de copia de seguridad total ligeramente más alto (13,5 s en comparación con 13,1 s con el índice estándar).
¡Estoy realmente interesado en cómo funciona esta estrategia de índice en otras configuraciones de prueba!

Índice de pernos: primera ejecución

<snip: same output as above>
processed 100000 files, 567.676 KiB in 0:30
snapshot 9ff3d71f saved
37.01user 3.25system 0:34.54elapsed 116%CPU (0avgtext+0avgdata 282816maxresident)k
0inputs+114096outputs (0major+5436minor)pagefaults 0swaps

(pprof) top
Showing nodes accounting for 2592.31kB, 90.31% of 2870.50kB total
Dropped 79 nodes (cum <= 14.35kB)
Showing top 10 nodes out of 81
      flat  flat%   sum%        cum   cum%
 1030.83kB 35.91% 35.91%  1037.16kB 36.13%  github.com/restic/chunker.NewWithBoundaries
  390.02kB 13.59% 49.50%   390.02kB 13.59%  reflect.New
  278.60kB  9.71% 59.20%   278.60kB  9.71%  github.com/restic/restic/internal/restic.NodeFromFileInfo
  274.66kB  9.57% 68.77%   933.59kB 32.52%  github.com/restic/restic/internal/archiver.(*Archiver).SaveDir
  244.05kB  8.50% 77.27%   244.05kB  8.50%  bytes.makeSlice
  169.81kB  5.92% 83.19%   169.81kB  5.92%  github.com/restic/restic/internal/archiver.(*TreeSaver).Save
      80kB  2.79% 85.98%       80kB  2.79%  github.com/restic/restic/internal/restic.BlobSet.Insert
   44.17kB  1.54% 87.52%   292.20kB 10.18%  github.com/restic/restic/internal/archiver.(*TreeSaver).save
   40.16kB  1.40% 88.91%    40.16kB  1.40%  strings.(*Builder).grow
      40kB  1.39% 90.31%       40kB  1.39%  github.com/restic/restic/internal/restic.NewBlobBuffer

También crea un archivo my.db de 72 MB donde se guarda el índice.

Como era de esperar, ¡el consumo de memoria básicamente se ha ido!
Sin embargo, esto conlleva una gran compensación en el tiempo de copia de seguridad (37 s en comparación con 13,1 s con el índice estándar).
Creo que sería interesante ver cómo funciona esto con conjuntos de datos más grandes. No tengo idea si este problema de rendimiento solo agrega un factor de aproximadamente 3 o incluso está creciendo más que linealmente.
Además, me parece que se deben considerar muchas más pruebas y ajustes con estrategias de índice basadas en almacenamiento...

Índice de pernos - segunda carrera

Como en la configuración del perno, la base de datos no se elimina después de la ejecución, volví a ejecutar con una base de datos ya presente:

processed 100000 files, 567.676 KiB in 0:08
snapshot b364fba4 saved
14.26user 1.94system 0:12.07elapsed 134%CPU (0avgtext+0avgdata 133464maxresident)k
0inputs+88outputs (0major+2601minor)pagefaults 0swaps

El perfil de memoria es casi idéntico al de la primera ejecución, que es el esperado.
Realmente me pregunto por qué esta segunda carrera es mucho más rápida que la primera. ¿Es el caché fs el que está funcionando ahora? ¿O boltdb de alguna manera usa la información del índice ya almacenado para acelerar las cosas?

En general, esto puede indicar que con el índice basado en almacenamiento, es posible que tengamos que pensar en un índice almacenado permanente, ¡porque es capaz de aumentar bastante el rendimiento!

Tal vez también valga la pena probar una tienda de clave/valor basada en LSM. Los LSM tienen compensaciones de rendimiento y huella de disco bastante diferentes en comparación con BTrees, y pueden funcionar mejor para el caso de uso de índice persistente. Yo personalmente usaría RocksDB , pero probablemente también haya tiendas Go-nativas lo suficientemente buenas.

¡Gracias por crear un prototipo de esto! Parece muy alentador.

Los archivos de índice están encriptados actualmente, por lo que cualquier almacenamiento de disco que usemos también debe estar encriptado. Miré https://github.com/dgraph-io/badger : es nativo de Go, está basado en LSM y admite cifrado.

El índice de memoria bajo se ve bien para las copias de seguridad. Sin embargo, no estoy seguro de si escalará para restauraciones desde repositorios con una gran cantidad de blobs.

Gracias por la discusión hasta ahora.
Tengo que admitir que no dediqué mucho tiempo a probar otras opciones de índice basadas en almacenamiento. (el tejón todavía está en mi lista de tareas pendientes)
La razón es la siguiente: Veo que surgirán bastantes problemas cuando se intente seriamente llevar esto al estado de grado de producción. Veo problemas sobre el cifrado, cómo limpiar archivos y cómo lidiar con operaciones canceladas, etc.

Es por eso que comencé a pensar en otra posibilidad para optimizar el índice en memoria. El resultado ya está en mi rama index-alternative y se llama low-mem2. La idea es almacenar todos los datos del índice en tablas ordenadas (ahorrando la sobrecarga del mapa) y tratar de no almacenar nada duplicado. La identificación del paquete, por ejemplo, se guarda en una tabla separada y solo se hace referencia en la tabla principal.
No realicé muchas pruebas hasta ahora, pero parece que el rendimiento es un poco peor que el índice estándar. Por otro lado, el consumo de memoria es comparable al de memoria baja, lo que significa un ahorro de alrededor del 60 % en comparación con el índice estándar en mi configuración de prueba, ver más arriba.
Teniendo en cuenta que para la mayoría de las operaciones (por ejemplo, copia de seguridad) solo se necesita información específica del índice, esto se puede mejorar aún más.

Entonces sugeriría lo siguiente: intentaré hacer un PR para reemplazar (u opcionalmente reemplazar) el índice estándar por el optimizado para memoria (low-mem2). Con suerte, esto mejorará rápidamente el problema de la memoria.
Las posibilidades de utilizar un índice basado en disco quedan entonces abiertas para el futuro.

¿Cual es tu opinión acerca de esto? Tal vez algunos de los mantenedores principales puedan darme una pista sobre la dirección correcta en la que trabajar. ¡Gracias!

Reducir el uso de la memoria a la mitad me parece una solución razonable a corto plazo. Actualmente estoy un poco preocupado por un posible impacto en el rendimiento al mantener los datos en una matriz ordenada. La implementación actual de MasterIndex simplemente itera sobre una lista que contiene los índices. Para repositorios más grandes, con unos pocos miles de archivos de índice, restic dedica una gran parte del tiempo a las copias de seguridad/verificaciones iterando sobre los diferentes índices, consulte #2284.

Con respecto a su rama low-mem2: estoy un poco confundido por las funciones SortByBlob y SortByPack. ¿No debería ser posible simplemente ordenar por blobID y luego simplemente iterar sobre todo el índice para búsquedas basadas en el packID (como lo hace la implementación actual)? (Mi preferencia sería deshacerme por completo del bloqueo una vez que se haya terminado un índice)
Actualmente, el código solo puede convertir un índice existente al nuevo formato. Sería bastante bueno si pudiera convertir un índice finalizado al formato más eficiente, para obtener un mejor consumo de memoria también durante la copia de seguridad inicial.

@MichaelEischer : Gracias por señalar este problema en masterindex. Por lo que puedo ver, hay tres formas posibles de usar el índice:

  1. leer desde archivos de índice y luego solo lectura
  2. creado durante la ejecución y luego de solo lectura
  3. Operaciones para modificar el índice (esto es solo purgar/recuperar)

Como 1. es el caso de uso principal y 2. es bastante similar, estoy de acuerdo en que debemos optimizar para esto. También propondría cambiar la forma en que funciona el índice maestro y permitir que una estructura de índice se llene con los datos de índice de muchos archivos de índice que se han leído (y también para los archivos de índice creados y luego utilizados solo como lectura).

Acerca de low-mem2:

  • Agregué SortByBlob y SortByPack porque hay un caso de uso para paquetes ordenados. Pero también estoy de acuerdo en que deberíamos intentar tener operaciones paralelas desbloqueadas en el índice. Por lo que puedo ver, la opción de buscar por blob o buscar por paquete está determinada por el comando principal. Por lo tanto, incluso si bloqueamos para recurrir, sería eficientemente no bloquear para la operación principal. Y, por supuesto, deberíamos usar RWMutex.
    Tal vez la búsqueda de paquetes en una lista ordenada por blob también esté bien, solo necesita algunas pruebas.
  • Acerca de las consideraciones de rendimiento. El hecho de que restic en este momento solo lea linealmente a través de todos los archivos de índice me hace estar bastante seguro de que una lista ordenada de blob de todos los archivos de índice y la búsqueda de bisección pueden incluso superar la implementación actual para índices grandes, es decir, para muchos archivos de índice ;-)

Entonces, ¿cómo continuamos? Como ya se mencionó, puedo preparar un PR para cambiar la implementación actual de index/masterindex por una optimizada.
@MichaelEischer Agradecería recibir comentarios tempranos y sugerencias de mejora. ¿Puedo darle algunas partes iniciales del trabajo para su revisión? Tal vez también encontremos a algunas personas probando los cambios con respecto al rendimiento y el uso de la memoria para escenarios de la vida real...

@aawsome Perdón por la respuesta tardía. Podría revisar las primeras partes del trabajo, sin embargo, también sería mejor obtener algunos comentarios sobre el diseño general por parte de los mantenedores principales. Para las pruebas, tengo un repositorio de 8 TB con 40 millones de archivos disponibles; Para mis pruebas de rendimiento del índice, hasta ahora he usado el comando check que hace mucho hincapié en el índice.

MasterIndex se usa para el caso 1 y 2. Combinar todos los archivos de índice en la memoria para un índice estático que sería suficiente para el caso 1 debería ser bastante fácil de implementar. Una sola lista ordenada definitivamente será más rápida que el índice actual una vez que alcance una cierta cantidad de archivos de índice O(log n) frente a O(n) . La adición dinámica de archivos de índice completos/finalizados (caso 2) no se combina fácilmente con una única lista de blobs ordenada, ya que sería necesario recurrir a ella después de cada índice agregado. Recurrir por completo a un índice con cientos de millones de entradas varias veces me parece una idea bastante mala. [EDITAR] Simplemente fusionar dos listas ya ordenadas también funciona y básicamente equivale a una copia completa de la lista existente. Con cientos de millones de entradas, lo más probable es que aún tome mucho tiempo [/EDIT] . El crecimiento dinámico de una lista de este tipo también requeriría alguna forma de dejar espacio para nuevas entradas sin tener que mantener una copia completa del búfer de la lista antigua y nueva en la memoria.

Las alternativas a una lista ordenada que me vienen a la mente en este momento serían algún tipo de árbol o un hashmap. Lo primero probablemente requeriría algunos punteros adicionales en la estructura de datos y lo último sería bastante similar a la implementación actual. Pensé en reemplazar el MasterIndex con un solo mapa hash grande, pero en este momento la estrategia de crecimiento para el búfer de respaldo del mapa hash me impidió intentarlo: Go duplica el tamaño del búfer de respaldo una vez que se alcanza un cierto factor de carga, que al final podría requerir mantener temporalmente un nuevo búfer de respaldo bastante vacío junto con el anterior en la memoria. Este escenario probablemente requeriría mucha más memoria que la implementación actual. Esto debería poder resolverse mediante el uso de dos niveles de hashmaps, donde el primero se indexa con los primeros bits del ID del blob. Simplemente no estoy seguro de cómo manejar de manera eficiente las ID de blob que no están distribuidas de manera uniforme y, por lo tanto, aún podrían generar grandes desequilibrios en los mapas hash de segundo nivel. Puede valer la pena mirar las estructuras de datos de índice utilizadas por las bases de datos o borg-backup.

El caso 3 (prune/rebuild-index/find) está manejado por index/Index.New este momento, por lo que esto no debería entrar en conflicto con los cambios en MasterIndex.

Los métodos ListPack en Index/MasterIndex me parecen un código muerto, restic aún se compila cuando elimino ese método (solo la prueba Index necesita algunos ajustes). Los únicos usos de un método ListPack que pude encontrar están en la estructura Repository . Esto haría innecesario SortByPack y evitaría la complejidad asociada con el recurso.

La búsqueda de paquetes en una lista ordenada por blob funcionaría, pero probablemente sea un poco más lenta que la implementación actual. La implementación actual itera sobre todos los índices y se detiene una vez que se encuentra un índice que contiene los paquetes, lo que en promedio requiere recorrer la mitad de la lista. La exploración de la lista ordenada por blobs siempre requeriría una exploración completa. Por otro lado, el escaneo debería ser más rápido, por lo que el rendimiento general debería ser más o menos comparable.

@aawsome Entonces, en este momento, el enfoque de los cambios sería optimizar el uso de memoria del índice junto con el rendimiento de masterIndex, mientras se posponen los cambios fundamentales, como el uso de una base de datos en disco, para más adelante.

Con respecto al caso 3: ¿Planea reemplazar también index/Index que está agrupado por packID? Mi comprensión actual del código restic es que estos serían los únicos usuarios potenciales de ListPack .
En este momento solo puedo encontrar dos usos activos de index/Index.Packs :

  • cmd_list/runList que solo necesita algún tipo de lista de blobs y realmente no le importa ordenar
  • cmd_prune/pruneRepository que lo usa para estadísticas, iterar sobre todos los blobs para encontrar duplicados y tomar la decisión de qué paquete reescribir o eliminar. Solo el último uso es un poco más complejo de evitar, ya que requeriría algunos hashmaps adicionales para realizar un seguimiento de los blobs usados ​​por paquete.

Su solicitud de combinación de "Índice optimizado" agregaría otro uso que en realidad requiere un índice agrupado por archivos de paquete.

Con respecto a un MasterIndex optimizado: mi último comentario se centró principalmente en el uso de una única estructura de datos de índice grande que probablemente necesitaría usar la partición por clave para evitar grandes picos en el uso de la memoria al agregar nuevas entradas. Esa forma de particionar tiene la desventaja de que agregar un nuevo índice probablemente requiera tocar todas las particiones. Además, los desequilibrios en el tamaño de las particiones podrían causar más problemas.

Sin embargo, también hay otra opción (que está ligeramente inspirada en los árboles de combinación estructurados de registros): Mantenga el número de entradas en el MasterIndex más o menos constante en un valor c. Esto significaría que un nuevo índice, después de la finalización, se fusionaría con una de las partes del índice existente. De esa manera solo se modifica una parte del índice a la vez. Para los mapas hash, esto podría usarse para mantener el factor de carga bastante alto, ya que cada parte se llenaría antes de usar la siguiente. Para matrices ordenadas, alguna otra estrategia suena más razonable: fusionar partes de índice en otras más grandes con aproximadamente 1k, 2k, 4k, 8k, ... entradas mientras solo fusiona la clase de tamaño más grande actualmente una vez que existe una cierta constante de partes de índice con ese tamaño. Esto debería proporcionar una complejidad de O(log n) para agregar nuevos archivos de índice de forma incremental. Dependiendo de la constante c, agregar nuevos archivos de índice es bastante económico (para partes de índice pequeñas) o la búsqueda es rápida (poca cantidad de partes de índice).

@aawsome Entonces, en este momento, el enfoque de los cambios sería optimizar el uso de memoria del índice junto con el rendimiento de masterIndex, mientras se posponen los cambios fundamentales, como el uso de una base de datos en disco, para más adelante.

Sí, ese es mi enfoque. Podemos analizar agregar opciones para que el comando de copia de seguridad solo guarde los blobs de árbol en el índice (y, por lo tanto, guarde estúpidamente cada nuevo blob en la instantánea) o tal vez solo cargue los blobs de datos utilizados en la instantánea anterior en el índice (y, por lo tanto, solo tenga deduplicación de blobs en las instantáneas anteriores). Esto permitiría a los clientes con poca memoria realizar copias de seguridad en grandes repositorios, seguidas de operaciones de poda en máquinas con mucha memoria para realizar la deduplicación completa.
Pospondré mi trabajo sobre el índice basado en disco.

Con respecto al caso 3: ¿Planea reemplazar también index/Index que está agrupado por packID?

Creo que esto es posible y debería ser el objetivo de la nueva implementación. Sin embargo, no lo analicé en detalle. ¡Gracias por tus sugerencias!

Acerca de los detalles de implementación: necesitaré un poco de tiempo para leer y pensar al respecto. ¡Muchas gracias por tus ideas y tu apoyo!

Como se anunció, preparé una versión anterior del índice refactorizado, consulte refactor-index
También se han modificado ligeramente algunas partes internas para aprovechar el nuevo índice. Por ejemplo check ahora debería requerir mucha menos memoria que antes...

Todavía es WIP, pero casi todas las pruebas ya pasaron y, según mis pruebas, se ve bastante bien.
Estos son los puntos abiertos:

  • Corregir pruebas para el índice (esta parte aún no se compila)
  • refactorizar rebuild-index y prune para usar el nuevo MasterIndex y el nuevo índice en memoria
  • pruebas de rendimiento

La implementación usa una matriz grande (ordenada) para almacenar el índice y el formato IndexJSON para mantener los elementos creados en la ejecución actual.
Al realizar una copia de seguridad, el IndexJSON se almacena regularmente en el repositorio y se agrega al índice principal en memoria.
Al cargar el índice desde el repositorio, cada archivo de índice se carga en el formato IndexJSON y luego se agrega al índice principal en memoria.

Por lo tanto, la función crucial es AddJSONIndex en internal/index/index_new.go , a partir de la línea 278.

Acerca de sus comentarios @MichaelEischer Realmente no puedo estimar los impactos de agregar a la tabla indexada, consulte la línea 329 en internal/index/index_new.go . ¿Significa esto que toda la tabla debe copiarse regularmente (mencionó los "picos de memoria")? ¿O es esto manejado muy bien por la gestión de memoria de go y del sistema operativo?

Además, después de insertar la tabla, debe ordenarse nuevamente (al menos antes de buscarla por primera vez). Supongo que la implementación actual debería evitar operaciones de clasificación innecesarias, pero esto necesita pruebas de rendimiento. ¿Alguien puede ayudar con estas pruebas?

@aawsome :

¿Alguien puede ayudar con estas pruebas?

Estoy dispuesto a ayudar con las pruebas de rendimiento. ¿Qué es exactamente lo que quiere tener probado?

@dimejo :
¡Muchas gracias por su ayuda!

Preferiría comparaciones 1:1 (es decir, la ejecución idéntica con ambas ramas) entre la rama maestra (o la última versión) y mi rama.
Estoy interesado en las diferencias sobre el uso y el rendimiento de la memoria (tiempo total y uso de la CPU) para repositorios grandes (muchos archivos, muchas instantáneas, etc.). Usar /usr/bin/time -v está bien, tal vez habilitar el perfilado (perfilado de memoria y perfilado de CPU) puede ayudar a explicar mejor las diferencias.
El backend no importa en absoluto: el backend local está bien, pero no es obligatorio.

Creo que los siguientes comandos deberían al menos ser probados:

  • backup 1. ejecución inicial 2. otra ejecución con archivos (casi) sin cambios; 3. otra carrera con --force
  • check
  • restore
  • tal vez mount
  • list blobs
  • cat blob
  • find --blob

[editar: lista agregada, cat y comandos de búsqueda]

¡Escriba si no entiende lo que quiero decir o si necesita instrucciones más detalladas sobre cómo ejecutar estas pruebas!

Intentaré hacer todas las pruebas este fin de semana, pero necesito ayuda para habilitar la creación de perfiles. No soy programador y seguir los consejos en Internet no funcionó tan bien :/

Puedes empezar con
/usr/bin/time -v restic <command>

Para habilitar la creación de perfiles, primero compile restic con la opción debug :
go run build.go --tags debug
y luego use la bandera --mem-profile o --cpu-profile , por ejemplo:
restic --mem-profile . <command>

Esto crea un archivo .pprof que se puede analizar con go tool pprof , por ejemplo:
go tool pprof -pdf mem.pprof restic
devuelve un buen pdf.

¡Impresionante! Pensé que sería mucho más complejo habilitar la creación de perfiles. Lo probare y volvere a informar.

@aawsome Gracias por tu trabajo, eso es realmente un montón de cambios.

Hice algunas pruebas con el comando check y sucedió algo extraño. Usando el maestro restic, el comando se completa en 6 minutos (327.25 real 613.08 usuario 208.48 sys), con los cambios de índice lleva más de una hora (4516.77 real 896.17 usuario 531.71 sys). El tiempo se gastó en algún lugar después de imprimir "comprobar instantáneas, árboles y blobs", pero aún no tuve tiempo de echar un vistazo más de cerca.

En mi opinión, su compromiso con los cambios de índice es demasiado grande para ser revisable, mil líneas de código agregado junto con dos mil eliminaciones es realmente mucho.
He notado al menos cuatro cambios individuales:

  • Index.Lookup ya no devuelve una lista, sino solo un blob en su lugar
  • Se extrae una gran cantidad de código de manejo de índices del Repositorio
  • El masterIndex solo mantiene un único índice sin terminar
  • El índice de memoria bajo real

Tengo algunos comentarios (incompletos) sobre el código en sí, pero aún no pensé demasiado en la arquitectura general del código:

struct IndexLowMem2.byFile : Me tomó bastante tiempo entender el nombre de la variable. Cámbielo a byPackFile o similar para que sea más descriptivo.

IndexLowMem2/FindPack : Mi entendimiento de restic.IDs.Find es que espera una lista ordenada de ID. Sin embargo, idx.packTable no parece estar ordenado ya que esto rompería el packIndex en blobWithoutType .

Condición de carrera en index_new/FindBlob : el índice se puede recurrir entre la llamada a SortByBlob y la readquisición del bloqueo de lectura. Actualmente no veo una buena forma de solucionar esto cuando se usa un RWMutex. El mismo problema también existe en FindBlobByPack .
IndexLowMem2/Lookup : La llamada a FindBlob y el acceso a blobTable no es atómico.

IndexLowMem2/AddJSONIndex : Las llamadas para agregar un segmento asignan un nuevo búfer cuando el anterior es demasiado pequeño y luego copian todos los datos en el nuevo búfer. Como el método recopila primero las nuevas entradas de packTable/indexTable y luego las agrega de una sola vez, esto no debería ser demasiado ineficiente.
Por lo tanto, los segmentos packTable en idx.byFile apuntarán a versiones antiguas de la matriz packTable cuando append asigne un nuevo búfer. Esto probablemente desperdiciará bastante memoria para packCounts grandes. Simplemente puede agregar endPackIndex a FileInfo además de startPackIndex y luego acceder directamente a packTable .
Añadir a it.blobTable parece un poco caro, ya que requiere una clasificación más adelante. Debería ser posible ordenar previamente las nuevas entradas table y luego fusionar las dos listas ordenadas (sin embargo, fusionarlas en el lugar puede ser un poco complicado).

MasterIndex.Lookup/Has/Count/... : Después de la búsqueda en mi.mainIndex , mi.idx podría guardarse y agregarse a mainIndex antes de que se adquiera idxMutex .

MasterIndex.Save : Esto parece bloquear los masterIndex mientras la carga del índice está pendiente. La implementación anterior en Repository.SaveIndex parece funcionar sin bloqueo.

Ah, he encontrado el problema: ahora mismo la llamada a PrepareCache está comentada. PrepareCache también configura la función PerformReadahead de Cache. Sin esto, Check tiene que cargar cada blob de datos de árbol por separado desde el backend.

@MichaelEischer : Muchas gracias por brindar sus valiosos comentarios a esta etapa inicial de implementación:+1:

Sé que es un gran cambio de código; sin embargo, creo que también es una simplificación de la implementación del índice actual. De ahí la gran cantidad de archivos/líneas de código eliminados.

Perdón por el problema PrepareCache . Recuerdo que lo comenté para manejarlo más tarde, pero hasta ahora solo hice pruebas de back-end locales y, por lo tanto, el problema no surgió.

Acerca de sus comentarios correctos de las cosas de byPack : Tiene razón, esto es WIP y aún no se usa. Lo dejé porque tenía la intención de usarlo más tarde para rebuild-index y prune . Sin embargo, me concentraré primero en hacer que funcione el caso de uso principal; tal vez elimine esta parte antes de hacer una PR.

Acerca de la clasificación: En este momento, se supone que el código hace la clasificación solo cuando es necesario, es decir, dentro de la primera llamada Lookup o Has . Esto significa que cuando se cargan muchos archivos de índice en el índice en memoria (que es la operación habitual al principio), no se realiza ninguna clasificación. Solo sucede cuando todos los archivos de índice ya están cargados. Durante la copia de seguridad, cuando se escriben más archivos de índice, se agregan entradas al índice en memoria y luego cada vez que se necesita un recurso. Sin embargo, este es un recurso de una lista en su mayoría ordenada. No sé cómo sort.Sort maneja la lista casi ordenada, pero generalmente se usan algoritmos de clasificación que son muy eficientes para listas casi ordenadas.

Me ocuparé de los otros problemas. ¡Gracias especialmente por encontrar las condiciones de carrera!

En mi opinión, su compromiso con los cambios de índice es demasiado grande para ser revisable, mil líneas de código agregado junto con dos mil eliminaciones es realmente mucho.

Esta.

Sé que es un gran cambio de código; sin embargo, creo que también es una simplificación de la implementación del índice actual. De ahí la gran cantidad de archivos/líneas de código eliminados.

Aquí es cuando debe dividir sus cambios en PR individuales separados, para que sea manejable y con una intención clara.

@rawtaz :
La intención clara es un rediseño del índice utilizado en restic para deshacerse de los problemas con la implementación actual.

En mi opinión, el cambio no es tan grande si se considera que es una reimplementación completa del índice (y tenga en cuenta que también se están reimplementando algunas cosas heredadas, consulte index/index_old_format.go ).

Sin embargo, esto es WIP y tiene algunos problemas que estoy dispuesto a solucionar antes de pensar en cómo integrar mejor este trabajo en la rama principal.
Estoy ansioso por obtener ideas sobre cómo dividirlo en partes más pequeñas. ¿Puede darme instrucciones sobre qué cambios podrían aceptarse como relaciones públicas?

Hice algunas pruebas con el comando check y sucedió algo extraño. Usando el maestro restic, el comando se completa en 6 minutos (327.25 real 613.08 usuario 208.48 sys), con los cambios de índice lleva más de una hora (4516.77 real 896.17 usuario 531.71 sys). El tiempo se gastó en algún lugar después de imprimir "comprobar instantáneas, árboles y blobs", pero aún no tuve tiempo de echar un vistazo más de cerca.

Esto debe ser resuelto ahora.

struct IndexLowMem2.byFile : Me tomó bastante tiempo entender el nombre de la variable. Cámbielo a byPackFile o similar para que sea más descriptivo.

Está cambiado ahora.

IndexLowMem2/FindPack : Mi entendimiento de restic.IDs.Find es que espera una lista ordenada de ID. Sin embargo, idx.packTable no parece estar ordenado ya que esto rompería el packIndex en blobWithoutType .

No se usó toda la lógica de byPack y usted señaló correctamente que también había algunos errores. Limpié este código muerto.

Condición de carrera en index_new/FindBlob : el índice se puede recurrir entre la llamada a SortByBlob y la readquisición del bloqueo de lectura. Actualmente no veo una buena forma de solucionar esto cuando se usa un RWMutex. El mismo problema también existe en FindBlobByPack .

Espero haber encontrado una buena manera de manejar esto. Básicamente pruebo sortedByBlob dos veces si está configurado como falso. Por lo tanto, este problema debería solucionarse ahora.

IndexLowMem2/Lookup : La llamada a FindBlob y el acceso a blobTable no es atómico.

Esto ahora se hace usando el nuevo indexTable.Get atómico.

Los packTable segmentos en idx.byFile , por lo tanto, apuntarán a versiones antiguas de la matriz packTable cuando append asigne un nuevo búfer. Esto probablemente desperdiciará bastante memoria para packCounts grandes. Simplemente puede agregar endPackIndex a FileInfo además de startPackIndex y luego acceder directamente a packTable .

Está arreglado ahora. packTable hasta ahora no se ha utilizado...

MasterIndex.Lookup/Has/Count/... : Después de la búsqueda en mi.mainIndex , mi.idx podría guardarse y agregarse a mainIndex antes de que se adquiera idxMutex .

Esto está arreglado ahora.

MasterIndex.Save : Esto parece bloquear los masterIndex mientras la carga del índice está pendiente. La implementación anterior en Repository.SaveIndex parece funcionar sin bloqueo.

Este problema sigue abierto. No encontré una buena manera de manejarlo con solo un índice sin terminar.
Tampoco sé el impacto de esto con respecto al rendimiento. Solo debería afectar a backup . Espero ver algunas pruebas si esto es relevante..

Hice algunas pruebas con el comando check y sucedió algo extraño. Usando el maestro restic, el comando se completa en 6 minutos (327.25 real 613.08 usuario 208.48 sys), con los cambios de índice lleva más de una hora (4516.77 real 896.17 usuario 531.71 sys). El tiempo se gastó en algún lugar después de imprimir "comprobar instantáneas, árboles y blobs", pero aún no tuve tiempo de echar un vistazo más de cerca.

Esto debe ser resuelto ahora.

Lo probaré en los próximos días.

Condición de carrera en index_new/FindBlob : el índice se puede recurrir entre la llamada a SortByBlob y la readquisición del bloqueo de lectura. Actualmente no veo una buena forma de solucionar esto cuando se usa un RWMutex. El mismo problema también existe en FindBlobByPack .

Espero haber encontrado una buena manera de manejar esto. Básicamente pruebo sortedByBlob dos veces si está configurado como falso. Por lo tanto, este problema debería solucionarse ahora.

Esto aún deja la posibilidad de una colisión entre SortByBlob y AddJSONIndex . La verificación de que el índice está ordenado correctamente y la búsqueda real del índice debe ocurrir en la misma sección crítica. Desafortunadamente, RWMutex no admite una degradación atómica de un bloqueo de escritura a uno de lectura, que sería la forma más fácil de mantener el bloqueo después de la clasificación. Actualmente veo dos posibilidades:

  • Tome el bloqueo de lectura, verifique que el índice esté ordenado y ejecute la búsqueda. Si el índice no está ordenado, libere el bloqueo de lectura y obtenga el bloqueo de escritura, ordene el índice y complete solo esa búsqueda mientras mantiene presionado el bloqueo de escritura.
  • Tome el bloqueo de lectura, verifique que el índice esté ordenado y ejecute la búsqueda. Si el índice no está ordenado, libere el bloqueo de lectura y obtenga el bloqueo de escritura, ordene el índice y vuelva a intentarlo desde el principio.

MasterIndex.Lookup/Has/Count/... : Después de la búsqueda en mi.mainIndex , mi.idx podría guardarse y agregarse a mainIndex antes de adquirir idxMutex .

Parece que te has perdido MasterIndex.Lookup .

MasterIndex.Save : Esto parece bloquear el masterIndex mientras la carga del índice está pendiente. La implementación anterior en Repository.SaveIndex parece funcionar sin bloqueo.

Este problema sigue abierto. No encontré una buena manera de manejarlo con solo un índice sin terminar.
Tampoco sé el impacto de esto con respecto al rendimiento. Solo debería afectar a backup . Espero ver algunas pruebas si esto es relevante..

Hmm, el bloqueador principal aquí es que no puede obtener la identificación del paquete de índice antes de que se complete la llamada de carga. Sería bastante fácil de resolver permitiendo que varios índices no estén terminados, mientras que solo se puede escribir en el último y los predecesores se están cargando actualmente.

Esto aún deja la posibilidad de una colisión entre SortByBlob y AddJSONIndex . La verificación de que el índice está ordenado correctamente y la búsqueda real del índice debe ocurrir en la misma sección crítica. Desafortunadamente, RWMutex no admite una degradación atómica de un bloqueo de escritura a uno de lectura, que sería la forma más fácil de mantener el bloqueo después de la clasificación. Actualmente veo dos posibilidades:

* Take the readlock, check that the index is sorted and run the search. If the index is not sorted, release the readlock and get the writelock, sort the index and complete just that search while holding the writelock.

* Take the readlock, check that the index is sorted and run the search. If the index is not sorted, release the readlock and get the writelock, sort the index and retry from the start.

Vaya, de hecho, estaba tan concentrado en SortByBlobs que no consideré los métodos a los que se llama :-(
¡Gracias por tus sugerencias! Usé la segunda opción ahora.

Parece que te has perdido MasterIndex.Lookup .

Está arreglado ahora.

Hmm, el bloqueador principal aquí es que no puede obtener la identificación del paquete de índice antes de que se complete la llamada de carga. Sería bastante fácil de resolver permitiendo que varios índices no estén terminados, mientras que solo se puede escribir en el último y los predecesores se están cargando actualmente.

También arreglé esto. Ahora codifico/descifro el índice y lo escribo en el índice principal mientras está bloqueado, pero escribo el archivo en el backend después de liberar el bloqueo.

Además, encontré otra optimización que reduce bastante el consumo de memoria (y también soluciona el problema de agregar): cambié la matriz grande por una estructura que implementa una lista de blobs "paginados". Aquí se mantiene una lista de "páginas" y si es necesario ampliarla, solo se asigna una nueva página. Esto permite agregar sin necesidad de volver a copiar todos los elementos antiguos. Además, la sobrecarga es fija y aumenta al máximo el tamaño de la página, mientras que con la adición obtiene una sobrecarga bastante grande (la matriz obtiene una "reserva de capacidad" creciente para garantizar que no necesite volver a copiar con demasiada frecuencia). Esta optimización parece ahorrar otro ~15-20% de memoria.

Me alegra recibir comentarios y me gusta aún más ver pruebas de rendimiento con grandes repositorios.

Aquí están los resultados de mi primera comparación.
Usé un directorio de datos con 550.000 archivos (da como resultado 550.000 blobs de datos) usando el script

#!/bin/sh
mkdir data
for i in `seq 1 550`; do
        echo $i
        mkdir data/$i
        for j in `seq 1 1000`; do
                echo $i$j > data/$i/$j
        done
done

Luego hice una copia de seguridad de este directorio de datos tres veces y ejecuté check después de:

/usr/bin/time/restic -r /path/to/repo --mem-profile . backup /path/to/data
/usr/bin/time/restic -r /path/to/repo --mem-profile . backup /path/to/data
/usr/bin/time/restic -r /path/to/repo --mem-profile . backup /path/to/data -f
/usr/bin/time/restic -r /path/to/repo --mem-profile . check

Estos pasos los ejecuto una vez con la rama maestra (compilada en restic.old ) y mi rama (compilada en restic.new ).
Acabo de usar mi vieja computadora portátil con SSD local.

Aquí están los resultados:
time_backup1_antiguo.txt
mem_backup1_antiguo.pdf
time_backup1_nuevo.txt
mem_backup1_nuevo.pdf

time_backup2_old.txt
mem_backup2_antiguo.pdf
time_backup2_nuevo.txt
mem_backup2_nuevo.pdf

time_backup3_old.txt
mem_backup2_antiguo.pdf
time_backup3_nuevo.txt
mem_backup3_nuevo.pdf

time_check_old.txt
mem_check_old.pdf
time_check_new.txt
mem_check_nuevo.pdf

Para resumir:

  • La nueva implementación de índice usa solo el 22% de la memoria utilizada por la implementación de índice im master (!!!)
  • con check el uso de memoria relacionado con el índice se reduce aún más (se eliminó el IDSet)
  • Con la nueva implementación, el uso de la memoria de índice ya no es el único consumidor de memoria excepcional, lo que hace que las optimizaciones de otras partes valgan la pena.
  • La velocidad y el uso de la CPU fueron comparables. El único caso en el que la nueva implementación fue un poco más lenta fue la tercera ejecución de copia de seguridad que usa mucho las búsquedas de índice. Sin embargo, estamos hablando de un repositorio recién generado con solo unos pocos archivos de índice. Por lo tanto, el principal inconveniente de la implementación del índice master (recorrer todos los archivos de índice) no jugó un papel importante aquí. Creo que las cosas cambian completamente con muchos archivos de índice y que la nueva implementación superará claramente a la actual.

Finalmente he terminado con mis pruebas. Tomó más tiempo de lo estimado porque tuve que volver a ejecutar mis pruebas debido a algunos resultados misteriosos. Quería comparar resultados entre 1 y múltiples núcleos. Curiosamente, la VM con solo 1 vCPU fue mucho más rápida con casi todas las operaciones. Tal vez @fd0 sabe lo que está pasando.

resultados para VM1

1 CPU virtual
RAM de 2GB

Tenga en cuenta que he realizado 1 CPU y 1 ejecución de perfilado de memoria para cada versión restic. El tiempo que se muestra (en segundos) en esta tabla es el promedio de ambas ejecuciones.

| | restic v.0.9.6 | índice nuevo restic | diferencia |
| :--- | ---: | ---: | ---: |
| repositorio de inicio | 2,24 | 2,27 | 1,12% |
| 1ra copia de seguridad | 1574,36 | 1542,80 | -2,00% |
| segunda copia de seguridad | 556,95 | 541,71 | -2,74% |
| 3er respaldo (--force) | 1192,90 | 1195,53 | 0,22% |
| olvidar | 0,51 | 0,52 | 2,97% |
| ciruela pasa | 43,87 | 44,02 | 0,34% |
| comprobar | 30,77 | 31,59 | 2,68% |
| lista de manchas | 2,92 | 3,36 | 14,90% |
| gota de gato | 2,58 | 2,60 | 0,97% |
| encontrar gota | 22,86 | 21,17 | -7,39% |
| restaurar | 895,01 | 883,57 | -1,28% |

resultados para VM8

8 vCPU
RAM de 32GB

Tenga en cuenta que he realizado 1 CPU y 1 ejecución de perfilado de memoria para cada versión restic. El tiempo que se muestra (en segundos) en esta tabla es el promedio de ambas ejecuciones.

| | restic v.0.9.6 | índice nuevo restic | diferencia |
| :--- | ---: | ---: | ---: |
| repositorio de inicio | 2,11 | 2,09 | -0,95% |
| 1ra copia de seguridad | 1894,47 | 1832,65 | -3,26% |
| segunda copia de seguridad | 827,95 | 776,38 | -6,23% |
| 3er respaldo (--force) | 1414,60 | 1411,98 | -0,19% |
| olvidar | 0,60 | 0,56 | -6,72% |
| ciruela pasa | 93,10 | 89,84 | -3,50% |
| comprobar | 70,22 | 68,44 | -2,53% |
| lista de manchas | 3,86 | 7,24 | 87,68% |
| gota de gato | 3,88 | 3,69 | -4,90% |
| encontrar gota | 30,95 | 29,11 | -5,95% |
| restaurar | 1150,49 | 1089,66 | -5,29% |

Información adicional

$ uname -r
5.4.15-200.fc31.x86_64
$restic-orig version
debug enabled
restic 0.9.6 (v0.9.6-40-gd70a4a93) compiled with go1.13.6 on linux/amd64
$ restic-newindex version
debug enabled
restic 0.9.6 (v0.9.6-43-g5db7c80f) compiled with go1.13.6 on linux/amd64

archivos cambiados agregados para la copia de seguridad 1:

Files:       487211 new,     0 changed,     0 unmodified
Dirs:            2 new,     0 changed,     0 unmodified
Added to the repo: 62.069 GiB

archivos cambiados agregados para la copia de seguridad 2:

Files:       166940 new,     0 changed, 487211 unmodified
Dirs:            0 new,     2 changed,     0 unmodified
Added to the repo: 6.805 GiB

archivos cambiados agregados para la copia de seguridad 3:

Files:       654215 new,     0 changed,     0 unmodified
Dirs:            2 new,     0 changed,     0 unmodified
Added to the repo: 4.029 MiB

Registros

registros_vm1_cpu.zip
registros_vm1_mem.zip
registros_vm8_cpu.zip
registros_vm8_mem.zip

@dimejo ¡ Muchas gracias por tus pruebas!

Primero con respecto a su pregunta sobre el aumento del tiempo para la configuración de 8 CPU: está comparando el "tiempo de usuario" que es el total de segundos de CPU utilizados. Con el procesamiento en paralelo, esto es (y debería ser más alto) que usar una sola CPU, ya que siempre hay algo de sobrecarga debido a la paralelización. De hecho, sus 8 ejecuciones de CPU fueron mucho más rápidas, vea el tiempo transcurrido.

Usaste la confirmación 5db7c80f para tus pruebas de índice nuevo. ¿Es posible que vuelva a probar con la última confirmación 26ec33dd ? Debería haber otra disminución en el uso de la memoria y (si hice todo bien) tal vez incluso un mejor rendimiento.

¡En general estoy bastante satisfecho con estos resultados! Parece que mi implementación usa mucha menos memoria y aún tiene un rendimiento similar al de la implementación del índice original. (Por cierto: arreglaré el problema obviamente abierto con list blobs )

Primero con respecto a su pregunta sobre el aumento del tiempo para la configuración de 8 CPU: está comparando el "tiempo de usuario" que es el total de segundos de CPU utilizados. Con el procesamiento en paralelo, esto es (y debería ser más alto) que usar una sola CPU, ya que siempre hay algo de sobrecarga debido a la paralelización. De hecho, sus 8 ejecuciones de CPU fueron mucho más rápidas, vea el tiempo transcurrido.

Gracias por la explicación. Estúpido de mí ni siquiera vi el tiempo transcurrido...

Usaste el commit 5db7c80f para tus pruebas de newindex. ¿Es posible que vuelva a probar con la última confirmación 26ec33dd? Debería haber otra disminución en el uso de la memoria y (si hice todo bien) tal vez incluso un mejor rendimiento.

Seguro.

Resultados

tiempo transcurrido

Tenga en cuenta que he enumerado el tiempo transcurrido (en mm: ss) ahora. Nuevamente mostrando el tiempo promedio para ambas carreras.

| | v0.9.6 | nuevoíndice | nuevoíndice2 | diferencia (v0.9.6 y newindex2) |
| :--- | ---: | ---: | ---: | ---: |
| repositorio de inicio | 00:42,4 | 01:11,6 | 00:21,5 | -49,27% |
| 1ra copia de seguridad | 26:42,7 | 26:58,2 | 31:00,4 | +16,08% |
| segunda copia de seguridad | 10:18,9 | 09:54,1 | 09:03,7 | -12,14% |
| 3er respaldo (--force) | 17:32,6 | 17:05,8 | 20:37,1 | +17,52% |
| olvidar | 00:00,8 | 00:00,9 | 00:00,8 | +0,61% |
| ciruela pasa | 01:02,5 | 00:56,5 | 00:51,2 | -18,16% |
| comprobar | 00:31,6 | 00:30,8 | 00:31,0 | -1,74% |
| lista de manchas | 00:05,1 | 00:06,2 | 00:05,6 | +9,44% |
| gota de gato | 00:02,2 | 00:02,3 | 00:02,1 | -3,39% |
| encontrar gota | 00:28,5 | 00:28,2 | 00:23,0 | -19,28% |
| restaurar | 13:02,0 | 12:23,3 | 14:09,1 | +8,58% |

uso de memoria

Esta tabla muestra el uso total de memoria (en MB) de los archivos pprof. Espero que esa sea la correcta.

| | v0.9.6 | nuevoíndice | nuevoíndice2 | diferencia (v0.9.6 y newindex2) |
| :--- | ---: | ---: | ---: | ---: |
| repositorio de inicio | 32,01 | 32,01 | 32,00 | -0,03% |
| 1ra copia de seguridad | 227,50 | 178,48 | 174,77 | -23,18% |
| segunda copia de seguridad | 227,89 | 157,92 | 149,52 | -34,39% |
| 3er respaldo (--force) | 204,53 | 147,15 | 136,06 | -33,48% |
| olvidar | 32,25 | 32,09 | 32,26 | +0,03% |
| ciruela pasa | 167,55 | 108,96 | 130,60 | -22,05% |
| comprobar | 163,29 | 75,06 | 70,34 | -56,92% |
| lista de manchas | 33,22 | 30,21 | 25,47 | -23,33% |
| gota de gato | 113,79 | 48,86 | 32,23 | -71,68% |
| encontrar gota | 79,48 | 30,40 | 25,64 | -67,74% |
| restaurar | 226,08 | 176,54 | 198,19 | -12,33% |

Información adicional

8 vCPU
32GB RAM
$ restic-newindex2 version
debug enabled
restic 0.9.6 (v0.9.6-44-g26ec33dd) compiled with go1.13.6 on linux/amd64

Registros

registros_prueba3.zip

@aawsome : Todavía tengo problemas con el comando de verificación que no almacena en caché los blobs del árbol. El antiguo código llamado Repository.SetIndex que también desencadenó las llamadas a PrepareCache. Ahora se elimina la llamada a SetIndex; sin embargo, no pude encontrar un reemplazo para la llamada a PrepareCache.

@dimejo ¡Muchas gracias por las pruebas! ¡Esto brinda muy buena información y los resultados con respecto al consumo de memoria ya parecen realmente buenos!

Traté de comprender los resultados del consumo de memoria para v0.9.6, 3er respaldo, pero no pude reproducir sus resultados. ¿Puede ser que hayas utilizado el consumo de memoria del índice (102 MB) en lugar de la memoria total (204 MB)?

Tampoco entiendo por qué newindex2 muestra un mayor consumo de memoria que newindex para prune y restore . En el perfil de memoria se puede ver que la memoria utilizada por el índice es menor y el mayor consumo de memoria se debe a encoding/json.Marshal y restic.NewBlobBuffer en prune y restorer.(*packCache).get en restauración (que básicamente no están presentes en el perfil para v0.9.6). Además, el consumo de memoria mostrado por time es menor para newindex2 que para newindex.
Entonces, creo que newindex2 también tiene un consumo de memoria más bajo para estos dos comandos que newindex, pero no entiendo por qué el perfil muestra estas partes adicionales (no relacionadas con el índice) que consumen memoria.

Sobre el tiempo total: me irrita un poco que newindex y newindex2 difieran tanto y que las diferencias sean a veces positivas ya veces negativas. Traté de mirar los perfiles de CPU de algunas ejecuciones y no vi ningún uso de CPU relacionado con el índice. Por ejemplo, con las tres ejecuciones de copia de seguridad, las partes que consumen CPU en todas (v0.9.6 y newindex2) están totalmente dominadas por sha256, chunker, syscall y runtime y no veo por qué debería haber diferencias debido a una implementación de índice diferente.
¿Puede ser que la máquina no tenga una potencia de CPU constante y las diferencias no se deban a diferentes implementaciones sino a diferentes circunstancias de la máquina? Cuando comparó v0.9.6 y newindex, los resultados no variaron mucho y, por lo que parece, estas dos ejecuciones se realizaron aproximadamente al mismo tiempo.
En otras palabras: ¿es posible que vuelva a ejecutar la medición de la CPU para v0.9.6 y newindex2 al mismo tiempo? Luego, podríamos verificar si la variación en los tiempos de la CPU es común y tal vez obtener mejores conocimientos sobre los cambios en el rendimiento debido a la implementación modificada.

@MichaelEischer Gracias por informar sobre este problema que aún no se ha solucionado y siento que no haya funcionado hasta ahora.
La llamada a PrepareCache está en internal/index/masterindex.go:Load que es llamada por internal/repository/repository.go:LoadIndex .
Tendré que depurar esa parte del código.

Simplemente dejo otros resultados ad-hoc de la sucursal con un repositorio grande (~500 GB/1500+instantáneas) + copia de seguridad pequeña (1,3 GB). Muy prometedor :+1:
predeterminado-restic.txt
parcheado.txt

@MichaelEischer Encontré el problema de no usar el caché con el comando check : como la verificación reconstruye la funcionalidad de masterindex.Load() el PrepareCache debe llamarse explícitamente (estaba en repository.UseIndex() ) Esto ya está solucionado.
También solucioné el problema de rendimiento con list blobs

@seqizz ¡ Gracias por probar! Estoy bastante satisfecho con el consumo de memoria :smile:
¿Tiene una explicación de por qué el tiempo transcurrido en la ejecución parcheada es mucho mayor que en la ejecución predeterminada? No puedo explicar este comportamiento por los cambios de índice. ¿Observó una paralelización reducida debido a la implementación diferente o es un efecto que se puede explicar desde dentro del sistema que utilizó?

Por cierto: los tiempos totales de CPU parecen ser comparables entre las dos ejecuciones. Esto es exactamente lo que esperaba.

@aawsome , diría que este fue el efecto secundario de las pruebas incorrectas realizadas por mí. Aunque forget las instantáneas creadas entre ejecuciones, parece que fue lento porque ejecuté primero la versión parcheada. Tal vez el almacenamiento en caché del sistema operativo en nuestro lado de almacenamiento (Minio) afectó el tiempo de respuesta.

Ahora, como una ejecución de control, construí el restic upstream (en lugar de la versión de lanzamiento anterior, ya que construí su rama upstream) y ejecuté esta versión upstream primero. Muestra que puede ignorar los tiempos transcurridos, ya que depende del orden.

restic-upstream.txt
restic-parcheado.txt

Traté de comprender los resultados del consumo de memoria para v0.9.6, 3er respaldo, pero no pude reproducir sus resultados. ¿Puede ser que hayas utilizado el consumo de memoria del índice (102 MB) en lugar de la memoria total (204 MB)?

Buena captura, probablemente un error de copia y pasado. Ahora está corregido en mi publicación anterior.

Tampoco entiendo por qué newindex2 muestra un mayor consumo de memoria que newindex para podar y restaurar. En el perfil de memoria, puede ver que la memoria utilizada por el índice es menor y el mayor consumo de memoria se debe a la codificación/json.Marshal y restic.NewBlobBuffer en pruneand restorer.(*packCache).get in restore (que básicamente no son presentes en el perfil para v0.9.6).
Entonces, creo que newindex2 también tiene un consumo de memoria más bajo para estos dos comandos que newindex, pero no entiendo por qué el perfil muestra estas partes adicionales (no relacionadas con el índice) que consumen memoria.

TBH, no tengo idea de lo que pasó allí. Traté de reproducir los resultados en la misma VM pero no pude.

Además, el consumo de memoria mostrado por el tiempo es menor para newindex2 que para newindex.

He comparado los resultados mostrados por tiempo de todas las ejecuciones de mi 4. prueba (ver más abajo), y no parecen ser demasiado confiables. Para algunas pruebas, el consumo de memoria entre la ejecución de perfiles de cpu y mem varía entre un 15 y un 20 %.

restic-orig_test4_cpu_findblob: 253,12MB
restic-orig_test4_mem_findblob: 307,44MB
restic-newindex2_test4_cpu_catblob: 184,12MB
restic-newindex2_test4_mem_catblob: 216,56MB
restic-newindex2_test4_cpu_findblob: 174,81MB
restic-newindex2_test4_mem_findblob: 202,38MB

¿Puede ser que la máquina no tenga una potencia de CPU constante y las diferencias no se deban a diferentes implementaciones sino a diferentes circunstancias de la máquina? Cuando comparó v0.9.6 y newindex, los resultados no variaron mucho y, por lo que parece, estas dos ejecuciones se realizaron aproximadamente al mismo tiempo.
En otras palabras: ¿es posible que vuelva a ejecutar la medición de la CPU para v0.9.6 y newindex2 al mismo tiempo? Luego, podríamos verificar si la variación en los tiempos de la CPU es común y tal vez obtener mejores conocimientos sobre los cambios en el rendimiento debido a la implementación modificada.

La máquina virtual que utilicé no tiene núcleos dedicados. Especialmente con 8 núcleos, Restic ni siquiera está cerca de usar toda la potencia de CPU disponible. Por eso pensé que no importaría mucho para mis pruebas. Ahora he repetido la prueba con CPU dedicadas y los resultados son mucho más uniformes. Parece que eso es lo que causó la discrepancia.

Resultados

tiempo transcurrido (en mm:ss)

| | v0.9.6 | nuevoíndice | nuevoíndice2 | diferencia (v0.9.6 y newindex2) |
| :--- | ---: | ---: | ---: | ---: |
| repositorio de inicio | 00:26,2 | 00:48,7 | 00:58,4 | +122,79% |
| 1ra copia de seguridad | 32:51,8 | 32:12,1 | 32:49,0 | -0,14% |
| segunda copia de seguridad | 08:40,2 | 08:31,2 | 08:30,3 | -1,90% |
| 3er respaldo (--force) | 21:40,5 | 21:43,6 | 21:41,0 | +0,04% |
| olvidar | 00:00,8 | 00:00,7 | 00:00,8 | -7,98% |
| ciruela pasa | 00:43,7 | 00:43,8 | 00:42,2 | -3,56% |
| comprobar | 00:26,3 | 00:26,9 | 00:26,4 | +0,30% |
| lista de manchas | 00:03,5 | 00:04,4 | 00:04,6 | +29,46% |
| gota de gato | 00:01,7 | 00:01,8 | 00:01,8 | +9,04% |
| encontrar gota | 00:17,7 | 00:17,3 | 00:16,7 | -5,95% |
| restaurar | 12:40,2 | 13:20,3 | 12:24,1 | -2,12% |

uso de memoria (en MB)

| | v0.9.6 | nuevoíndice | nuevoíndice2 | diferencia (v0.9.6 y newindex2) |
| :--- | ---: | ---: | ---: | ---: |
| repositorio de inicio | 32,05 | 32,02 | 32,02 | -0,09% |
| 1ra copia de seguridad | 242,38 | 170,70 | 161,80 | -33,25% |
| segunda copia de seguridad | 224,63 | 154,78 | 146,69 | -34,70% |
| 3er respaldo (--force) | 218,38 | 159,48 | 150,07 | -31,28% |
| olvidar | 32,01 | 32,24 | 32,05 | +0,12% |
| ciruela pasa | 183,05 | 110,84 | 113,29 | -38,11% |
| comprobar | 195,05 | 74,28 | 70,33 | -63,94% |
| lista de manchas | 33,32 | 29,45 | 25,45 | -23,62% |
| gota de gato | 92,94 | 49,02 | 33,13 | -64,35% |
| encontrar gota | 111,51 | 29,62 | 25,41 | -77,21% |
| restaurar | 292,94 | 217,41 | 206,77 | -29,42% |

Información adicional

Máquina virtual utilizada:

8 vCPU (dedicated CPUs)
32GB RAM
$ restic-orig version
debug enabled
restic 0.9.6 (v0.9.6-40-gd70a4a93) compiled with go1.13.6 on linux/amd64



md5-6ad2d5d28ba7c01c107571884b108e74



$ restic-newindex version
debug enabled
restic 0.9.6 (v0.9.6-43-g5db7c80f) compiled with go1.13.6 on linux/amd64



md5-6ad2d5d28ba7c01c107571884b108e74



$ restic-newindex2 version
debug enabled
restic 0.9.6 (v0.9.6-44-g26ec33dd) compiled with go1.13.6 on linux/amd64

Registros

registros_prueba4.zip

@dimejo Buen trabajo, ¡muchas gracias!
El rendimiento del blob de lista (y el uso de la memoria) mejoró mucho después de la confirmación 908c8977df05ea88dcad86c37f39d201468446ad.

Los otros resultados ya parecen bastante buenos. Actualmente estoy buscando si el índice puede ahorrar algo de memoria para backup y restore usando una pequeña refactorización de estos comandos (como ya se hizo con check ).

Luego trabajaré en las pruebas y haré un PR.
@rawtaz Mi pregunta sobre qué parte de este trabajo debe separarse para que una RP sea aceptable sigue abierta. Necesitaré algunas instrucciones aquí si los cambios son demasiado grandes para que los mantenedores los manejen.
Sigo convencido de que la reimplementación completa del índice aquí es clave para estos buenos resultados en la reducción del consumo de memoria.

@aawsome Gracias por arreglar el comando de verificación. Lo probaré.

Con respecto a sus optimizaciones para el comando check , es posible que desee echar un vistazo a mi solicitud de extracción n.º 2328, que logra reducir el uso de la memoria un poco más y es mucho más rápida para los repositorios que contienen más de unas pocas instantáneas.

Después de observar la diferencia entre el índice maestro y el refactor, tengo la impresión de que casi la mitad de los cambios se deben a mover el código entre repository y index . Sin embargo, esto no está claro cuando se miran las confirmaciones. ¿Es este movimiento realmente necesario?
Tampoco estoy muy convencido de tener una referencia al repositorio almacenado en MasterIndex. La forma anterior de permitir que el Repositorio controle el MasterIndex parece estar mejor separada en myview. Aunque, probablemente sea una buena idea mover el respaldo del formato de índice actual al antiguo a la implementación del índice.
Estoy de acuerdo en que probablemente no sea posible reducir mucho la cantidad total de cambios sin perder funcionalidad, pero probablemente ayudaría mucho tener que mirar un conjunto de confirmaciones con menos de unos pocos cientos de líneas cada una.

@aawsome Todavía hay un error con el almacenamiento en caché de treePacks. La línea https://github.com/aawsome/restic/blob/908c8977df05ea88dcad86c37f39d201468446ad/internal/repository/repository.go#L97 debería decir treePacks.Insert(pb.PackID) . Esto también debería reducir un poco más el uso de la memoria.

La implementación actual en restic solo almacena en caché paquetes que contienen solo árboles. Esto es equivalente a la nueva implementación para paquetes creados con versiones recientes de restic. Sin embargo, en versiones restic antiguas (creo que <0.8) los datos y los blobs de árbol se mezclan. Estos blobs de datos no deben almacenarse en caché. Por lo tanto, sugeriría agregar una segunda iteración sobre todos los blobs y quitar los paquetes que contienen blobs de datos nuevamente.

@MichaelEischer
Gracias por encontrar otro error de almacenamiento en caché. Espero que ahora sea realmente el último. Y lo siento por no poder encontrarlo; Hasta ahora, solo usé el backend local y no hay una prueba estándar que verifique si el almacenamiento en caché está configurado correctamente ... Tal vez debería agregarse tal prueba ...

Esto (y el antiguo problema de formato de repositorio) se solucionó en la última confirmación.

También muchas gracias por tu otro comentario!
Acerca check Estoy de acuerdo en que hay aún más potencial de optimización de memoria (y, por supuesto, velocidad). Ya estaba pensando en hacer que el índice pudiera guardar diferentes indicadores definidos por el usuario para blobs. De esa manera, puede guardar enormes IDSet s o mapas definidos por el usuario mientras guarda la ID de blob solo una vez. Esto es lo que estaba pensando al optimizar prune en un segundo paso.
Su PR #2328 se puede adaptar fácilmente para usar banderas de índice en lugar del mapa definido por el usuario y obtenemos una reducción de memoria adicional.

Estoy de acuerdo con su problema de diseño y eliminé repo de MasterIndex . Por lo tanto, también los comandos Load y Save volvieron a repository.go haciendo que el diif para dominar sea un poco más pequeño.
Sin embargo, no creo que el objetivo principal deba ser tener una fusión lo más pequeña posible. Me gustaría implementar el mejor diseño. Acerca de dónde ubicar la implementación index , ya tenemos dos lugares en el maestro: internal/index y internal/repository . Como las cosas relacionadas con el índice son bastante código, pensé que debería separarse de repository , por lo que elegí internal/index como la ubicación para colocar todo sobre el índice (y también definir una nueva interfaz en internal/restic/index.go ). Todo el código en index_new.go , index_old_format.go y master_index.go está escrito desde cero y index.go está muy adaptado mientras mantiene la funcionalidad original utilizada en rebuild-index y prune .
Por otro lado, todo lo relacionado con el índice (excepto cargar/guardar) se elimina de internal/repository .

Acerca de la optimización backup y restore :

  • Con backup agregué la posibilidad de agregar blobs "conocidos" al índice. Como estos se eliminan una vez que se agrega el blob "conocido" al índice, esto debería ahorrar una gran cantidad de memoria utilizada en backup . Espero que el uso de la memoria principal ahora se deba al espacio reservado para el chunker paralelo, si no tienes un repositorio realmente ENORME.
  • Por restore no encontré ninguna optimización relacionada con el índice. Parece que todo el uso de la memoria se debe a la forma en que restore funciona internamente. Tengo la sensación de que hay posibilidades de optimización, pero esto debería hacerse en un trabajo separado.

Entonces, para mí, solo está abierto trabajar en la implementación de prueba (si no hay otros errores que se informen). Entonces haré un PR.

@aawsome Hola, perdón por tardar tanto en responder. Siento que no puedo darle instrucciones detalladas relevantes, pero lo que estaba pensando era dividir cosas como "código de refactorización en preparación para los próximos cambios" (como cambiar el manejo del índice) y "código de refactorización para optimizar o agregar características " (como las optimizaciones reales y las cosas que busca aquí) en confirmaciones separadas o incluso relaciones públicas (depende del código).

Estoy tratando de que veas esto con mejores cosas que decir que yo, ¡porque creo que tu trabajo aquí se ve interesante!

Además, agradezco que hayas comenzado esta contribución abriendo un problema (en lugar de simplemente abrir un PR), ¡gracias por eso! Siempre que sea posible, es bueno si podemos tener una discusión sobre las sugerencias antes de que se realice la implementación real. Al hacerlo, podemos encontrar un terreno común y establecer una dirección que tenga a todos a bordo (y, a menudo, surgen problemas o perspectivas inesperados que cambian la forma en que se realizaría la implementación, lo que al final hace que se desperdicie menos trabajo).

@rawtaz ¡ Gracias por tu respuesta!

Tienes razón en que durante el desarrollo traté de moverme rápido y ser capaz de probar qué cosas funcionan y qué no. Por ahora, parece que el cambio de código es lo suficientemente maduro como para discutir cómo se puede integrar mejor en el maestro.

Creo que ayuda a la discusión dar una visión general de los cambios que he hecho y las razones por las que hice cada cambio. La implementación cubre los siguientes cambios:

Cambios principales:

  • la estructura del código base, es decir, qué funcionalidad reside en internal/repository y internal/index
  • estructura de definiciones de interfaz en internal/restic
  • estructura interna de MasterIndex, incluido el manejo de archivos de índice "completos"
  • la estructura de datos relacionada con el archivo de índice
  • soporte para archivos de índice de formato antiguo ( index_old.go )
  • la nueva estructura de datos del índice en memoria ( index_new.go )
  • cambio de índice de acceso donde sea necesario

Cambios adicionales menores:

  • cambió Lookup y agregó LookupAll
  • reemplazado 'Tienda' por StorePack EDITAR: se implementa en #2773
  • agregar CheckSetKnown para manejar blobs conocidos durante la copia de seguridad EDIT: se implementa en #2773
  • elimine el blob IDSet de checker.go y reemplácelo por la funcionalidad de índice
  • use la nueva funcionalidad de índice en 'list blobs'

Los principales cambios son, en mi opinión, la clave para lograr un bajo consumo de memoria, un buen rendimiento y un diseño de código más limpio (en comparación con la implementación real). Los cambios menores son ganancias rápidas que también podrían ser relaciones públicas independientes. Sin embargo, si se supone que son buenas mejoras, preferiría mantenerlas en el PR principal, de lo contrario, probablemente deban implementarse dos veces o deban posponerse.

Estructura del código base

Moví la mayoría de las funciones relacionadas con el índice de internal/repository a repository/index . En mi opinión, esto hace que la estructura base del código sea más clara y también separa claramente qué código está en qué directorio (a diferencia de la implementación real.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Estructura de la interfaz

También separé las definiciones de interfaz en internal/restic para que quede claro qué está relacionado con Index y qué con Repository . Esto también permite cambios más claros de las interfaces en el futuro.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Estructura de MasterIndex

Actualmente, MasterIndex combina estructuras de datos relacionadas con archivos de índice y, por lo general, itera sobre todas estas subestructuras para operaciones de índice. Cada una de las estructuras de datos relacionadas con el archivo de índice puede estar llena (es decir, lo suficientemente llena como para guardarla como un archivo de índice completo en el repositorio) o terminada (es decir, ya presente como archivo de índice).

Cambié completamente este comportamiento. Ahora MasterIndex usa un índice principal en memoria donde la mayoría de las entradas del índice deberían estar presentes, especialmente todas aquellas que también están presentes como archivos de índice en el repositorio, y una segunda estructura de datos "inacabada" que solo se usa para guardar un nuevo índice. entradas y se inserta en el índice principal en memoria una vez que el contenido se escribe como archivo de índice en el repositorio.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Estructura de datos relacionada con el archivo de índice

Actualmente, hay tres definiciones de estructuras de datos relacionadas con el archivo de índice: una definida por JSON en internal/index , una definida por JSON en internal/repository y una optimizada para búsqueda en internal/repository .
Decidí usar la estructura de datos JSON de internal/index y agregué los métodos necesarios para usarla en MasterIndex (y eliminé por completo las implementaciones de internal/repository ). Esto elimina por completo la conversión de estructuras de datos internas para guardar archivos de índice durante la copia de seguridad. Por otro lado, las operaciones de búsqueda deben recorrer todas las entradas del índice. Por lo tanto, es fundamental que esta estructura de datos no contenga muchas entradas durante la búsqueda, es decir, se inserta periódicamente en el índice principal.

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Compatibilidad con el formato de archivo de índice antiguo

La compatibilidad con nuevos formatos de archivo de índice ahora se subcontrata a index_old_format.go .

Pregunta a los mantenedores: ¿Reconocen este cambio principal?

Nueva estructura de datos de índice en memoria ( index_new.go )

Este es obviamente el mayor cambio. En principio, se mantiene una gran lista de blobs por tipo de blob. La lista está ordenada por ID de blob, de modo que la búsqueda se puede realizar mediante la búsqueda binaria. Dado que la optimización apunta a muchas entradas en esta lista, el tamaño de cada entrada se minimiza tanto como sea posible. Esto significa que la identificación del paquete se almacena en una tabla separada y solo se guarda una referencia y el desplazamiento y la longitud son uint32 (limitando efectivamente el tamaño máximo del paquete a 4 GB). EDITAR: estas dos optimizaciones se implementan en #2781.
Una matriz ordenada simple ya ahorra la sobrecarga de memoria que se encuentra dentro de map pero aún genera cierta sobrecarga de memoria y rendimiento dentro append : se reserva un "búfer" adicional para adiciones futuras y los datos deben ser copiado. Por lo tanto, implementé una matriz "paginada" con una sobrecarga de memoria pequeña y limitada y sin necesidad de copiar para agregar.

Las pruebas que se han realizado indican que este índice es un poco más rápido que la implementación estándar de MasterIndex y ahorra más del 75% de la memoria.

Pregunta a los mantenedores: ¿La implementación de este índice cumple con sus expectativas?

Cambió Lookup y agregó LookupAll

Lookup en realidad devuelve más de un resultado (pero no si están distribuidos en muchos archivos de índice, por cierto). En muchos casos, solo se utiliza el primer resultado. Por lo tanto, cambié Lookup para devolver solo un resultado y agregué LookupAll para usar cuando se usa más de un resultado. Hace que el código sea un poco más claro y también ahorra algunos ciclos de CPU innecesarios.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Se reemplazó 'Tienda' por StorePack

Store almacena solo un blob en el índice, pero solo se llama para el contenido de un paquete completo. Por lo tanto, lo cambié a una funcionalidad de StorePack donde se guarda todo el contenido del paquete. Esto hace que el código sea más claro, el almacenamiento en formato JSON sea mucho más fácil y también más eficaz.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

agregue CheckSetKnown para manejar blobs conocidos durante la copia de seguridad

Las pruebas mostraron que para muchos archivos nuevos, backup usa bastante memoria para almacenar qué blobs se agregaron durante esta ejecución de copia de seguridad. Esto es para no guardar blobs dos veces mediante protectores paralelos. Moví esta funcionalidad al índice ya que esta información de "blob conocido" se puede eliminar una vez que se agrega la entrada correspondiente al índice. Con este cambio, solo se "conocen" muy pocos blobs pero no (todavía) en el índice.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Elimine el blob IDSet de checker.go y reemplácelo por la funcionalidad de índice

Checker crea su propio índice en memoria y, además, mantiene todos los ID de blob en un IDSet adicional. Como el índice ya conoce todos los blobs, esto se puede reemplazar por el método Each del índice.
Esto ahorra memoria adicional dentro check .

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Use la nueva funcionalidad de índice en 'blobs de lista'

En lugar de leer todo el índice en la memoria y luego recorrer todas las entradas para imprimir la lista de blobs, cada archivo de índice se puede cargar por separado en la memoria y recorrer estos contenidos. Hace que list blobs consuma menos memoria.

Pregunta para los mantenedores: ¿Está bien mantener este cambio o preferiría eliminarlo del índice PR?

Un pequeño resultado de prueba @aawsome , no puedo hacer una copia de seguridad en un repositorio existente después de la última confirmación.
Valores desde time :

|---|Copia de seguridad "normal"| fix masterindex.Load() compromiso|último compromiso|
|---|---|---|---|
|Uso de memoria|~650Mb|~320Mb|~55Mb|
|Tiempo de copia de seguridad|~12 segundos|~12 segundos|(se rindió después de 5 minutos)|

El almacenamiento informa que detiene la actividad después de consultar los índices. (Lo siento si eso fue intencionado debido al cambio de formato)

@seqizz Gracias por enviar el informe de error. Debo admitir que solo probé la última confirmación con go run build.go -T que no mostró ningún problema... Lo arreglé en la confirmación 5b4009fcd275794e3ce05304fce255d5fa3e1864

@aawsome Gracias por escribir ese comentario resumido . Una de las cosas que experimenté como desarrollador no central (solo soy un mantenedor o cosas simples) es que durante el tiempo que el desarrollador principal se dedica a otras partes del proyecto, problemas como este siguen creciendo. Luego, cada vez que un desarrollador central lo mira, es mucho o, en algunos casos, demasiado para profundizar en ese momento. Cuanto menos haya para leer para ellos, mayores serán las posibilidades de que podamos hacer un buen uso de su tiempo. Me tomé la libertad de editar la publicación inicial de este número para agregar una referencia a su comentario resumido.

Con respecto a sus preguntas, personalmente no puedo responderlas. Tendré que aplazarlos un tiempo, y les pido amablemente su paciencia. Nuestro enfoque principal en este momento con el tiempo disponible que tenemos son otras cosas, pero puedo decirles que este problema y sus relaciones públicas están en mi radar y estoy tratando de verlos eventualmente.

Creo que con su comentario de resumen, además de corregir errores (estoy un poco preocupado de que pueda haber algunos errores más pequeños al acecho) y cualquier cosa que pueda encontrar que no se desvíe de lo que escribió en su comentario, lo más productivo es para dejar que esto descanse un poco. El siguiente paso sería poner los ojos del desarrollador central en ello, y hasta que eso suceda, probablemente sea más contraproducente hacer crecer más la discusión. Avíseme si mi razón con esto no parece tener sentido, para que pueda aclarar lo que quiero decir.

Finalmente encontré tiempo para probar el rendimiento del comando de copia de seguridad en un repositorio de archivos de 8 TB y 40 millones. Para mantener bajo el tiempo de ejecución general, acabo de hacer una copia de seguridad de un directorio de 6 GB. El uso de CPU de restic se limitó usando GOMAXPROCS=1 .

maestro actual: cpu-idx-master.pdf
este pr: cpu-idx-table.pdf

Con este PR Restic usa alrededor de 6,5 GB de memoria en lugar de los 18 GB del maestro actual, lo que es una mejora gigantesca. El índice tarda alrededor de 9 minutos en cargarse en ambos casos (el host no es compatible con AES-NI, por lo que el descifrado es un poco lento). La copia de seguridad tarda 34 minutos para este PR y 18 minutos para el maestro actual. Echar un vistazo a los gráficos de perfiles de CPU muestra la diferencia: indexTable dedica 16 minutos solo a ordenar el índice en memoria. El índice es lo suficientemente grande como para que una única llamada de clasificación para el índice en memoria tarde unos 75 segundos (!) en completarse. Durante este tiempo, todo el procesamiento de la copia de seguridad se bloquea ya que el índice se bloquea exclusivamente.

El índice en memoria probablemente necesite una forma de fusionar un nuevo índice sin recurrir a todo, tal vez incluso implementado de forma incremental. Esto debería ser posible ordenando el nuevo índice de antemano y luego combinándolo en el MasterIndex con un solo escaneo sobre el indexTable.

@MichaelEischer ¡ Muchas gracias por hacer su prueba! De hecho, la implementación actual necesita un recurso de la tabla de índice cada vez que se escribe un archivo de índice. Entonces, si calculé correctamente, se crearon 13 nuevos archivos de índice, ¿verdad?
El buen mensaje es que las búsquedas en IndexJSON (que no están ordenadas y, por lo tanto, requieren una búsqueda lineal) no aparecen en absoluto, por lo que esta parece ser una buena manera de manejar archivos de índice "inacabados".

En realidad, mi juicio es que recurrir a una tabla casi ordenada con sort.Sort debería ser casi óptimo. (Sin embargo, probar sort.Stable o un mergesort puro debería valer la pena intentarlo...) Creo que el problema principal es: si queremos tener una matriz de índice ordenada e insertar cosas en ella, necesitamos para mover ~6,5 GB de datos en la memoria.
Así que propongo separar las entradas de índice leídas de los archivos de índice de las recién generadas (y ya guardadas). Esto crea tres estructuras de datos en la implementación de MasterIndex. Ya cambié esto en mi rama refactor-index . ¿Le importaría volver a probar su configuración?

Cuando hago zoom en los datos del perfil, IndexJSON tarda 1,22 segundos en total. Así que esto parece estar bien por ahora. El peor de los casos sería una gran cantidad de archivos pequeños en caso de que un mapa pudiera ser más rápido.

La ejecución de la copia de seguridad en realidad creó 16 nuevos archivos de índice. Los 75 segundos fueron una conjetura de una ejecución de check que solo activa la reclasificación una vez. Tanto quicksort (utilizado por sort.Sort) como mergesort tienen una complejidad de tiempo en el mejor de los casos de O (n log n), por lo tanto, los datos preordenados _no mejoran el rendimiento._ Como nota al margen, el índice en memoria requiere menos espacio que el representación en disco ^^ .

Volví a ejecutar la prueba con el código actualizado y el rendimiento es mucho mejor: cpu-idx-table-split.pdf

Sin embargo, el uso de un índice separado para las nuevas entradas de índice simplemente oculta, pero no resuelve, el problema subyacente: solo imagine una ejecución de copia de seguridad que agrega algunos millones de archivos nuevos o una gran copia de seguridad inicial. La implementación actual tiene una complejidad O(n * n*log n) . Los primeros n se relacionan con la cantidad de anexos al índice que se escala de manera algo lineal con la cantidad de nuevos datos de copia de seguridad. Y la última parte de n*log n es la complejidad ya mencionada para ordenar el índice. Al reemplazar la ordenación rápida con una combinación lineal, esto se reduce a O(n * n) lo que podría ser lo suficientemente bueno para tamaños de índice por debajo de varias docenas de gigabytes. Más allá de eso, no escalará, pero supongo que los repositorios de Petabyte son un problema en sí mismos. La combinación lineal podría incluso implementarse para que funcione de forma incremental. Tengo una idea de cómo reducir la complejidad a O(n * log n) pero eso aumentaría los costos de búsqueda a O(log^2 n) y requeriría algunos cambios extensos en el código.

Me pregunto si existe una estructura de datos que se ajuste perfectamente a nuestras necesidades:

  • debe tener poca sobrecarga de memoria
  • velocidad de consulta sublineal amortizada, preferiblemente log n o menos
  • costos de inserción sublineales amortizados, preferiblemente log n o menos. Con n inserciones, esto generaría costos de O(n * log n) para una copia de seguridad completa

Al eliminar el requisito de una representación en memoria realmente compacta, los hashmaps encajan perfectamente, ya que sus consultas y actualizaciones ocurren en O(1) . Sin embargo, requieren mucha más memoria: mi prototipo actual usa aproximadamente un 70 % más de memoria que este PR.

Una solución sería combinar ambos enfoques y recopilar nuevas entradas en un hashmap y combinarlas en la matriz ordenada de vez en cuando (por ejemplo, cuando el tamaño del hashmap alcanza el 10 % de la matriz ordenada).

Dicho esto, probablemente sea mejor esperar los comentarios de @fd0 antes de realizar cambios importantes. (Perdón por hacer crecer la discusión de nuevo).

Me pregunto si existe una estructura de datos que se ajuste perfectamente a nuestras necesidades:

Diría que hay "muchas" estructuras de datos que satisfacen más o menos estas necesidades, pero no sé cuánto esfuerzo sería cambiar a ellas en restic. Básicamente, cualquier árbol B o, en general, las estructuras de árbol deberían satisfacer estas necesidades (los mapas hash son realmente ineficientes para tales escenarios, como también descubrió usted mismo en sus mediciones).

Si no me equivoco, un B-Tree solo garantiza que sus nodos estén al menos medio llenos. Y requeriría punteros que agregarían más o menos un puntero de 8 bytes por cada 48 bytes de datos. Si un B-Tree garantiza un uso de espacio del 70% en promedio, terminaría con una sobrecarga de memoria similar a la de mi implementación basada en hashmap. Sin embargo, puede haber algunas variantes como B*-Trees que son más eficientes.

@michaeldorner sí, ese es el "viejo árbol B tradicional": como escribiste, hay muchas (he visto al menos diez bastante diferentes hasta ahora) estructuras tipo árbol B, cada una con propiedades diferentes. Otras estructuras similares a árboles bien conocidas incluyen, por ejemplo, árboles rojo-negro (populares para mapas que deben garantizar un tiempo y espacio de inserción/eliminación consistentes; esto contrasta con los mapas basados ​​en hash que pueden sufrir "saltos" u otros picos excesivos tanto en tiempo y espacio, lo que eventualmente conduce a graves problemas de seguridad/rendimiento ). También hay varios mapas "comprimidos"; siéntase libre de buscarlos. Así que hay realmente una serie de opciones.

Aunque no estoy en contra de usar un archivo temporal pequeño como se discutió en este tema y como, por ejemplo, sqlite lo hace, por cierto. sqlite es muy eficiente y lo he usado como almacenamiento solo con el propósito de un uso de memoria extremadamente bajo, al mismo tiempo que ofrece un rendimiento bastante alto a pesar de usar discos duros como caché temporal; siéntase libre de probarlo, puede que lo sorprenda.

Solo mis 2 centavos: guiño:.

La matriz ordenada y densamente empaquetada de entradas de índice es básicamente el estándar de oro para el uso de la memoria. Sí, podría aplicar algo de compresión, pero eso solo ayudaría con el tamaño/desplazamiento del blob y el idx del idx del paquete (aprox. 16 bytes), los 32 bytes restantes son el id del blob (hashes sha256) que supongo que son básicamente incompresibles. No estoy seguro de si el potencial de otro 20-30% menos de uso de memoria justificaría la complejidad aún mayor.

Los árboles siempre requieren uno (y algo de espacio no utilizado como con B-Trees) o dos punteros (rojo-negro-árbol y otros) por elementos que ya agregarían 8 o 16 bytes de sobrecarga a los aprox. 48 bytes de la propia entrada de índice. Tampoco estoy seguro de qué tan bien manejaría el recolector de basura Go millones o incluso miles de millones de entradas de índice.

Para salvar el honor de las tablas hash: se dice que cuckoo hashing (tiempo de búsqueda constante) junto con 4 entradas por cubo logra más del 90% de utilización del espacio. Si solo usa diez de estas tablas hash, la sobrecarga total de la memoria también se mantendría por debajo del 33% incluso mientras crece un mapa hash.

Creo que la pregunta principal es más si queremos una estructura de datos única (árbol/hashmap) con cierta sobrecarga de memoria. O si queremos la sobrecarga más baja de una matriz empaquetada ordenada, que necesitará una estructura de datos auxiliar para proporcionar una velocidad de inserción razonable. Si esa estructura de datos auxiliares solo contiene del 10 al 20 % de los datos del índice general, entonces no importará mucho si una estructura de datos tiene una sobrecarga del 30 % o del 70 % y, en ese caso, el hashmap integrado de Go sería la solución más simple.

El uso de un índice en disco tiene sus propios problemas: si el índice es demasiado grande para guardarlo en la memoria, es probable que el rendimiento del índice implosione en los HDD (los SSD deberían estar mucho mejor) a menos que el índice pueda beneficiarse de localidad entre blobs para, por ejemplo, una carpeta/archivo (en una lista completamente ordenada de ID de blob, el patrón de acceso sería básicamente uniformemente aleatorio).

@MichaelEischer gracias por el resumen. Aún así, lo animo a probar el caché de disco (solo para tener una idea del posible rendimiento, no para incorporarlo en Restic) usando sqlite3 con diferentes configuraciones (por ejemplo, con y sin índice, con mayores restricciones de memoria máxima que el muy bajo valores predeterminados, usando solo una tabla como almacenamiento tipo mapa de clave-valor, etc.) mientras aprovecha algunas peculiaridades de sqlite3 como la clave primaria integrada integrada y el acceso multiproceso sin bloqueo explícito (sqlite lo maneja solo) y usa una gran transacción para todos los accesos y no usando WAL ya que es significativamente más lento para las lecturas (pero un poco más rápido para las escrituras).

El uso de caché de disco también podría beneficiarse significativamente del procesamiento orientado a la transmisión, pero no sé si es una buena opción para una prueba rápida. Solo una idea basada en mi buena experiencia con el almacenamiento de clave-valor basado en sqlite3: guiño:.

@aawsome ¿Podría dividir su optimización de "lista de blobs" en un PR separado?

No planeo fusionar la eliminación de $ blob IDSet de checker.go ya que #2328 también proporciona esa optimización pero sin tener que involucrar al índice maestro.

Eso deja LookupAll como el último cambio menor. Esa función sería útil al agregar soporte para la poda de repositorios dañados. Sin embargo, sin algunas optimizaciones de rendimiento de índice primero, probablemente solo reducirá el rendimiento general de restic. Así que esto tendrá que esperar.

Con respecto a los cambios principales, estos también deben permanecer en espera por ahora.

@dumblob El problema con el uso de sqlite o cualquier otra base de datos genérica para un índice en disco es que sin optimizaciones que hagan uso de la localidad entre blobs en un archivo (es decir, los blobs dentro de un archivo terminan en paquetes cercanos y generalmente se accede a ellos juntos), terminaremos con un acceso pseudoaleatorio a todas las partes del índice que es más grande que la memoria principal y, por lo tanto, tiene que cargar datos desde ubicaciones aleatorias en el disco. Esa es una implementación de base de datos genérica que no tiene ninguna posibilidad de proporcionar un buen rendimiento.

@aawsome ¿Podría dividir su optimización de "lista de blobs" en un PR separado?

Seguro. PR vendrá pronto.

Eso deja LookupAll como el último cambio menor. Esa función sería útil al agregar soporte para la poda de repositorios dañados. Sin embargo, sin algunas optimizaciones de rendimiento de índice primero, probablemente solo reducirá el rendimiento general de restic. Así que esto tendrá que esperar.

De hecho, la implementación real de Lookup es (parcialmente) LookupAll en el sentido de que devuelve todos los resultados dentro de un archivo de índice, pero solo los resultados del primer archivo de índice que tiene una coincidencia .

Como el caso habitual es que no hay duplicados dentro de un archivo de índice, esto en realidad no proporciona duplicados incluso en los casos en que podrían ser necesarios.

Prepararé un PR para cambiar Lookup y agregar LookupAll donde sea útil. Entonces podemos discutir si esto es útil o no.

¿Fue útil esta página
0 / 5 - 0 calificaciones