GENRO / STORAGE LABREPORT TECNICO
MINIO · DOCKER LOCALE

Benchmark comparativo S3

Quanto costa
l’astrazione?

Genropy legacy, Genro Storage e client diretti alla prova. Tempi, richieste HTTP e controlli di correttezza per capire dove nasce la differenza.

5 raccolte concluse7 percorsi confrontatiDati locali · esplorativi
Report aggiornato con la prima ottimizzazione. Sintesi e grafici principali mostrano la raccolta più recente. I sette percorsi originali restano consultabili nelle raccolte iniziali: non sono stati rimisurati dopo la modifica.

01 / Cosa emerge

Il risultato cambia con l’operazione.

Intervenendo soltanto su Genro Storage, gli attributi del file passano da 6,70 a 1,52 ms, contro 3,27 ms del legacy nella raccolta finale. Copia e spostamento restano più lenti e non sono stati ottimizzati.

2.292campioni misurati
esclusi warmup e avvio
584test superati nella verifica finale
14 saltati, nessun fallimento
0campioni falliti
nelle cinque raccolte incluse

Il numero di richieste aiuta a spiegare i tempi.

Per gli attributi del file: da 4 a 1 richiesta nel nuovo, 2 nel legacy. Per un albero di otto file: 1 richiesta nel nuovo, 18 nel legacy. Sono due comportamenti diversi dello stesso sistema, misurati a un worker con cache dei metadati svuotata.

Le prove riguardano S3 su MinIO locale. Nessuna misura su Hetzner, SFTP o WebDAV è inclusa. La suite è già configurabile per un successivo confronto sull’object store Hetzner.

02 / Le raccolte

Stessi dati, percorsi diversi.

Le tre raccolte iniziali hanno tre ripetizioni; le due raccolte prima/dopo ne hanno nove. Ogni caso ha un round di warmup, mentre l’avvio è osservato una sola volta per client. Ordine dei casi rimescolato con seed registrato; dati sintetici deterministici e verifica del risultato fuori dal tratto cronometrato.

01

Iniziale · file piccoli

1 KiB / 64 KiB

Client
7
Concorrenza
1 / 2 worker
Ripetizioni
3
Directory
8 file
Versioni per chiave
1
Campioni misurati
1.260
33fface4-afbf-4f3e-b7f0-a5e01c4d924e
02

Iniziale · trasferimenti

1 MiB / 32 MiB

Client
7
Concorrenza
1 / 4 worker
Ripetizioni
3
Directory
8 file
Versioni per chiave
1
Campioni misurati
420
72e35711-6375-45c6-af71-df03d301f4f3
03

Iniziale · oggetti versionati

1 KiB

Client
4
Concorrenza
1 worker
Ripetizioni
3
Directory
8 file
Versioni per chiave
5
Campioni misurati
72
05a69d08-12e5-4b0c-ac2c-eb0f161f6957
04

Baseline · prima ottimizzazione

1 KiB / 32 MiB

Client
2
Concorrenza
1 worker
Ripetizioni
9
Directory
8 file
Versioni per chiave
1
Campioni misurati
270
72a82c23-9820-47e7-b0d2-42111d865a09
05

Aggiornato · dopo ottimizzazione

1 KiB / 32 MiB

Client
2
Concorrenza
1 worker
Ripetizioni
9
Directory
8 file
Versioni per chiave
1
Campioni misurati
270
dac69290-2cbe-4b30-b484-4fedd4201269

Un campione è un batch con una chiamata per worker: 2.292 batch corrispondono a 3.552 operazioni. Sono conservati a parte 644 batch di warmup e 22 osservazioni di inizializzazione. La terza raccolta confronta quattro client, le prime due tutti e sette. Le ultime due confrontano legacy e Genro Storage, prima e dopo la modifica.

I sette percorsi di esecuzione
Che cosa viene realmente invocato
PercorsoImplementazione
Genropy legacyStorageNode e servizio aws_s3 reali, con un contesto minimo al posto dell’intero sito.
Genro StorageAPI pubblica StorageManager / StorageNode del checkout misurato.
Genro · versioni disattivateStesso percorso, con version_aware=False sul client s3fs creato, solo per diagnosi.
FsspecBackendBackend del repository chiamato direttamente, senza manager e node.
s3fs direttoS3FileSystem con version awareness attiva, come nel nuovo storage.
boto3 direttoAPI per oggetti S3; download gestito per il file temporaneo locale.
smart_open direttosmart_open per lettura e scrittura; boto3 per le altre operazioni.

