Tu magasines Supabase pour ton prochain projet et tu veux savoir combien de trafic ça peut vraiment absorber. C’est la bonne question à poser avant de construire, pas après avoir signé avec tes premiers clients. Voici les chiffres bruts, ce qu’ils veulent dire concrètement, et où ça craque. Ce que tu vas apprendre :

  • Comment les connexions simultanées sont distribuées selon le palier de compute
  • Le rôle exact de PgBouncer dans la capacité réelle de ton app
  • Les chiffres de débit par instance : Small, Medium, Large
  • Les facteurs qui plombent le débit théorique en production
  • Quand Supabase n’est plus suffisant et quoi faire

Ce qu’on entend par capacité de trafic concurrent en serverless

Quand on parle de « requêtes par seconde », il faut d’abord comprendre ce qu’on mesure. Une requête HTTP qui frappe ton API, ça peut être une lecture simple d’un profil utilisateur ou une transaction qui touche cinq tables. Le chiffre de débit ne veut rien dire sans le contexte de la complexité des queries. Le modèle serverless de Supabase ajoute une couche de complexité. Ton backend peut scaler horizontalement (plusieurs instances de ta fonction, plusieurs conteneurs), mais ta base de données, elle, reste sur une seule instance Postgres. C’est là que le goulot se forme.

Schéma montrant plusieurs fonctions serverless convergeant vers une instance Postgres unique via un filtre PgBouncer.
À gauche, N fonctions serverless éphémères ouvrent chacune leur connexion. Au centre, PgBouncer multiplexe ce flux — mille clients potentiels réduits à une poignée de connexions réutilisées. À droite, Postgres reste une instance unique au plafond rigide (max_connections). Sans le pooler, l'élasticité amont noierait la base.

Le concept critique ici : le nombre de connexions simultanées que Postgres peut maintenir est une ressource finie. Chaque connexion ouverte consomme de la RAM, que la connexion soit active ou non. Ouvrir mille connexions inactives sur une petite instance, c’est la façon la plus rapide de la faire manquer de mémoire.

À retenir : En serverless, tu ne contrôles pas le nombre de connexions que tes fonctions ouvrent. Sans pooling, une app à volume modéré peut épuiser les connexions disponibles en quelques minutes de pic de trafic.


Architecture de Supabase : connexions, PgBouncer et limites par palier

Supabase intègre PgBouncer comme pooler de connexions entre ton application et Postgres. PgBouncer maintient un pool de connexions persistantes vers Postgres, et tes clients se connectent à lui plutôt qu’à Postgres directement.

Diagramme en trois couches montrant le flux d'une application serverless vers Postgres via PgBouncer, avec un chemin A passant par le pooler et un chemin B en connexion directe déconseillé.
Deux chemins de connexion vers Postgres sur Supabase. Chemin A (ambre plein) : l'app serverless ouvre N connexions éphémères vers PgBouncer, qui les multiplexe sur k connexions stables (pool_size) ouvertes en permanence côté Postgres. Chemin B (bronze pointillé) : chaque fonction ouvre sa propre connexion directe et sature vite le plafond max_connections du tier compute. Le vrai goulot d'étranglement pour le RPS n'est pas max_connections mais pool_size divisé par la latence moyenne d'une requête.

PgBouncer a trois modes de fonctionnement, et le mode choisi change complètement le comportement sous charge :

  • Session pooling : Une connexion Postgres est réservée pour toute la durée de la session client. Pratiquement inutile pour le serverless.
  • Transaction pooling : Une connexion est attribuée pour la durée d’une transaction, puis retournée au pool. C’est le mode recommandé pour Supabase avec des functions serverless.
  • Statement pooling : Connexion allouée par statement individuel. Incompatible avec les transactions multi-statements. Le mode transaction pooling est le mode optimal pour maximiser le débit. Sauf qu’il a une contrainte : les prepared statements côté client ne fonctionnent pas. C’est pourquoi certains ORM (Prisma en mode natif, par exemple) nécessitent une configuration supplémentaire pour fonctionner correctement avec PgBouncer.
