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:

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:

  1. DNS non configuré

  2. Règle iptables manquante

  3. 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 :

  1. Vérifier DNAT actif :

sudo iptables -t nat -L OUTPUT -n -v | grep 3910
# Doit afficher règle DNAT
  1. Tester connexion directe :

curl -o /dev/null http://192.168.80.202:3910/mangetamain/recipes_clean.parquet
  1. 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