03 / Risultati selezionati

Il confronto, operazione per operazione.

Dati dopo l’ottimizzazione: nove ripetizioni, un worker e cache dei metadati fredda. Nei grafici dei tempi, una barra più corta indica una durata minore.

Genropy legacyGenro Storage
Attributi del file
Legacy3,27 ms
Nuovo1,52 ms
0 ms3,7 ms
Lettura completa
Legacy4,40 ms
Nuovo2,86 ms
0 ms4,9 ms
Albero con attributi
Legacy29,76 ms
Nuovo3,58 ms
0 ms33,3 ms

Ogni grafico usa una propria scala lineare che parte da zero. File da 1 KiB; l’albero contiene otto file da 12 byte.

Dopo ottimizzazione · file da 1 KiB e directory di 8 file · 9 ripetizioni
OperazioneLegacy · msNuovo · msLegacy · HTTP/opNuovo · HTTP/op
Attributi del file3,271,5221
Lettura completa4,402,8632
Albero con attributi29,763,58181

Le tabelle ampie scorrono orizzontalmente.

Attributi del singolo file

Il nuovo node richiede una tupla di metadati al backend, che usa una sola info(). Il percorso precedente chiedeva separatamente esistenza, tipo, dimensione e modifica: quattro HEAD sullo stesso file.

Albero con attributi

Il listing riempie la cache dei metadati di s3fs. Le successive letture degli attributi possono riusarla anche se la cache era vuota all’inizio del batch.

Dietro il grafico: quali richieste HTTP sono state inviate?
Richieste HTTP per albero completo di 8 file · 1 worker, cache fredda
ClientHEADLIST correnteLIST versioniTotale
Genropy legacy8,010,00,018,0
Genro Storage0,00,01,01,0

Medie di richieste per operazione; setup e verifiche esclusi. HEAD = HeadObject, LIST corrente = ListObjectsV2, LIST versioni = ListObjectVersions.

Trasferimenti da 32 MiB

Qui intervengono anche buffering e modalità di trasferimento. Una singola lettura completa non rappresenta tutti i possibili carichi S3.

Dopo ottimizzazione · 32 MiB · mediana in ms, 1 worker, cache fredda
OperazioneLegacyNuovo ottimizzato
Lettura completa145,37136,42
Scrittura completa294,00293,87
Copia81,9489,21
Spostamento101,79109,07
File temporaneo locale153,97172,93

Version awareness · raccolta iniziale, prima della modifica

Con cinque versioni per chiave il listing risulta più rapido nella variante disattivata, mentre gli attributi del singolo file non migliorano. Questo piccolo carico non misura la crescita del costo su un bucket con molta storia.

Cinque versioni per chiave · mediana in ms, 1 worker, cache fredda
OperazioneVersion awareness attivaVersion awareness disattiva
Attributi del file5,216,70
Elenco directory4,682,40
Albero con attributi6,654,38

Confronto completo prima/dopo

Prima/dopo · nove ripetizioni, 1 worker, cache fredda
OperazioneDimensioneNuovo prima · msNuovo dopo · msLegacy dopo · msHTTP prima → dopo / legacy
Attributi del file1 KiB6,701,523,274 → 1 / 2
Copia1 KiB16,3513,226,936 → 6 / 3
Copia32 MiB101,8089,2181,946 → 6 / 3
Esistenza1 KiB1,561,401,401 → 1 / 1
Elenco directory1 KiB3,543,424,341 → 1 / 2
File temporaneo locale1 KiB5,215,047,713 → 3 / 4
File temporaneo locale32 MiB187,73172,93153,973 → 3 / 7
File assente1 KiB3,502,933,372 → 2 / 2
Spostamento1 KiB19,8718,2110,5110 → 10 / 6
Spostamento32 MiB122,68109,07101,7910 → 10 / 6
Lettura completa1 KiB4,102,864,402 → 2 / 3
Lettura completa32 MiB157,85136,42145,372 → 2 / 3
Albero con attributi1 KiB4,883,5829,761 → 1 / 18
Scrittura completa1 KiB5,034,1118,381 → 1 / 5
Scrittura completa32 MiB315,75293,87294,001 → 1 / 5
Le raccolte prima/dopo sono sequenziali. Entrambe includono legacy e Genro, con nove ripetizioni e senza test concorrenti. Le variazioni nelle operazioni non modificate non dimostrano un effetto dell’ottimizzazione. I dati iniziali dei client diretti non vanno confrontati come se appartenessero alla nuova raccolta.

