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
Benchmark comparativo S3
Genropy legacy, Genro Storage e client diretti alla prova. Tempi, richieste HTTP e controlli di correttezza per capire dove nasce la differenza.
01 / Cosa emerge
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.
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
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.
1 KiB / 64 KiB
1 MiB / 32 MiB
1 KiB
1 KiB / 32 MiB
1 KiB / 32 MiB
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.
| Percorso | Implementazione |
|---|---|
| Genropy legacy | StorageNode e servizio aws_s3 reali, con un contesto minimo al posto dell’intero sito. |
| Genro Storage | API pubblica StorageManager / StorageNode del checkout misurato. |
| Genro · versioni disattivate | Stesso percorso, con version_aware=False sul client s3fs creato, solo per diagnosi. |
| FsspecBackend | Backend del repository chiamato direttamente, senza manager e node. |
| s3fs diretto | S3FileSystem con version awareness attiva, come nel nuovo storage. |
| boto3 diretto | API per oggetti S3; download gestito per il file temporaneo locale. |
| smart_open diretto | smart_open per lettura e scrittura; boto3 per le altre operazioni. |
03 / Risultati selezionati
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.
Ogni grafico usa una propria scala lineare che parte da zero. File da 1 KiB; l’albero contiene otto file da 12 byte.
| Operazione | Legacy · ms | Nuovo · ms | Legacy · HTTP/op | Nuovo · HTTP/op |
|---|---|---|---|---|
| Attributi del file | 3,27 | 1,52 | 2 | 1 |
| Lettura completa | 4,40 | 2,86 | 3 | 2 |
| Albero con attributi | 29,76 | 3,58 | 18 | 1 |
Le tabelle ampie scorrono orizzontalmente.
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.
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.
| Client | HEAD | LIST corrente | LIST versioni | Totale |
|---|---|---|---|---|
| Genropy legacy | 8,0 | 10,0 | 0,0 | 18,0 |
| Genro Storage | 0,0 | 0,0 | 1,0 | 1,0 |
Medie di richieste per operazione; setup e verifiche esclusi. HEAD = HeadObject, LIST corrente = ListObjectsV2, LIST versioni = ListObjectVersions.
Qui intervengono anche buffering e modalità di trasferimento. Una singola lettura completa non rappresenta tutti i possibili carichi S3.
| Operazione | Legacy | Nuovo ottimizzato |
|---|---|---|
| Lettura completa | 145,37 | 136,42 |
| Scrittura completa | 294,00 | 293,87 |
| Copia | 81,94 | 89,21 |
| Spostamento | 101,79 | 109,07 |
| File temporaneo locale | 153,97 | 172,93 |
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.
| Operazione | Version awareness attiva | Version awareness disattiva |
|---|---|---|
| Attributi del file | 5,21 | 6,70 |
| Elenco directory | 4,68 | 2,40 |
| Albero con attributi | 6,65 | 4,38 |
| Operazione | Dimensione | Nuovo prima · ms | Nuovo dopo · ms | Legacy dopo · ms | HTTP prima → dopo / legacy |
|---|---|---|---|---|---|
| Attributi del file | 1 KiB | 6,70 | 1,52 | 3,27 | 4 → 1 / 2 |
| Copia | 1 KiB | 16,35 | 13,22 | 6,93 | 6 → 6 / 3 |
| Copia | 32 MiB | 101,80 | 89,21 | 81,94 | 6 → 6 / 3 |
| Esistenza | 1 KiB | 1,56 | 1,40 | 1,40 | 1 → 1 / 1 |
| Elenco directory | 1 KiB | 3,54 | 3,42 | 4,34 | 1 → 1 / 2 |
| File temporaneo locale | 1 KiB | 5,21 | 5,04 | 7,71 | 3 → 3 / 4 |
| File temporaneo locale | 32 MiB | 187,73 | 172,93 | 153,97 | 3 → 3 / 7 |
| File assente | 1 KiB | 3,50 | 2,93 | 3,37 | 2 → 2 / 2 |
| Spostamento | 1 KiB | 19,87 | 18,21 | 10,51 | 10 → 10 / 6 |
| Spostamento | 32 MiB | 122,68 | 109,07 | 101,79 | 10 → 10 / 6 |
| Lettura completa | 1 KiB | 4,10 | 2,86 | 4,40 | 2 → 2 / 3 |
| Lettura completa | 32 MiB | 157,85 | 136,42 | 145,37 | 2 → 2 / 3 |
| Albero con attributi | 1 KiB | 4,88 | 3,58 | 29,76 | 1 → 1 / 18 |
| Scrittura completa | 1 KiB | 5,03 | 4,11 | 18,38 | 1 → 1 / 5 |
| Scrittura completa | 32 MiB | 315,75 | 293,87 | 294,00 | 1 → 1 / 5 |
04 / Tutta la matrice
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.
| Client | Campioni | Mediana ms | p95 ms | Ops/s | HTTP/op | MiB/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à
Un risultato incompleto non deve sembrare più veloce. La suite verifica dati, spostamenti e listing prima di accettare i campioni.
| Verifica | Passati | Falliti | Saltati |
|---|---|---|---|
| Contratti differenziali · sorgente originale | 10 | 4 attesi | 0 |
| Stessi contratti · sorgente modificato | 14 | 0 | 0 |
| Suite completa finale, inclusi i contratti | 584 | 0 | 14 |
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.
| Controllo | Casi | Tipo | Esito |
|---|---|---|---|
| Statistiche: esclusione di warmup e campioni falliti | 1 | Unitario | Superato |
| Nessuna velocità dichiarata se tutti i campioni falliscono | 1 | Unitario | Superato |
| Conteggio richieste HTTP, retry e azzeramento | 1 | Unitario | Superato |
| Rifiuto di un listing incompleto | 1 | Unitario | Superato |
| Rifiuto di contenuti alterati | 1 | Unitario | Superato |
| Pulizia: rifiuto di prefissi non validi o non circoscritti | 6 | Unitario | Superato |
| Report: errori conservati, tempi non validi esclusi | 1 | Unitario | Superato |
| Hetzner richiede una configurazione esplicita | 1 | Unitario | Superato |
| Il target MinIO non può usare un endpoint remoto | 1 | Unitario | Superato |
| Credenziali escluse dalla rappresentazione del target | 1 | Unitario | Superato |
| Pulizia reale: preserva gli oggetti di un’altra prova | 1 | MinIO reale | Superato |
| Contatore valido anche dopo il rinnovo reale del client legacy | 1 | MinIO reale | Superato |
| 1.001 file: listing legacy incompleto scartato, nuovo completo | 1 | MinIO reale | Superato |
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.
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à
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.
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.
“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.
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.
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.
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.
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.
| Componente | Versione / ambiente |
|---|---|
| Genro Storage · sorgente misurato | 0.8.0 |
| fsspec | 2025.9.0 |
| s3fs | 2025.9.0 |
| boto3 | 1.40.18 |
| botocore | 1.40.18 |
| aiobotocore | 2.24.2 |
| smart_open | 7.3.1 |
| genro-toolbox | 0.14.0 |
| Python | 3.12.9 |
| Sistema del client | macOS-26.6.2-arm64-arm-64bit |
| MinIO · immagine Compose | RELEASE.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.
33fface4-afbf-4f3e-b7f0-a5e01c4d924ea07af08794c5c32abe854807d1e196f2697dc0bd687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce919a3a572acf89b4e016b2b32fd5ec556a269533278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de44cc67e23b8a539e0bc10c042004763275f2e4c313ac66f289b669e5a379ec2672e35711-6375-45c6-af71-df03d301f4f3a07af08794c5c32abe854807d1e196f2697dc0bd687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce919a3a572acf89b4e016b2b32fd5ec556a269533278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de44cc67e23b8a539e0bc10c042004763275f2e4c313ac66f289b669e5a379ec2605a69d08-12e5-4b0c-ac2c-eb0f161f6957a07af08794c5c32abe854807d1e196f2697dc0bd687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce919a3a572acf89b4e016b2b32fd5ec556a269533278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de44cc67e23b8a539e0bc10c042004763275f2e4c313ac66f289b669e5a379ec2672a82c23-9820-47e7-b0d2-42111d865a09a07af08794c5c32abe854807d1e196f2697dc0bd687e9cdb67819e0030cede114b271e299095dd72db54fef2255a716455f839ce919a3a572acf89b4e016b2b32fd5ec556a269533278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de8bb54b40d860ed3e28571f2ef7f721e2593bd83a2c792c0a6f4ce64d5e52b9aedac69290-2cbe-4b30-b484-4fedd4201269a07af08794c5c32abe854807d1e196f2697dc0bd5e52ee511f0628d3de43bbfe796804f0eadb7ca665eb906467ec700f3d409f8e919a3a572acf89b4e016b2b32fd5ec556a269533278aba856e959604151a6fd0ab5cdb557c223b9f4e8c2344f9f929ece79673de8bb54b40d860ed3e28571f2ef7f721e2593bd83a2c792c0a6f4ce64d5e52b9aeDalla 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:cacheproviderPer 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.