Maquette au trait du tableau de bord Supabase, section Database → Connection Pooling, avec le mode Transaction sélectionné et les champs de configuration du pool.
Section Connection Pooling du tableau de bord Supabase : PgBouncer intercale un pool entre les clients et Postgres. Le mode Transaction (sélectionné) recycle une connexion serveur par transaction — c'est ce qui rend possible de servir des milliers de clients serverless avec une poignée de connexions Postgres.

Ce qui varie entre les paliers de compute, c’est précisément le pool_size (connexions réelles vers Postgres) et le max_client_conn (nombre maximum de clients qui peuvent se connecter à PgBouncer simultanément). Ces deux chiffres déterminent ta capacité réelle.

Les chiffres bruts : requêtes par seconde selon la taille d’instance

Voici les données vérifiées par palier de compute :

Instance Pool Size (connexions Postgres) Max Client Connections Débit estimé
Small 15 connexions 400 connexions ~300 queries/sec
Medium 50 connexions 1 000 connexions ~1 000 queries/sec
Large 100 connexions 2 000 connexions ~2 000 queries/sec

Ces chiffres sont des estimations basées sur des queries simples : des SELECT sur des tables indexées, latence réseau locale. En production, avec des jointures, des agrégations ou des requêtes non optimisées, ce chiffre descend. L’instance Small avec ses 15 connexions réelles vers Postgres peut encore servir 400 clients simultanés via PgBouncer en mode transaction pooling. Le pooler fait le travail de multiplexage : 400 clients qui attendent, 15 qui travaillent activement dans Postgres.

Le truc c’est que ces 15 connexions suffisent si tes queries sont rapides. Une query qui prend quelques millisecondes libère sa connexion Postgres presque immédiatement. Mais si tes queries prennent des secondes, tu bloques le pool.

Diagramme éditorial montrant 400 clients passant par 15 slots PgBouncer, avec deux chronologies comparant queries courtes et longues selon leur impact sur le débit.
Quinze connexions actives font tourner le trafic de 400 clients. Selon la durée moyenne d'une query, le même pool peut délivrer un débit nettement plus élevé sur des queries courtes que sur des queries longues. Le débit n'est pas fixé par le compute seul : il est gouverné par le temps passé dans chaque slot.

Le chiffre de ~300 queries/sec pour la Small implique des queries très courtes. C’est réaliste pour une API de lecture de données simples, un endpoint de vérification d’authentification, ou une recherche sur une table bien indexée.

Instance Large à 110 $/mois : plafond réel et cas d’usage adaptés

L’instance Large coûte 0,1517 $/heure (~110 $/mois). Pour ce prix, tu obtiens 100 connexions réelles vers Postgres, 2 000 connexions clients maximum via PgBouncer, et un débit estimé de ~2 000 queries/sec.

C’est adapté pour quel type d’app? Un SaaS B2B avec quelques milliers d’utilisateurs actifs, une app mobile avec des pics de trafic raisonnables, ou une API interne qui gère du volume sans transactions ultra-complexes. Pas pour une app de financement participatif qui passe à la télé et reçoit dix mille inscriptions en vingt minutes. Ce que l’instance Large ajoute aussi : la possibilité d’ajouter jusqu’à 5 replicas en lecture. Ça, c’est majeur pour les apps à lecture intensive. Si la majorité de tes requêtes sont des SELECT (typique pour beaucoup d’apps web), tu peux répartir la charge de lecture sur plusieurs replicas et réserver l’instance principale pour les écritures.

Maquette au trait du tableau de bord Supabase montrant l'écran de configuration des read replicas avec plusieurs régions sélectionnables.
Écran « Replication » du dashboard Supabase : la région primaire est fixée, et l'utilisateur choisit une région pour déployer un réplica en lecture. Les cartes indiquent la disponibilité et le niveau de latence indicatif ; certaines régions restent grisées selon le plan.