04 / Tutta la matrice

Esplora le misure.

Seleziona una combinazione realmente eseguita e confronta tutti i client disponibili. La selezione iniziale mostra il codice ottimizzato. Le raccolte iniziali mantengono i dati storici dei client diretti. I controlli aggiornano sia il grafico sia la tabella.

Confronto dei client

Misure della combinazione selezionata
ClientCampioniMediana msp95 msOps/sHTTP/opMiB/s logici

Scorri la tabella per vedere tutte le metriche.

Include anche warmup e avvio nel JSON; il CSV contiene le sintesi. Nessuna credenziale o percorso privato della macchina.

05 / Correttezza prima della velocità

I test che rendono leggibili i tempi.

Un risultato incompleto non deve sembrare più veloce. La suite verifica dati, spostamenti e listing prima di accettare i campioni.

584
VERIFICA SUPERATA

Test passati nella suite completa; 14 saltati, nessun fallimento.
Verifica finale: 18,30 secondi.

Test prima e dopo la modifica
VerificaPassatiFallitiSaltati
Contratti differenziali · sorgente originale104 attesi0
Stessi contratti · sorgente modificato1400
Suite completa finale, inclusi i contratti584014

I quattro fallimenti sul sorgente originale dimostrano le discrepanze: data inventata per directory S3 e quattro HEAD per gli attributi, entrambi con e senza versioning. I 14 casi differenziali sono inclusi nei 584, non aggiuntivi. Coprono file vuoti, binari, nomi Unicode, letture, file temporanei, assenza, directory implicite, sovrascritture, copia, spostamento e cancellazione. Non certificano tutta l’API legacy.

I 14 salti comprendono servizi opzionali o configurazioni assenti, casi già esclusi e il bug preesistente S3 set_metadata. I salti non sono successi.

I test originari del benchmark
Dettaglio dei 18 test originari del benchmark, inclusi nella verifica finale
ControlloCasiTipoEsito
Statistiche: esclusione di warmup e campioni falliti1UnitarioSuperato
Nessuna velocità dichiarata se tutti i campioni falliscono1UnitarioSuperato
Conteggio richieste HTTP, retry e azzeramento1UnitarioSuperato
Rifiuto di un listing incompleto1UnitarioSuperato
Rifiuto di contenuti alterati1UnitarioSuperato
Pulizia: rifiuto di prefissi non validi o non circoscritti6UnitarioSuperato
Report: errori conservati, tempi non validi esclusi1UnitarioSuperato
Hetzner richiede una configurazione esplicita1UnitarioSuperato
Il target MinIO non può usare un endpoint remoto1UnitarioSuperato
Credenziali escluse dalla rappresentazione del target1UnitarioSuperato
Pulizia reale: preserva gli oggetti di un’altra prova1MinIO realeSuperato
Contatore valido anche dopo il rinnovo reale del client legacy1MinIO realeSuperato
1.001 file: listing legacy incompleto scartato, nuovo completo1MinIO realeSuperato

La prova dei 1.001 file

Il legacy restituisce un elenco incompleto oltre la capacità della sua singola pagina. Il test passa perché il controllo rifiuta quel risultato e accetta il listing completo del nuovo storage. Non significa che il limite del legacy sia stato corretto.

Il rinnovo del client

La prima raccolta lunga di sviluppo è stata interrotta: il contatore perdeva il collegamento al rinnovo del client legacy. La strumentazione è stata corretta e verificata con un rinnovo reale. Quella raccolta interrotta è esclusa da questo documento. È esclusa anche la baseline preliminare 33e21eba, eseguita in parte insieme ai test; il confronto usa la successiva baseline senza test concorrenti.

Questi esiti appartengono alla verifica già eseguita durante la preparazione della suite. La generazione del documento non riesegue benchmark o test di integrazione.

06 / Metodo e riproducibilità

Che cosa misuriamo, esattamente.

Un risultato equivalente

Letture consumate interamente; scritture completate; copie verificate tramite rilettura e SHA-256; spostamenti verificati anche sull’assenza della sorgente. I listing devono contenere tutti i nomi attesi.

