Saltar al contenido principal

Servicios

Para poder organizar y usar rac-delta correctamente, es importante atomizar las responsabilidades del protocolo en distintos servicios.

El core de rac-delta se compone de cuatro servicios principales que conectarán cada operación del protocolo.

Servicio hasher​

Este servicio se encargará de implementar el hashing para crear los hashes de los archivos y los chunks, para verificar la integración y para generar los objetos FileEntry y Chunk con sus hashes.

Aquí está la abstracción del servicio, lista para ser implementada con la librería que prefieras.

Servicio hasher abstracto en TypeScript
interface StreamingHasher {
update(data: Uint8Array | Buffer): void;
digest(encoding?: 'hex'): string;
}

interface HasherService {
hashFile(filePath: string, rootDir: string, chunkSize: number): Promise<FileEntry>;

hashStream(
stream: AsyncChunkStream,
chunkSize: number,
onChunk: (data: Uint8Array, chunk: Chunk) => void
): Promise<void>;

hashBuffer(data: Uint8Array): Promise<string>;

verifyChunk(data: Uint8Array, expectedHash: string): Promise<boolean>;

verifyFile(path: string, expectedHash: string): Promise<boolean>;

createStreamingHasher(): Promise<StreamingHasher>;
}

Servicio de validación​

El servicio de validación se usa únicamente para validar archivos e índices. Usa el servicio hasher internamente para esto. Tiene métodos básicos que devuelven booleanos.

Servicio de validación abstracto en TypeScript
interface ValidationService {
validateFile(entry: FileEntry, path: string): Promise<boolean>;

validateIndex(index: RDIndex, basePath: string): Promise<boolean>;
}

Servicio delta​

El servicio delta se encarga principalmente de crear los rd-index.json, compararlos y fusionarlos.

Comparar dos archivos rd-index generarán un Delta Plan que incluye los cambios a subir o descargar.

Servicio delta abstracto en TypeScript
interface DeltaService {
createIndexFromDirectory(
rootPath: string,
chunkSize: number,
concurrency?: number,
ignorePatterns?: string[]
): Promise<RDIndex>;

createFileEntryFromStream(stream: AsyncChunkStream, path: string): Promise<FileEntry>;

compare(source: RDIndex, target: RDIndex | null): DeltaPlan;

mergePlans(base: DeltaPlan, updates: DeltaPlan): DeltaPlan;

compareForUpload(localIndex: RDIndex, remoteIndex: RDIndex | null): Promise<DeltaPlan>;

compareForDownload(localIndex: RDIndex | null, remoteIndex: RDIndex): Promise<DeltaPlan>;
}

AsyncChunkStream​

Como puedes ver arriba, createFileEntryFromStream usa AsyncChunkStream, un objeto que extiende AsyncIterable para una experiencia más personalizada:

interface AsyncChunkStream extends AsyncIterable<Uint8Array> {
nextChunk(): Promise<Uint8Array | null>;
reset?(): Promise<void>;
close?(): Promise<void>;
}

Por cómo los streams se manejan de forma diferente en cada lenguaje, toma esto solo como recomendación y guia, puede diferir bastante de otros lenguajes como Python o Rust.

Servicio de reconstrucción​

El servicio de reconstrucción es un servicio complejo que se encarga de reconstruir los archivos a partir de los chunks.

Puede reconstruir un solo archivo o todos los archivos de un delta plan.

Abstracción de ejemplo del servicio de reconstrucción en TypeScript
interface ReconstructionService {
reconstructFile(
entry: FileEntry,
outputPath: string,
chunkSource: ChunkSource,
options?: ReconstructionOptions
): Promise<void>;

reconstructAll(
plan: DeltaPlan,
outputDir: string,
chunkSource: ChunkSource,
options?: ReconstructionOptions
): Promise<void>;

reconstructToStream(entry: FileEntry, chunkSource: ChunkSource): Promise<Readable>;
}

Solo tiene tres métodos públicos, pero tendrá tantos métodos privados como quieras hacer de complejo el servicio. (Reconstrucción temporal, in-place, umbrales, validación, streaming...)

ChunkSource​

Como puedes ver arriba, cada método necesita un ChunkSource, un servicio especial que conecta directamente con tu adaptador de almacenamiento, tu memoria, o tu disco, dependiendo de cómo quieras manejar tus hcunks.

Esta es la abstracción para ChunkSource:

Abstracción de ChunkSource en TypeScript
interface ChunkSource {
getChunk(hash: string): Promise<Buffer>;

getChunks?(hashes: string[], options?: { concurrency?: number }): Promise<Map<string, Buffer>>;

streamChunks?(
hashes: string[],
options?: { concurrency?: number; preserveOrder?: boolean }
): AsyncGenerator<{ hash: string; data: Readable }>;
}

Las implementaciones recomendadas son: StorageChunkSource, MemoryChunkSource y DiskChunkSource (La primera descargará los chunks desde el almacenamiento remoto, la segunda desde memoria si los chunks se descargaron primero, y la tercera lo mismo perdo desde el disco)

El uso recomendado para el servicio de reconstruccion es intentar streamChunks > getChunks > getChunk si existen, para mejor rendimiento.

ReconstructionOptions​

Para una mejor experiencia de usuario, personalización y rendimiento, hay algunos parámetros de configuración recomendados para el servicio:

interface ReconstructionOptions {
forceRebuild?: boolean;
verifyAfterRebuild?: boolean;
inPlaceReconstructionThreshold?: number;
fileConcurrency?: number;
signal?: AbortSignal;
onProgress?: (
reconstructProgress: number,
diskSpeed: number,
networkProgress?: number,
networkSpeed?: number
) => void;
}

forceRebuild​

Fuerza la reconstrucción incluso si el hash del archivo coincide. (boolean)

verifyAfterRebuild​

Verifica el hash del archivo reconstruido tras terminar. Si no coincide, se lanza un error. (boolean)

inPlaceReconstructionThreshold​

Tamaño de archivo mínimo (en bytes) necesarios para realizar una reconstrucción in-place en lugar de usar un archivo temporal. Por defecto: 400 * 1024 * 1024 (400 MB).

Reconstrucción in-place: El archivo existente es abierto y actualizado directamente sobreescribiendo solo los chunks modificados o faltantes.

Reconstrucción .tmp: El archivo es completamente reconstruido en un .tmp temporal usando todos los chunks (nuevos y existentes), luego se reemplaza sobre el archivo original.

Cúando usar:

La reconstrucción in-place se recomienda para archivos grandes, ya que evita reescribir el archivo completo y reduce drásticamente el uso del espacio en disco. Sin embargo, puede ser inseguro para ciertos formatos (ejemplo: Archivos zip o bases de datos) que son sensibles a las escrituras parciales o la corrupción. Para desactivar la reconstrucción in-place por completo, pon este valor como 0.

fileConcurrency​

Cuántos archivos se reconstruirán concurrentemente (por defecto el valor es 5). (number)

signal​

AbortSignal opcional para cancelar las operaciones de forma segura.

onProgress​

Callback que devuelve el uso del disco y una velocidad de red opcional (solo funciona con los chunk sources que usan el almacenamiento y la estrategia de streaming descarga-reconstrucción)