Avec 5 replicas en lecture, ton débit de lecture effectif peut être multiplié plusieurs fois par rapport au débit d’une seule instance. Le chiffre de ~2 000 queries/sec devient la capacité d’écriture et de lecture non répliquée, pas le plafond absolu de l’architecture. La latence REST API round-trip est de 100 à 150 ms depuis les États-Unis pour un SELECT simple. Depuis le Canada, ajoute quelques dizaines de millisecondes selon la région de ton instance. Ce chiffre est important : il plafonne mécaniquement ton débit par connexion persistante. Une connexion ne peut pas faire plus d’une certaine quantité de requêtes séquentielles par seconde si chaque requête prend cette durée.

Comparaison des paliers et facteurs qui réduisent le débit réel

Regardons aussi les plans tarifaires de Supabase pour comprendre dans quel contexte tu accèdes à quel compute :

Plan Prix Accès compute custom
Free 0 $US/mois Compute Nano seulement
Pro 25 $US/mois Compute upgradeable en option
Team 599 $US/mois Compute upgradeable, SLA inclus
Enterprise Négocié Compute dédié et custom

Le plan Free te donne accès à l’instance Nano, qui n’est pas dans le tableau de débit ci-dessus. Considère-la comme en-dessous de la Small en termes de ressources. C’est bien pour du développement et du prototypage, pas pour de la production avec des utilisateurs réels.

Diagramme en barres comparant le débit approximatif des paliers Supabase Small (~300 req/s), Medium (~1 000 req/s) et Large (~2 000 req/s), avec facteurs de progression marqués entre les paliers.
Débit indicatif par palier de compute Supabase : la progression Small → Medium → Large n'est pas linéaire — le saut vers Medium offre souvent le meilleur rapport capacité/coût sur des requêtes simples via PgBouncer.

À noter : La progression du débit entre les paliers n’est pas linéaire dans le sens des coûts. Passer de Small à Medium triple approximativement le débit en multipliant le coût par moins que trois. C’est souvent Medium qui représente le meilleur équilibre pour une app en croissance. Maintenant, les facteurs qui réduisent ce débit en production : Les queries lourdes. Une query avec plusieurs JOIN, une agrégation sur une grande table non partitionnée, ou un LIKE sans index trigram peut tenir une connexion Postgres pendant des secondes. Pendant ce temps, cette connexion est bloquée. Quinze connexions occupées pendant plusieurs secondes chacune, c’est ton pool Large qui est épuisé. Les transactions longues. En mode transaction pooling, une connexion Postgres est occupée pour toute la durée de ta transaction, pas juste pour chaque statement individuel. Une transaction qui fait plusieurs opérations en séquence avec des appels API entre les deux (jamais une bonne idée, mais ça arrive) monopolise une connexion. Les migrations en production. Quand Supabase ou toi-même exécutez une migration qui acquiert un lock sur une table populaire, toutes les requêtes vers cette table attendent. En dehors des heures de pointe, c’est gérable. En plein pic, c’est une coupure.

page Logs & Monitoring du tableau de bord Supabase montrant un graphique de connexions actives sur une période, avec un pic visible
page Logs & Monitoring du tableau de bord Supabase montrant un graphique de connexions actives sur une période, avec un pic visible

Le cold start des Edge Functions. Les fonctions serverless de Supabase (Edge Functions) ont un temps de démarrage à froid. Le premier appel après une période d’inactivité prend plus longtemps. Ça n’affecte pas directement les connexions Postgres, mais ça allonge la latence perçue par le client et peut empiler les requêtes en attente. Les connexions directes non-poolées. Supabase donne accès à un port de connexion directe à Postgres, en plus du port PgBouncer. Certains outils (migrations, scripts batch, certains ORM mal configurés) utilisent ce port direct. Chaque connexion directe compte sur les connexions totales de Postgres et bypasse le pooler. Si ton script de migration ouvre dix connexions directes sur une Small, tu manges une proportion significative de tes connexions disponibles.

Quand Supabase devient le goulot d’étranglement