Confini del cronometro

La costruzione del node è inclusa nelle operazioni legacy e nuove. Preparazione dei dati, hash e controlli successivi sono esclusi. L’inizializzazione del client è registrata a parte, una volta per client.

Cache fredda e calda

“Fredda” svuota la cache dei metadati del client prima del batch. Connessioni e cache di server e sistema operativo non vengono azzerate. “Calda” conserva la cache, senza garantire che ogni richiesta trovi un dato già disponibile.

Concorrenza e throughput

Le API sincrone vengono chiamate da pool di thread. La latenza parte dentro il worker; il throughput include l’attesa del batch. Non è un confronto di API asyncio native. Per copie e spostamenti i MiB/s sono byte logici degli oggetti, non traffico di rete.

Contare le richieste

Gli eventi HTTP botocore/aiobotocore contano anche i retry. Le richieste del client di controllo, usato per setup e verifica, sono escluse. Non vengono registrati URL firmati, header o credenziali.

Client e servizi reali

Il legacy usa il proprio servizio S3 e StorageNode con un contesto minimo, senza avviare il sito o il database. Gli adapter s3fs possiedono client distinti. Ogni raccolta usa un prefisso casuale dedicato nello stesso bucket.

Le definizioni delle operazioni e delle statistiche

attrs restituisce modifica, dimensione e tipo. tree_attrs elenca la directory e raccoglie gli attributi di ogni file. local_path ottiene un file locale temporaneo, lo legge interamente e ne include la pulizia.

La mediana e il p95 usano le latenze delle singole chiamate nei batch validi. Il p95 segue il metodo nearest-rank. Ops/s e throughput dividono il lavoro totale per la somma dei tempi di batch: non derivano dall’inverso della mediana. Un batch errato è escluso per intero dalle statistiche.

Il numero di byte selezionato non descrive il payload del listing: per le prove di directory i file contengono 12 byte ciascuno. L’avvio client ha una sola osservazione; il suo p95 non è statisticamente informativo.

Ambiente, versioni e configurazione effettiva
Ambiente registrato nelle prove
ComponenteVersione / ambiente
Genro Storage · sorgente misurato0.8.0
fsspec2025.9.0
s3fs2025.9.0
boto31.40.18
botocore1.40.18
aiobotocore2.24.2
smart_open7.3.1
genro-toolbox0.14.0
Python3.12.9
Sistema del clientmacOS-26.6.2-arm64-arm-64bit
MinIO · immagine ComposeRELEASE.2025-09-07T16-13-09Z

Sorgente e metadati di distribuzione differiscono. Il codice misurato è il checkout Genro Storage 0.8.0, mentre i metadati della distribuzione installata riportano 0.4.4. Il percorso del sorgente registrato punta a src/genro_storage del checkout; commit e hash identificano il codice effettivamente misurato.

I client s3fs registrano blocchi da 50 MiB, cache readahead e cache dei listing attiva. La version awareness è attiva salvo nella variante diagnostica. Le prime due raccolte e le due raccolte prima/dopo usano un bucket non versionato; la terza un bucket versionato con cinque versioni per chiave. Gli hash distinguono il sorgente originale da quello ottimizzato, anche a parità di versione nominale.

