Contenido direccionado por su hash
El patrón para lo que se acumula y no cambia: nombrar cada fichero por el hash de su contenido. Deduplicación gratis, integridad verificable, y borrado que hay que pensar.
El patrón
pub fn guardar(dir: &Path, datos: &[u8]) -> Result<String, Error> { let hash = blake3::hash(datos).to_hex().to_string(); let ruta = ruta_de(dir, &hash); // si ya existe, es el mismo contenido: no hay nada que hacer if !ruta.exists() { std::fs::create_dir_all(ruta.parent().unwrap())?; escribir_atomico(&ruta, datos)?; // nivel 40.2 } Ok(hash)}// dos niveles de subdirectorio por los primeros caracteres del hashfn ruta_de(dir: &Path, hash: &str) -> PathBuf { dir.join(&hash[0..2]).join(&hash[2..4]).join(hash)}Y en la base de datos solo se guarda el hash, como referencia. La tabla de adjuntos tiene el hash, el nombre que el usuario le puso, el tipo y la fecha — no los bytes.
Qué sale gratis
Las cinco salen de la misma propiedad: el nombre depende del contenido y el contenido no cambia. Es un caso claro de una decisión pequeña que elimina cinco problemas en vez de resolverlos.
Por qué los subdirectorios
Un detalle práctico: no metas cien mil ficheros en un solo directorio. Los sistemas de ficheros lo permiten y se degradan —listar es lento, algunas herramientas se atragantan, y el explorador del usuario se queda colgado si abre esa carpeta—.
Partir por los primeros caracteres del hash reparte de forma uniforme por construcción: con dos niveles de dos caracteres hay 65.536 directorios posibles, y como los hashes se distribuyen bien, no hay que pensar en el equilibrio.
El borrado, que es lo que hay que pensar
La única parte que el patrón no resuelve solo: cuándo se puede borrar un fichero cuyo contenido puede estar referenciado desde varios sitios.
El barrido es lo correcto para una aplicación de escritorio: se ejecuta de vez en cuando, en segundo plano, y es idempotente — si se interrumpe, la próxima vez sigue donde estaba sin haber roto nada.
Y la regla de oro del barrido: si tienes cualquier duda sobre si un hash está referenciado, no lo borres. El coste de dejar un fichero huérfano es unos kilobytes; el de borrar uno vivo es un adjunto que desaparece.