Supabase ne te prévient pas automatiquement quand tu approches tes limites. Il y a quelques signaux à surveiller. Le premier : les erreurs too many connections. C’est Postgres qui refuse de nouvelles connexions. En théorie, PgBouncer devrait absorber ce problème, mais si des connexions directes saturent le pool ou si PgBouncer lui-même est mal configuré, tu vas voir ces erreurs côté application. Le deuxième : une augmentation soudaine du temps de réponse de tes endpoints. Si une API qui répond normalement en quelques millisecondes commence à prendre beaucoup plus longtemps sous charge, c’est souvent le signe que les connexions PgBouncer font la file. Le client attend qu’une connexion Postgres soit disponible dans le pool.

tableau de bord Supabase avec les métriques de performance, montrant query execution time et connexions actives dans le temps
tableau de bord Supabase avec les métriques de performance, montrant query execution time et connexions actives dans le temps

Le troisième signal : des timeouts côté client. Quand la file d’attente de PgBouncer est pleine (le max_client_conn est atteint), PgBouncer refuse la connexion. Ton client reçoit une erreur plutôt qu’une réponse lente. Quand tu arrives là, tu as plusieurs options : Upgrade de compute. La plus simple. Passer de Small à Medium ou de Medium à Large donne plus de connexions Postgres et plus de CPU pour traiter les queries. Ça résout le problème si la limite est sur les connexions ou le CPU. Ajout de read replicas. Si ton trafic est majoritairement en lecture, un ou plusieurs replicas distribuent la charge. Disponible sur l’instance Large avec jusqu’à 5 replicas. Optimisation des queries. Souvent l’étape la plus rentable avant un upgrade. Un index manquant sur une colonne de filtre populaire peut transformer une query de plusieurs secondes en quelques millisecondes, libérant les connexions beaucoup plus vite. Mise en cache applicative. Ajouter Redis (ou Upstash si tu restes dans l’écosystème serverless) pour les données qui changent peu. Les requêtes qui touchent le cache n’atteignent jamais Postgres. Architecture dédiée. Quand le volume dépasse ce que même une Large avec replicas peut absorber de façon rentable, ou quand tu as besoin d’un contrôle fin de la configuration Postgres, l’auto-hébergement sur un VPS dédié ou une base de données managée plus puissante (RDS, Cloud SQL, Neon avec ses propres caractéristiques) devient pertinent.

Arbre de décision à quatre paliers reliant le volume de requêtes par seconde d'une app Supabase au bon choix d'architecture, du plan Free jusqu'à une base dédiée.
Quatre paliers de trafic, quatre décisions. Sous 50 req/s, un Free ou Pro suffit. Entre 50 et 200 req/s, on garde Supabase mais on monte le compute et on ajuste le pooling. Entre 200 et 800 req/s, il faut un Large avec read replicas et cache. Au-delà, on sort du modèle mutualisé et on part vers une architecture dédiée. Les signaux du bas indiquent quand franchir un seuil sans attendre la panne.

Supabase est bâti sur Postgres open-source sous licence Apache 2.0. Ça veut dire que si tu migres vers de l’auto-hébergement, tu emmènes les mêmes données, le même schéma, les mêmes fonctions SQL. La migration est technique mais pas un lock-in complet.

Facteurs architecturaux souvent négligés

Il y a deux aspects que les benchmarks de débit n’incluent généralement pas, mais qui impactent la capacité réelle de ton app en production. Le Row Level Security (RLS). Supabase encourage fortement l’activation du RLS sur toutes les tables. C’est une bonne pratique de sécurité. Mais chaque policy RLS ajoute une condition à chaque requête. Sur des tables avec beaucoup de lignes et des policies complexes, le coût CPU par query monte. Ton instance peut atteindre sa limite CPU avant d’atteindre sa limite de connexions. La taille de tes payloads REST. La latence REST API round-trip de 100 à 150 ms depuis les États-Unis s’applique à des SELECT simples retournant peu de données. Si ton endpoint retourne des centaines de lignes avec des champs volumineux (texte long, JSON imbriqué), le temps de sérialisation et de transmission s’ajoute à la latence de la base de données. Le débit effectif en termes de requêtes par seconde pour ces endpoints va être significativement plus bas.

