Stockage S3 Garage
Configuration et utilisation du stockage S3 Garage haute performance.
S3 Garage est une implémentation self-hosted du protocole Amazon S3 (Glossaire). Ce guide couvre la configuration, l’optimisation performance et le dépannage.
Pour débuter rapidement, voir Guide Démarrage Rapide.
Vue d’ensemble
Endpoints disponibles:
HTTP: http://s3fast.lafrance.io (port 3910) - Privilégié dans le code
HTTPS: https://s3fast.lafrance.io (port 443, via reverse proxy)
Bucket: mangetamain
Performance: 500-917 MB/s (DNAT bypass)
Région: garage-fast
Choix HTTP vs HTTPS:
Le code utilise l’endpoint HTTP pour des raisons de performance :
Gain de rapidité : Pas de surcharge TLS/SSL lors des transferts de données
Réseau DMZ sécurisé : La communication reste dans le réseau local isolé (192.168.80.0/24)
HTTPS disponible : Accessible via reverse proxy pour les accès externes si nécessaire
Installation
Configuration DNS Locale
echo "192.168.80.202 s3fast.lafrance.io" | sudo tee -a /etc/hosts
Installation iptables-persistent
sudo apt update
sudo apt install iptables-persistent -y
Règle iptables DNAT
Bypass du reverse proxy pour performance maximale:
sudo iptables -t nat -A OUTPUT -p tcp -d 192.168.80.202 --dport 80 -j DNAT --to-destination 192.168.80.202:3910
Sauvegarde Permanente
sudo netfilter-persistent save
Vérification Installation
# DNS
getent hosts s3fast.lafrance.io
# Doit afficher: 192.168.80.202 s3fast.lafrance.io
# iptables
sudo iptables -t nat -L OUTPUT -n -v | grep 3910
# Doit afficher la règle DNAT
Configuration Credentials
Structure 96_keys/
96_keys/
├── credentials # Profil s3fast
├── aws_config # Config AWS CLI
└── garage_s3.duckdb # Base DuckDB avec secret S3
Fichier credentials
Format ConfigParser:
[s3fast]
aws_access_key_id = GK4feb...
aws_secret_access_key = 50e63b...
endpoint_url = http://s3fast.lafrance.io
region = garage-fast
bucket = mangetamain
Fichier aws_config
Format AWS CLI:
[profile s3fast]
region = garage-fast
s3 =
endpoint_url = http://s3fast.lafrance.io
Base DuckDB avec Secret
Créer une fois:
cd ~/mangetamain/96_keys
duckdb garage_s3.duckdb
Dans DuckDB:
INSTALL httpfs;
LOAD httpfs;
CREATE SECRET s3fast (
TYPE s3,
KEY_ID 'votre_access_key_id',
SECRET 'votre_secret_access_key',
ENDPOINT 's3fast.lafrance.io',
REGION 'garage-fast',
URL_STYLE 'path',
USE_SSL false
);
Utilisation AWS CLI
Liste Fichiers
aws s3 ls s3://mangetamain/ \
--endpoint-url http://s3fast.lafrance.io \
--region garage-fast
Download
aws s3 cp s3://mangetamain/PP_recipes.csv /tmp/recipes.csv \
--endpoint-url http://s3fast.lafrance.io \
--region garage-fast
Upload
aws s3 cp /tmp/results.csv s3://mangetamain/results/ \
--endpoint-url http://s3fast.lafrance.io \
--region garage-fast
Utilisation Python boto3
Chargement Credentials
import boto3
from configparser import ConfigParser
# Charger credentials depuis 96_keys/
config = ConfigParser()
config.read('../96_keys/credentials')
s3 = boto3.client(
's3',
endpoint_url=config['s3fast']['endpoint_url'],
aws_access_key_id=config['s3fast']['aws_access_key_id'],
aws_secret_access_key=config['s3fast']['aws_secret_access_key'],
region_name=config['s3fast']['region']
)
Liste Objets
# Liste fichiers avec tailles
response = s3.list_objects_v2(Bucket='mangetamain')
for obj in response.get('Contents', []):
print(f"{obj['Key']} - {obj['Size']/1e6:.1f} MB")
Download Fichier
s3.download_file('mangetamain', 'PP_recipes.csv', '/tmp/recipes.csv')
Upload Fichier
s3.upload_file('/tmp/results.csv', 'mangetamain', 'results/analysis.csv')
Utilisation DuckDB
Requêtes SQL sur S3
En CLI:
# Requête simple
duckdb ~/mangetamain/96_keys/garage_s3.duckdb \
-c "SELECT COUNT(*) FROM 's3://mangetamain/PP_recipes.csv'"
# Analyse avec GROUP BY
duckdb ~/mangetamain/96_keys/garage_s3.duckdb -c "
SELECT calorie_level, COUNT(*) as total
FROM 's3://mangetamain/PP_recipes.csv'
GROUP BY calorie_level
ORDER BY total DESC"
En Python:
import duckdb
# Connexion à la base avec secret
conn = duckdb.connect('~/mangetamain/96_keys/garage_s3.duckdb')
# Requête SQL directe sur S3
df = conn.execute("""
SELECT *
FROM 's3://mangetamain/PP_recipes.csv'
LIMIT 1000
""").fetchdf()
Parquet sur S3
DuckDB optimisé pour Parquet:
# Lecture Parquet depuis S3 (zero-copy)
conn.execute("""
SELECT AVG(calories) as mean_calories
FROM 's3://mangetamain/RAW_recipes_clean.parquet'
WHERE year >= 2010
""")
Utilisation Polars
Lecture Directe S3
import polars as pl
from configparser import ConfigParser
# Charger credentials
config = ConfigParser()
config.read('../96_keys/credentials')
# Configuration storage options
storage_options = {
'aws_endpoint_url': config['s3fast']['endpoint_url'],
'aws_access_key_id': config['s3fast']['aws_access_key_id'],
'aws_secret_access_key': config['s3fast']['aws_secret_access_key'],
'aws_region': config['s3fast']['region']
}
# Lecture CSV depuis S3
df = pl.read_csv(
's3://mangetamain/PP_recipes.csv',
storage_options=storage_options
)
# Lecture Parquet depuis S3
df = pl.read_parquet(
's3://mangetamain/RAW_recipes_clean.parquet',
storage_options=storage_options
)
Tests Performance
Benchmark Download
# Test avec fichier volumineux
time aws s3 cp s3://mangetamain/large_file.parquet /tmp/ \
--endpoint-url http://s3fast.lafrance.io \
--region garage-fast
Résultats attendus:
Avec DNAT bypass: 500-917 MB/s
Sans bypass (reverse proxy): 50-100 MB/s
Gain: 5-10x plus rapide
Vérification DNAT Actif
# Vérifier iptables rule
sudo iptables -t nat -L OUTPUT -n -v | grep 3910
# Test connexion directe port 3910
curl -I http://192.168.80.202:3910/mangetamain/
# Doit retourner HTTP 200 ou XML erreur S3
Structure Bucket
Organisation Fichiers
s3://mangetamain/
├── RAW_recipes.csv
├── RAW_recipes_clean.parquet
├── RAW_interactions.csv
├── RAW_interactions_clean.parquet
├── PP_recipes.csv
├── PP_users.csv
├── PP_ratings.parquet
├── interactions_train.csv
├── interactions_test.csv
└── interactions_validation.csv
Tailles Fichiers
Fichier |
Taille |
|---|---|
RAW_recipes.csv |
~50 MB |
RAW_recipes_clean.parquet |
~25 MB |
RAW_interactions.csv |
~200 MB |
RAW_interactions_clean.parquet |
~80 MB |
PP_recipes.csv |
~30 MB |
PP_ratings.parquet |
~60 MB |
Tests Infrastructure
Tests Automatiques (50_test/)
S3_duckdb_test.py (14 tests):
Environnement système (AWS CLI, credentials)
Connexion S3 avec boto3
Performance download (>5 MB/s)
DuckDB + S3 intégration
Tests Docker (optionnels)
test_s3_parquet_files.py (5 tests):
Scanne automatiquement le code
Trouve les références aux fichiers parquet
Teste l’accessibilité S3
Lancer Tests S3
cd ~/mangetamain/50_test
pytest S3_duckdb_test.py -v
Dépannage
Erreur: Cannot connect to S3
Causes possibles:
DNS non configuré
Règle iptables manquante
Credentials invalides
Solution:
# Vérifier DNS
getent hosts s3fast.lafrance.io
# Vérifier iptables
sudo iptables -t nat -L OUTPUT -n -v | grep 3910
# Tester credentials
aws s3 ls s3://mangetamain/ \
--endpoint-url http://s3fast.lafrance.io \
--region garage-fast
Erreur: Slow Download Speed
Cause: DNAT bypass non actif, trafic passe par reverse proxy
Solution: Vérifier règle iptables
sudo iptables -t nat -L OUTPUT -n -v | grep 3910
# Si absent, recréer règle
sudo iptables -t nat -A OUTPUT -p tcp -d 192.168.80.202 --dport 80 -j DNAT --to-destination 192.168.80.202:3910
sudo netfilter-persistent save
Erreur: DuckDB Secret Not Found
Cause: Secret S3 non créé dans base DuckDB
Solution: Recréer le secret
duckdb ~/mangetamain/96_keys/garage_s3.duckdb
DROP SECRET IF EXISTS s3fast;
CREATE SECRET s3fast (
TYPE s3,
KEY_ID 'your_key_id',
SECRET 'your_secret',
ENDPOINT 's3fast.lafrance.io',
REGION 'garage-fast',
URL_STYLE 'path',
USE_SSL false
);
Bonnes Pratiques
Sécurité Credentials
JAMAIS commiter 96_keys/ (dans .gitignore)
Partager credentials via canal sécurisé uniquement
Rotation régulière des clés
Performance
Privilégier Parquet sur CSV (2-3x plus rapide)
Utiliser DuckDB pour requêtes SQL (zero-copy)
Activer DNAT bypass (10x plus rapide)
Cache local pour fichiers fréquemment accédés
Cache Streamlit
import streamlit as st
@st.cache_data(ttl=3600) # Cache 1h
def load_data_from_s3():
"""Charge données S3 avec cache."""
# Lecture S3 coûteuse une seule fois
return df
Benchmarks Performance
Comparaison Configurations
Tests effectués avec recipes_clean.parquet (250 MB) :
Configuration |
Vitesse |
Temps (250 MB) |
Gain |
|---|---|---|---|
Sans DNAT (via reverse proxy) |
50-100 MB/s |
2.5-5 secondes |
Baseline |
DNAT bypass (direct port 3910) |
500-917 MB/s |
0.27-0.5 sec |
10x |
DNAT + lecture locale SSD |
2-3 GB/s |
0.08-0.12 sec |
40x |
Recommandation : DNAT bypass obligatoire pour performance acceptable.
Test de Performance
Script benchmark :
#!/bin/bash
# test_s3_speed.sh
echo "=== Test sans DNAT ==="
# Désactiver DNAT temporairement
sudo iptables -t nat -D OUTPUT -p tcp -d 192.168.80.202 --dport 80 \\
-j DNAT --to-destination 192.168.80.202:3910 2>/dev/null
time aws s3 cp s3://mangetamain/recipes_clean.parquet /tmp/test1.parquet --profile s3fast
rm /tmp/test1.parquet
echo "=== Test avec DNAT ==="
# Réactiver DNAT
sudo iptables -t nat -A OUTPUT -p tcp -d 192.168.80.202 --dport 80 \\
-j DNAT --to-destination 192.168.80.202:3910
time aws s3 cp s3://mangetamain/recipes_clean.parquet /tmp/test2.parquet --profile s3fast
rm /tmp/test2.parquet
Résultats attendus :
Sans DNAT: real 0m4.520s (55 MB/s)
Avec DNAT: real 0m0.380s (658 MB/s)
Gain: 11.9x plus rapide
Optimisation Lecture Parquet
Comparaison formats :
Format |
Taille |
Temps lecture |
Vitesse |
|---|---|---|---|
CSV (non compressé) |
1.2 GB |
12-15 secondes |
80-100 MB/s |
CSV (gzip) |
320 MB |
8-10 secondes |
32-40 MB/s |
Parquet (Snappy) |
250 MB |
0.3-0.5 sec |
500-833 MB/s |
Pourquoi Parquet est optimal :
Compression Snappy intégrée (ratio ~5:1)
Format columnar (lecture sélective)
Metadata intégré (pas besoin parser)
Zero-copy avec DuckDB/Polars
Lecture optimale avec Polars :
import polars as pl
# Lecture Parquet optimisée
df = pl.read_parquet(
"s3://mangetamain/recipes_clean.parquet",
use_pyarrow=True, # Moteur Arrow (plus rapide)
columns=['id', 'name'], # Lecture sélective (columnar)
n_rows=1000 # Limite pour preview
)
Monitoring Performance
Mesurer temps chargement :
import time
from loguru import logger
@st.cache_data(ttl=3600)
def load_with_timing():
start = time.time()
df = pl.read_parquet("s3://mangetamain/recipes_clean.parquet")
elapsed = time.time() - start
logger.info(f"S3 load: {len(df)} rows in {elapsed:.2f}s ({len(df)/elapsed:.0f} rows/s)")
return df
Logs attendus :
2025-10-27 15:23:45 | INFO | S3 load: 178265 rows in 0.42s (424441 rows/s)
Dépannage Performance
Vitesse < 100 MB/s :
Vérifier DNAT actif :
sudo iptables -t nat -L OUTPUT -n -v | grep 3910
# Doit afficher règle DNAT
Tester connexion directe :
curl -o /dev/null http://192.168.80.202:3910/mangetamain/recipes_clean.parquet
Vérifier latence réseau :
ping -c 10 192.168.80.202
# RTT doit être < 1ms (réseau local)
Vitesse fluctuante :
Cause : Charge serveur Garage
Solution : Répéter mesures sur 5-10 essais
Variance normale : ±20%
Premier chargement lent :
Cause : Cold start Garage (cache serveur)
Normal : 2-3x plus lent que suivants
Solution : Pre-warm avec
aws s3 ls
Limites et Quotas
Garage S3 (installation actuelle) :
Bande passante : ~1 Gbps (125 MB/s théorique)
IOPS : Illimité (SSD serveur)
Connexions simultanées : 100+ (suffisant)
Taille bucket : ~5 GB utilisés / 1 TB disponible
Pas de quotas AWS : Installation self-hosted, pas de limites AWS.
Voir Aussi
Installation - Installation complète du projet
Tests et Coverage - Tests infrastructure S3 (50_test/)
api/data - Module data.cached_loaders avec schémas
api/infrastructure - Tests S3 automatisés
Guide Démarrage Rapide - Commandes S3 essentielles
S3_INSTALL.md (racine) - Documentation détaillée installation
S3_USAGE.md (racine) - Guide d’utilisation complet