← Tous les articles • 01/03/2026 • 3 min de lecture

Comment j'ai réduit la latence p99 de 60 % sur un service Rust

Vrai playbook p99 : pooling, index, allocs JSON, coût du tracing, tuning Tokio, timeouts. De 220 ms à 85 ms de p99 sur Axum + Postgres.

#performance#axum#postgres#latency

Un service Rust à 220 ms de p99 qui aurait dû être rapide. Après une semaine : 85 ms de p99 (moins 60 %), même hardware, même Postgres. Voici exactement ce qui a compté, dans l’ordre.

Stack : Axum 0.7, Tokio, SQLx, Postgres 16, un seul VPS Hetzner à 12 € + Postgres managé. Charge : ~2k rps mixtes lecture/écriture. Mesuré avec oha et des histogrammes Prometheus (pas des moyennes !).

0. Mesure la p99, pas la moyenne

La moyenne cache les queues de distribution. J’ai ajouté :

// histogram, not counter
metrics::histogram!("http_request_duration_seconds", latency);

Et j’ai dashboardé p50, p95 et p99 séparément. La p50 était déjà à 12 ms. Correct. La p99 était le problème. Des correctifs différents ciblent des percentiles différents.

Correctif 1 : pool et index manquant (moins 35 % de p99)

Deux classiques empilés :

CREATE INDEX CONCURRENTLY idx_tasks_user_created
  ON tasks (user_id, created_at DESC);

Résultat : p99 de 220 à 145 ms. Ennuyeux et énorme. Vérifie toujours la BDD en premier. Rust est rarement le goulot.

Correctif 2 : requêtes N+1 (moins 15 % de p99)

Une route chargeait les tâches puis bouclait un SELECT par assigné. 1 + 47 requêtes par appel à p99.

-- avant : N+1 dans une boucle Rust
-- après : un seul JOIN
SELECT t.id, t.title, u.name AS assignee
FROM tasks t LEFT JOIN users u ON u.id = t.assignee_id
WHERE t.project_id = $1;

Avec query_as! de SQLx, un seul aller-retour. p99 de 145 à 122 ms. pg_stat_statements et les spans de tracing par requête ont rendu ça évident.

Correctif 3 : allocs JSON sur le chemin chaud (moins 10 % de p99)

Le profiling avec flamegraph montrait 18 % du temps dans le churn de serde_json::Value : parse, Value, resérialisation. Correctif : désérialiser direct dans des structs, utiliser bytes::Bytes pour le passthrough, activer CompressionLayer seulement pour les gros bodies.

// mal : Json<Value> puis bidouille
// bien : DTO typé, borrow quand possible
#[derive(Deserialize)]
pub struct Ingest<'a> { #[serde(borrow)] pub title: &'a str }

p99 de 122 à 108 ms. Petit mais gratuit.

Correctif 4 : coût du tracing et des logs (moins 8 % de p99)

Niveau TRACE avec des jolis logs en prod et des bodies complets loggués. Passé à :

tracing_subscriber::fmt().json()
    .with_max_level(tracing::Level::INFO)
    .with_env_filter("myapp=info,tower_http=warn,sqlx=warn")
    .init();

Spans de debug échantillonnés, info! par ligne supprimés. p99 de 108 à 95 ms. L’observabilité ne devrait pas coûter 10 %.

Correctif 5 : timeouts et dégradation gracieuse (le reste)

Sans timeouts, un seul downstream lent empoisonne tous les workers. Ajouté :

use tower_http::timeout::TimeoutLayer;
use std::time::Duration;
// par route : appels externes 2s, acquisition BDD 3s

Plus tokio::time::timeout sur les appels reqwest sortants avec fallback sur le cache. p99 de 95 à 85 ms, et la p99.9 a arrêté de monter à plusieurs secondes.

Checklist (à voler)

  1. Dashboarde p50/p95/p99 séparément
  2. Pool à 10-20, acquire_timeout, surveille la profondeur de file
  3. EXPLAIN ANALYZE sur les 5 top requêtes, ajoute des index composites
  4. Tue les N+1 (join ou WHERE id = ANY($1))
  5. JSON typé, évite Value sur les chemins chauds
  6. Logs JSON en INFO, fais taire le bruit sqlx et tower_http
  7. Timeouts partout et compression sélective
  8. --release + lto="thin" + codegen-units=1 pour les builds prod
  9. Teste la charge avec oha -c 100 -z 60s, pas 5s

Total : 220 à 85 ms de p99. Pas de réécriture, pas de nouvelle infra, juste de la mesure et des correctifs ennuyeux.

Tu veux la checklist complète et le JSON du dashboard Grafana ? Gratuit dans la newsletter. Je fais aussi des sprints perf d’une semaine, benchmarks avant/après inclus.


Ça t'a plu ? Récupère la checklist prod.

Template Axum + patterns Postgres + checklist p99. Gratuit, un email par semaine.

Je la veux →

🚀 Gratuit : la checklist Rust Backend Prod

Rejoins les devs backend. Reçois mon template Axum, ma checklist latence p99 et mes questions d'entretien. Un email pratique par semaine.

Zéro spam. Désinscription en un clic. Ce que tu vas recevoir →