Maquette au trait de l'éditeur SQL Supabase affichant une requête EXPLAIN ANALYZE et son plan d'exécution hiérarchique.
Éditeur SQL de Supabase : une requête EXPLAIN ANALYZE révèle chaque étape du plan d'exécution — GroupAggregate, Sort, Hash Join, Seq Scan — pour repérer où le temps est réellement dépensé.

Un anti-pattern fréquent : utiliser l’API REST Supabase pour charger des listes complètes côté client et filtrer en JavaScript. Chaque appel charge des centaines de lignes que le client ignore. Non seulement c’est lent, mais ça consomme des connexions et de la bande passante inutilement. Filtrer côté base de données, paginer, retourner seulement les colonnes nécessaires : c’est la base de l’optimisation. L’utilisation des Realtime subscriptions. Les abonnements Realtime de Supabase maintiennent des websockets ouverts. Chaque abonnement actif a un coût en ressources côté serveur. Pour des apps avec beaucoup d’utilisateurs actifs simultanément qui utilisent tous le Realtime, ce coût s’accumule et peut affecter la capacité globale indépendamment du compute choisi.

Conclusion : Supabase convient à quel volume de trafic

Soyons clairs. Supabase est un excellent choix pour une large gamme d’applications web et mobile, à condition de comprendre ses paliers. L’instance Small à budget minimal gère une app avec un volume de trafic modéré sur des queries simples et bien indexées. C’est suffisant pour un outil interne, un SaaS en early access, ou une app hobby qui commence à avoir de vrais utilisateurs. L’instance Medium est le bon palier pour la plupart des SaaS B2B avec quelques milliers d’utilisateurs actifs. ~1 000 queries par seconde, c’est déjà beaucoup pour une app qui n’est pas encore à scale significatif. La grande majorité des apps n’atteignent jamais ce plafond. L’instance Large à 0,1517 $/heure (~110 $/mois) avec ses ~2 000 queries par seconde et ses 5 replicas en lecture possibles couvre une très large gamme d’applications web sans nécessiter d’infrastructure supplémentaire. C’est le plafond naturel de Supabase managé avant de considérer une architecture plus complexe.

Palier Adapté pour Pas adapté pour
Small Dev, early access, outils internes Apps avec pics de trafic imprévisibles
Medium SaaS B2B, app mobile < quelques milliers d'actifs Charge transactionnelle intensive
Large SaaS en croissance, apps à lecture intensive avec replicas Volumes massifs, transactions très complexes
Auto-hébergé Volume extrême, contrôle total requis Équipes sans expertise ops Postgres

Ce que Supabase ne remplace pas : une architecture pensée. Peu importe le palier, si tes queries ne sont pas optimisées, si tu ouvres des connexions directes à côté du pooler, si tes transactions sont trop longues, tu vas saturer l’instance avant d’atteindre son plafond théorique.

Le verdict de fond : Supabase est fondé en 2020, passé par Y Combinator, bâti sur Postgres open-source. Ce n’est pas un outil jouet. C’est une infrastructure sérieuse avec des limites documentées et prévisibles. Ces limites correspondent à ce que la plupart des startups et des équipes produit vont rencontrer seulement après avoir acquis un volume d’utilisateurs réel. La vraie question n’est pas « est-ce que Supabase peut gérer mon trafic? ». La vraie question est : « est-ce que mon code est assez efficace pour que Supabase n’ait pas à compenser mes mauvaises requêtes? » Commence par le plan Pro à 25 $US/mois avec l’instance Small. Surveille les métriques. Optimise tes queries. Quand tu approches des limites, upgrade de compute. Ce n’est pas plus compliqué que ça.


Les chiffres de débit et de connexions cités dans cet article sont basés sur les données documentées de Supabase. Les chiffres de production réels varient selon la complexité des queries, l’utilisation du RLS, et la configuration applicative.

On continue à la Taverne ?

Un courriel par semaine. Pas de fluff.

En t'abonnant, tu reçois Le Tour de Table chaque semaine. Tu peux te désabonner en un clic. Voir notre politique de confidentialité.

Texte par David Cyr