Iniziale · file piccoli · identificazione dei sorgenti
run_id
33fface4-afbf-4f3e-b7f0-a5e01c4d924e
genro_revision
a07af08794c5c32abe854807d1e196f2697dc0bd
genro_source_sha256
687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce
legacy_revision
919a3a572acf89b4e016b2b32fd5ec556a269533
legacy_service_sha256
278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de
benchmark_source_sha256
44cc67e23b8a539e0bc10c042004763275f2e4c313ac66f289b669e5a379ec26
Iniziale · trasferimenti · identificazione dei sorgenti
run_id
72e35711-6375-45c6-af71-df03d301f4f3
genro_revision
a07af08794c5c32abe854807d1e196f2697dc0bd
genro_source_sha256
687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce
legacy_revision
919a3a572acf89b4e016b2b32fd5ec556a269533
legacy_service_sha256
278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de
benchmark_source_sha256
44cc67e23b8a539e0bc10c042004763275f2e4c313ac66f289b669e5a379ec26
Iniziale · oggetti versionati · identificazione dei sorgenti
run_id
05a69d08-12e5-4b0c-ac2c-eb0f161f6957
genro_revision
a07af08794c5c32abe854807d1e196f2697dc0bd
genro_source_sha256
687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce
legacy_revision
919a3a572acf89b4e016b2b32fd5ec556a269533
legacy_service_sha256
278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de
benchmark_source_sha256
44cc67e23b8a539e0bc10c042004763275f2e4c313ac66f289b669e5a379ec26
Baseline · prima ottimizzazione · identificazione dei sorgenti
run_id
72a82c23-9820-47e7-b0d2-42111d865a09
genro_revision
a07af08794c5c32abe854807d1e196f2697dc0bd
genro_source_sha256
687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce
legacy_revision
919a3a572acf89b4e016b2b32fd5ec556a269533
legacy_service_sha256
278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de
benchmark_source_sha256
8bb54b40d860ed3e28571f2ef7f721e2593bd83a2c792c0a6f4ce64d5e52b9ae
Aggiornato · dopo ottimizzazione · identificazione dei sorgenti
run_id
dac69290-2cbe-4b30-b484-4fedd4201269
genro_revision
a07af08794c5c32abe854807d1e196f2697dc0bd
genro_source_sha256
5e52ee511f0628d3de43bbfe796804f0eadb7ca665eb906467ec700f3d409f8e
legacy_revision
919a3a572acf89b4e016b2b32fd5ec556a269533
legacy_service_sha256
278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de
benchmark_source_sha256
8bb54b40d860ed3e28571f2ef7f721e2593bd83a2c792c0a6f4ce64d5e52b9ae
Ripetere le prove

Dalla radice del repository, dopo aver installato le dipendenze del benchmark e del legacy:

python -m pip install -e '.[benchmark,dev]'
export BENCH_LEGACY_ROOT=/percorso/al/checkout/genropy
python -m pip install -e "$BENCH_LEGACY_ROOT/gnrpy"

docker compose -p genro-storage-bench \
  -f benchmarks/compose.yaml up -d --wait

# 1. File piccoli
python -m benchmarks --smoke --create-bucket

# 2. Trasferimenti da 1 e 32 MiB
python -m benchmarks --sizes 1048576 33554432 \
  --operations read write copy move local_path \
  --workers 1 4 --list-count 8 --repeats 3 --caches cold

# 3. Cinque versioni per chiave
python -m benchmarks --smoke --enable-versioning --history 5 \
  --adapters legacy genro genro-unversioned s3fs \
  --operations attrs listing tree_attrs --workers 1 --sizes 1024

# 4–5. Matrice prima/dopo, su bucket non versionato
# Eseguire sul sorgente originale e su quello ottimizzato, senza test concorrenti.
BENCH_S3_BUCKET=genro-storage-before-after python -m benchmarks --create-bucket \
  --adapters legacy genro --sizes 1024 33554432 \
  --workers 1 --repeats 9 --list-count 8 --caches cold

# Test della suite sul servizio reale
MINIO_ENDPOINT=http://127.0.0.1:29000 BENCH_TEST_ENDPOINT=http://127.0.0.1:29000 \
  python -m pytest tests benchmarks/tests -q -o addopts='' -p no:cacheprovider

Per Hetzner: impostare BENCH_S3_ENDPOINT, BENCH_S3_BUCKET, BENCH_S3_REGION, BENCH_S3_ACCESS_KEY e BENCH_S3_SECRET_KEY nell’ambiente, poi usare python -m benchmarks --target hetzner --smoke. Le credenziali non devono essere inserite in questo documento.

Come usare questi risultati

  • Ripetere i casi rilevanti più volte su una macchina il più possibile libera da altri carichi: tre o nove round non bastano a stabilizzare il p95.
  • MinIO locale non riproduce rete, TLS, banda e implementazione del servizio Hetzner. Il confronto remoto resta da eseguire; non è stata iniettata latenza controllata.
  • Le prove isolano le operazioni storage. Non misurano l’intero sito Genropy, il suo handler, la cifratura o altri protocolli.
  • I JSON identificano le raccolte tramite UUID, senza timestamp di avvio. Limiti di CPU e memoria Docker non sono stati registrati automaticamente.
  • Gli oggetti di prova sono stati ripuliti e il Docker temporaneo rimosso. La suite documenta il recupero manuale per upload multipart interrotti; la pulizia ordinaria riguarda gli oggetti del prefisso della singola prova.