8 min

Tests de charge Go avant le premier recrutement backend

Utilisez des tests de charge Go pour simuler un trafic réaliste et mesurer latence p95, pression PostgreSQL, mémoire et erreurs avant de recruter.

Tests de charge Go avant le premier recrutement backend

Dix mille utilisateurs actifs par mois ne constituent pas une exigence de capacité. C'est une donnée de facturation ou d'analyse. Un service Go peut en accueillir beaucoup plus si les requêtes sont légères et réparties sur le mois, ou s'effondrer avec quelques centaines si chaque session déclenche des requêtes lentes, des envois de fichiers et des appels à des services tiers.

La bonne question consiste à savoir si le backend généré respecte un objectif de service défini sous le pic de trafic attendu, avec une marge suffisante pour la croissance et une panne ordinaire. Vous pouvez y répondre avant de recruter un ingénieur backend, mais seulement si le test de charge ressemble à votre produit et enregistre simultanément ce que font l'API, le runtime Go et PostgreSQL. Une courbe verte de latence moyenne ne prouve presque rien.

Voici le test que j'exigerais avant d'affirmer à un fondateur qu'un backend Go généré est prêt pour 10 000 utilisateurs actifs mensuels. Il produit un résultat reproductible, révèle le premier goulot d'étranglement et sépare un problème de capacité d'un problème de justesse.

Les utilisateurs mensuels doivent devenir des requêtes de pointe

Convertissez les prévisions d'utilisateurs en requêtes par seconde avant de choisir un niveau de charge. Le nombre d'utilisateurs actifs mensuels masque les deux variables qui sollicitent un backend : le nombre de sessions dans la période la plus chargée et le travail créé par chaque session.

Commencez par les données observées si une bêta privée existe. Comptez les sessions pendant les 15 minutes les plus chargées, les requêtes par session et la répartition des routes. S'il n'y a pas encore de trafic, écrivez les hypothèses pour que chacun puisse les contester. Supposons que 10 000 utilisateurs actifs créent huit sessions par mois et 15 requêtes API par session, avec 20 pour cent du trafic quotidien pendant l'heure la plus chargée. Cela donne environ 8 requêtes par seconde lors d'une journée chargée ordinaire. Un lancement, des notifications, une échéance de paie ou un fuseau horaire commun peuvent multiplier le véritable pic.

Ne transformez pas ce calcul en fausse précision. Servez-vous-en pour définir trois niveaux :

  • Pic attendu : la charge la plus élevée prévue aujourd'hui.
  • Pic de croissance : deux fois le pic attendu, sauf si l'entreprise possède une meilleure prévision.
  • Niveau de stress : augmentez le trafic jusqu'à l'échec d'un objectif de service ou la saturation d'une ressource.

Les tests du pic attendu et de croissance indiquent si le lancement possède de la marge. Le test de stress révèle ce qui casse en premier et la forme que prend la panne pour les utilisateurs. Cette dernière réponse compte, car un service qui refuse rapidement le travail excédentaire est plus simple à exploiter qu'un service qui consomme toutes les connexions à la base et bloque des routes sans rapport.

Utilisez un modèle de charge ouvert pour le test de capacité. Il lance les requêtes à un taux d'arrivée fixe, même quand les précédentes ralentissent. Un modèle fermé avec un nombre fixe d'utilisateurs virtuels masque souvent l'effondrement : les réponses lentes conduisent ces utilisateurs à envoyer moins de nouvelles requêtes. La charge offerte diminue précisément quand le service souffre. La documentation k6 de Grafana matérialise cette distinction avec les exécuteurs fondés sur le taux d'arrivée et signale dropped_iterations lorsque le générateur ne peut pas démarrer le travail planifié. Traitez ces itérations abandonnées comme un échec du générateur, pas comme un succès du serveur.

Maintenez chaque palier stable pendant au moins 30 minutes après la chauffe. Un test de cinq minutes rate le renouvellement des connexions, les cycles du ramasse-miettes, l'éviction du cache, les tâches de fond et la hausse progressive de la mémoire. Ajoutez un test d'endurance de deux heures au pic attendu après la réussite du test court.

Écrivez la conversion dans une petite feuille de calcul et gardez toutes les unités. Les utilisateurs mensuels multipliés par les sessions par utilisateur et les requêtes par session donnent les requêtes mensuelles. Ne divisez qu'après avoir réparti le trafic entre les jours d'activité et l'heure la plus chargée. Ajoutez les nouvelles tentatives, les tâches de fond, les webhooks et l'interrogation périodique que les outils d'analyse peuvent ignorer. Un frontend qui interroge l'API toutes les dix secondes peut créer plus de travail que les clics ayant ouvert la page.

Modélisez les rafales séparément du pic stable. Une connexion après une notification, la fin d'un import ou des clients qui réessaient après une courte interruption peuvent concentrer le travail sur une minute. Ajoutez un palier qui atteint vite le taux attendu, le maintient assez longtemps pour remplir les files puis revient à la normale. Le service doit se rétablir sans accumulation croissante ni redémarrage manuel. Enregistrez le temps de récupération. Un système peut réussir le test stable tout en restant dangereux si une brève rafale bloque son pool ou ses workers.

Ne multipliez pas le taux final par un facteur de sécurité arbitraire en l'appelant réaliste. Reliez la marge à une incertitude commerciale : erreur de prévision, campagne prévue, indisponibilité d'une instance ou délai d'ajout de capacité. Testez chaque hypothèse sur laquelle vous comptez. Conservez la feuille avec le résultat, car une exécution réussie perd son sens si personne ne se rappelle quelles prévisions et hypothèses de rafale ont produit l'objectif.

La répartition du trafic doit ressembler à une vraie session

Un test réaliste préserve la fréquence des routes, la taille des charges utiles, l'authentification, la distribution des données, les temps de réflexion et la concurrence en écriture. Appeler /health cent fois par seconde mesure le gestionnaire de santé, pas l'application.

Construisez la répartition à partir des journaux d'accès si possible. Regroupez les routes par action métier plutôt que par URL brute, car /projects/123 et /projects/456 ont la même forme. Pour un jeune SaaS, une répartition plausible pourrait consacrer 45 pour cent aux listes et détails, 20 pour cent à la recherche, 15 pour cent aux créations ou modifications, 10 pour cent à la connexion et au renouvellement des jetons, et 10 pour cent aux exports ou autres tâches lourdes. Vos chiffres doivent venir du parcours produit, pas de cet exemple.

Utilisez de nombreux comptes et enregistrements de test. Réutiliser un seul compte peut créer un cache anormalement chaud, sérialiser les mises à jour d'une ligne ou activer des limites que le trafic réel répartirait. Préparez de petits, moyens et grands espaces clients. Incluez les enregistrements absents, les entrées invalides et les refus d'autorisation, car les chemins d'erreur interrogent parfois la base ou allouent les réponses autrement que les chemins réussis.

Placez les gros envois et longs exports dans leur propre scénario si leur objectif diffère. Exécutez-les tout de même avec le trafic normal. Sinon, le test manque exactement l'incident que voient les utilisateurs : une catégorie d'export occupe le pool pendant qu'une simple page de réglages attend derrière.

Ne remplacez ni PostgreSQL, ni le stockage objet, ni les files, ni les services sortants par des simulacres lors du test final. Un simulacre aide à isoler le coût du gestionnaire, mais retire les dépendances les plus susceptibles de fixer la capacité. Visez une pile de staging avec les mêmes tailles d'instance, réglages de base, index, limites de connexion et chemin réseau que la production. Des données nettoyées ayant la forme de la production valent mieux que mille lignes de départ identiques.

Évitez de tester à travers un cache de diffusion si l'API n'est normalement pas mise en cache. À l'inverse, gardez le vrai cache sur le chemin si la production l'utilise. Le but n'est pas de donner l'impression que le backend travaille. Il faut reproduire le travail réellement déclenché par une requête utilisateur.

Un test k6 exécutable doit inscrire le contrat

Placez les seuils et paliers de trafic sous contrôle de version pour qu'une exécution ne devienne pas une capture interprétée après coup. k6 traite les seuils comme des critères de réussite et renvoie un code différent de zéro s'ils échouent. Le résultat convient donc à un contrôle de livraison.

Le squelette suivant exécute une session mixte à un taux d'arrivée, vérifie le sens des réponses et fixe des limites distinctes pour les lectures ordinaires et les exports lourds. Remplacez les routes, les charges et les objectifs par les valeurs convenues pour votre produit. Le code reste volontairement simple afin qu'un contrôle échoué corresponde à une action utilisateur.

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const businessErrors = new Rate('business_errors');

export const options = {
  scenarios: {
    expected_peak: {
      executor: 'ramping-arrival-rate',
      startRate: 5,
      timeUnit: '1s',
      preAllocatedVUs: 40,
      maxVUs: 200,
      stages: [
        { target: 10, duration: '5m' },
        { target: 10, duration: '30m' },
        { target: 20, duration: '10m' },
        { target: 20, duration: '30m' },
      ],
    },
  },
  thresholds: {
    'http_req_duration{name:project_list}': ['p(95)<300'],
    'http_req_duration{name:project_create}': ['p(95)<500'],
    'http_req_duration{name:export}': ['p(95)<2000'],
    http_req_failed: ['rate<0.01'],
    business_errors: ['rate<0.005'],
    dropped_iterations: ['count==0'],
  },
};

export function setup() {
  const response = http.post(`${__ENV.BASE_URL}/api/login`, JSON.stringify({
    email: __ENV.TEST_EMAIL,
    password: __ENV.TEST_PASSWORD,
  }), { headers: { 'Content-Type': 'application/json' } });
  check(response, { 'login succeeds': r => r.status === 200 });
  return { token: response.json('token') };
}

export default function (data) {
  const headers = { Authorization: `Bearer ${data.token}`, 'Content-Type': 'application/json' };
  const list = http.get(`${__ENV.BASE_URL}/api/projects?limit=25`, {
    headers,
    tags: { name: 'project_list' },
  });
  businessErrors.add(!check(list, {
    'list status is 200': r => r.status === 200,
    'list has items': r => Array.isArray(r.json('items')),
  }));

  if (Math.random() < 0.25) {
    const create = http.post(`${__ENV.BASE_URL}/api/projects`, JSON.stringify({
      name: `load-${__VU}-${__ITER}`,
    }), { headers, tags: { name: 'project_create' } });
    businessErrors.add(!check(create, { 'create status is 201': r => r.status === 201 }));
  }

  if (Math.random() < 0.03) {
    const runExport = http.post(`${__ENV.BASE_URL}/api/exports`, '{}', {
      headers,
      tags: { name: 'export' },
    });
    businessErrors.add(!check(runExport, { 'export accepted': r => r.status === 202 }));
  }

  sleep(Math.random() * 2 + 1);
}

Ces objectifs sont des points de départ, pas des promesses universelles. Fixez p95 par catégorie de route selon le délai tolérable et les exigences du produit. Ne jugez pas un export asynchrone et une autocomplétion avec le même seuil global de 300 ms.

Lancez le script depuis une machine qui n'héberge pas l'application. Vérifiez que le générateur conserve du CPU libre et n'abandonne aucune itération. Enregistrez le commit exact, la configuration, l'identifiant du snapshot de données, la commande et la sortie brute. Sans ces éléments, une comparaison ultérieure repose surtout sur la mémoire et l'optimisme.

Calibrez le générateur avant de faire confiance à une longue exécution. Pointez-le vers un petit gestionnaire sans travail de base, augmentez le taux au-delà du test prévu et vérifiez qu'il le maintient sans épuiser son CPU, ses sockets ou son réseau. Lorsque k6 ajoute des utilisateurs virtuels ou signale des itérations abandonnées, la machine de charge peut être la limite. Ne distribuez la génération que si une machine ne suffit pas et synchronisez leurs horloges pour aligner les graphiques du serveur et du client.

Donnez à chaque exécution une base calme. Arrêtez les migrations, imports et tâches de staging sans rapport, sauf si ces tâches fonctionnent également au vrai pic. Planifiez ensuite un second test avec les véritables tâches de fond. La paire donne la capacité propre de l'API et la capacité opérationnelle reçue par les utilisateurs. Si seule l'exécution calme réussit, le lancement dépend d'une fiction de staging.

Écrivez un deuxième script pour un seul parcours utilisateur et exécutez-le avec un utilisateur virtuel avant d'ajouter la charge. Inspectez chaque réponse, enregistrement créé et nettoyage. Vous détecterez un mauvais jeton, un contrôle toujours vrai ou des données qui entrent en collision après la première itération. Un résultat de capacité ne signifie rien si le script exerce une page d'erreur ou lit sans cesse le même objet en cache.

p95 exige le contexte de chaque route

Utilisez p95 car les moyennes masquent une minorité lente, mais ne le lisez jamais seul. Avec un p95 de 800 ms, une requête sur vingt dure au moins aussi longtemps, ce qui peut rendre une page à plusieurs appels constamment lente. Le percentile devient aussi instable sur les routes ayant peu d'échantillons, donc affichez le nombre de requêtes à côté.

Enregistrez p50, p95, p99, maximum, débit et taux d'erreur pour chaque action métier nommée. p50 montre le comportement courant, p95 constitue un contrôle pratique et p99 révèle la queue sans laisser un seul maximum dominer. Séparez les résultats par statut. Une réponse 500 rapide ne doit pas embellir la latence.

Mesurez la durée du gestionnaire côté serveur en plus de celle vue par le client. L'écart comprend la connexion, les proxys, le réseau et le transfert de réponse. Si le p95 client augmente tandis que celui du gestionnaire reste stable, cherchez hors du gestionnaire. Si les deux montent avec l'attente de base, la requête attend probablement une connexion ou une requête SQL.

Séparez les résultats de chauffe et d'état stable. Un binaire Go déployé ne compile pas, mais les caches froids, nouvelles connexions, initialisations tardives et mises à l'échelle peuvent déformer les premières minutes. Les utilisateurs subissent aussi ce comportement à froid, conservez-le comme résultat distinct.

Les moyennes restent utiles pour comptabiliser les ressources. Le temps total de base divisé par les appels peut révéler une requête modérément lente et extrêmement fréquente. Il ne remplace pas un objectif percentile. Le domaine confond souvent latence et capacité : la latence décrit la durée du travail terminé, la capacité la quantité de travail offert que le service tient sans files ni erreurs croissantes. Une bonne latence avec un faible débit obtenu ne prouve pas la capacité.

Définissez l'échec avant l'exécution. Je ferais échouer le test de lancement si une route critique manque son p95, si les erreurs HTTP inattendues dépassent le taux convenu, si les contrôles métier échouent, si des itérations sont abandonnées ou si une ressource reste saturée. Réussir quatre critères sur cinq est un échec accompagné de données utiles.

L'attente de connexion révèle les files cachées

Déployez la version mesurée
Koder.ai peut déployer et héberger le commit exact testé sous un trafic réaliste.

Instrumentez database/sql avant le test, car la latence applicative ne dit pas si PostgreSQL est lent ou si l'application attend d'y accéder. DB.Stats() de Go fournit OpenConnections, InUse, Idle, WaitCount et WaitDuration, ainsi que des compteurs de fermeture. Exportez-les vers votre système de métriques toutes les quelques secondes.

func recordDBStats(ctx context.Context, db *sql.DB, g GaugeSet) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            s := db.Stats()
            g.Set("db_open_connections", float64(s.OpenConnections))
            g.Set("db_in_use_connections", float64(s.InUse))
            g.Set("db_idle_connections", float64(s.Idle))
            g.Set("db_wait_count_total", float64(s.WaitCount))
            g.Set("db_wait_seconds_total", s.WaitDuration.Seconds())
        }
    }
}

Calculez l'évolution des compteurs cumulatifs pendant la fenêtre stable. Une hausse de WaitCount signifie que des requêtes ont attendu une connexion libre. La variation de WaitDuration divisée par celle de WaitCount donne l'attente moyenne du pool sur l'intervalle. Tracez InUse face à MaxOpenConnections; une ligne plate à la limite avec une attente croissante indique la saturation.

Ne relevez pas SetMaxOpenConns jusqu'à rendre le graphique plus joli. Ce correctif populaire déplace la file dans PostgreSQL et peut accroître la concurrence, la mémoire et la latence SQL. Trouvez d'abord pourquoi les connexions restent occupées : requêtes lentes, transactions gardées pendant des appels réseau, lecture ligne par ligne ou oubli de Rows.Close(). Dimensionnez ensuite le pool dans le budget de la base pour toutes les instances et workers.

La documentation Go indique qu'une valeur non positive de SetMaxOpenConns laisse le pool illimité. C'est un défaut dangereux en production quand plusieurs instances peuvent ouvrir des connexions simultanément. Fixez une limite explicite, réglez volontairement l'inactivité et la durée de vie, puis réservez de la capacité pour les migrations, l'administration et les tâches de fond.

Suivez séparément la durée des transactions. Un gestionnaire peut répondre en 200 ms tandis qu'un nettoyage différé ou une transaction perdue conserve la connexion beaucoup plus longtemps. Les métriques du pool montrent la pression, les traces ou chronométrages montrent le responsable.

Les preuves de requêtes lentes doivent venir de PostgreSQL

Activez pg_stat_statements dans l'environnement de test et prenez des snapshots avant et après chaque exécution. La documentation PostgreSQL le décrit comme un suivi des statistiques de planification et d'exécution des instructions normalisées. La vue contient les appels, lignes, temps total et moyen, activité des blocs et blocs temporaires. Cette preuve vaut mieux qu'une supposition tirée d'une requête aperçue dans une trace.

Utilisez l'écart entre snapshots car la vue est cumulative. Ne la réinitialisez que dans une base isolée, puisque la remise à zéro détruit les preuves d'autres travaux. Cette requête trouve les instructions ayant consommé le plus de temps pendant une fenêtre propre :

SELECT
  queryid,
  calls,
  round(total_exec_time::numeric, 1) AS total_ms,
  round(mean_exec_time::numeric, 2) AS mean_ms,
  rows,
  shared_blks_read,
  temp_blks_written
FROM pg_stat_statements
WHERE calls > 20
ORDER BY total_exec_time DESC
LIMIT 20;

Le temps total révèle les requêtes fréquentes qui dominent le travail. Le temps moyen montre les instructions individuellement lentes. Aucun ne donne le p95 d'une requête, car pg_stat_statements agrège les appels. Utilisez des traces ou histogrammes si la queue d'une instruction compte. Le domaine brouille aussi cette différence : une requête lente peut avoir une moyenne élevée, une longue queue ou seulement un coût total énorme par fréquence. Chaque cas demande une correction différente.

Pour les principales instructions, lancez EXPLAIN (ANALYZE, BUFFERS) avec des paramètres sûrs et représentatifs hors du test chronométré. ANALYZE exécute l'instruction, donc placez celles qui modifient des données dans une transaction annulée ou travaillez sur une copie jetable. Cherchez les écarts marqués entre lignes estimées et réelles, les boucles répétées, les scans séquentiels de grandes tables sélectives, les tris vers disque et les nombreux blocs partagés lus.

Le code généré crée souvent un schéma N+1 qui paraît correct avec des données de départ : charger 25 projets, puis exécuter une requête de propriétaire et une de comptage pour chacun. À dix demandes par seconde, ce seul endpoint peut produire plus de 500 instructions SQL par seconde avant toute autre route. La correction peut être une jointure, un WHERE id = ANY($1) groupé ou un total précalculé. Relever la taille du pool ne supprime pas le gaspillage.

Capturez aussi l'attente des verrous. Une requête rapide seule peut se bloquer lors de mises à jour simultanées du même espace, compte ou compteur. Si la latence bondit uniquement dans le scénario d'écriture, inspectez les attentes actives et les limites de transaction au lieu d'ajouter un index par réflexe.

La mémoire doit se stabiliser sous charge constante

Exportez au-delà du cas idéal
Emportez le code Go quand requêtes lentes ou mémoire retenue exigent un diagnostic direct.

Jugez la mémoire d'après sa forme dans le temps, pas un pic isolé. Un processus Go qui monte à la chauffe puis oscille autour d'un niveau stable se comporte autrement qu'un processus dont la base après ramassage augmente pendant un test de deux heures.

Enregistrez la mémoire résidente, le heap Go, les objets du heap, le nombre de goroutines, la fréquence et les pauses du ramasse-miettes ainsi que le taux d'allocation. La mémoire du conteneur compte, car le système tue le processus selon sa limite, pas seulement selon le heap. Comparez la mémoire après ramassage à charge comparable. Vous retirez ainsi une grande partie des dents de scie normales et rendez la rétention visible.

Exposez les métriques standard du runtime Go ou un endpoint de profilage protégé dans staging. Prenez un profil du heap au début et à la fin du test, puis comparez les sites d'allocation retenus avec go tool pprof. Prenez aussi un profil des goroutines. Un nombre croissant peut révéler des requêtes bloquées sur des canaux, des corps de réponse non fermés ou du travail de fond sans annulation.

Ne fixez pas GOMEMLIMIT exactement à la limite du conteneur. Le processus a aussi besoin de mémoire pour les piles de goroutines, les mappages exécutables, les tampons du pilote et d'autres allocations hors heap. Gardez une marge et prouvez-la avec les plus grosses charges. Un test avec de petits JSON dit peu d'un endpoint qui lit un envoi de 20 MB en mémoire.

Forcez les cas gênants : corps maximaux acceptés, gros résultats, clients annulés, expirations et exports répétés. Vérifiez que la mémoire redescend après le travail. Surveillez aussi le CPU, car un ramassage intense peut maintenir la mémoire sous la limite tout en ruinant la latence.

Une règle pratique combine plafond et tendance. Exigez que la mémoire résidente reste nettement sous la limite au pic de croissance, puis que sa base après ramassage et le nombre de goroutines cessent d'augmenter pendant le test long. Aucun pourcentage n'est universel. Choisissez la marge selon le redémarrage de la plateforme, la variation du trafic et la capacité d'une autre instance à absorber un redémarrage.

Les échecs comprennent les mauvaises réponses et la surcharge

Gardez la maîtrise du backend
L'export du code maintient instrumentation et corrections de charge sous votre contrôle pendant la croissance.

Comptez séparément les échecs de transport, statuts HTTP, expirations, panics et réponses fausses. http_req_failed capture les échecs HTTP selon le callback de k6, mais une réponse 200 avec une liste vide, un débit en double ou un enregistrement absent reste un échec. C'est pourquoi l'exemple émet business_errors depuis les contrôles de contenu.

Étiquetez les rejets attendus comme une entrée invalide ou une limite volontaire pour ne pas polluer le taux inattendu. Vérifiez ensuite leur contrat : statut correct, corps borné et rejet rapide. Un système surchargé ne doit pas attendre 30 secondes avant de renvoyer 503.

Cherchez dans les journaux les récupérations de panic, dépassements de contexte, retards d'acquisition de connexion, erreurs de sérialisation PostgreSQL et requêtes annulées. Regroupez par cause stable plutôt que par message entier afin que les identifiants ne créent pas des milliers de catégories. Gardez des traces représentatives de la première occurrence et de la queue de forte latence.

Exécutez un test de dégradation après le test propre. Réduisez les connexions disponibles, ajoutez une latence contrôlée à une dépendance externe ou redémarrez une instance pendant le trafic. Faites-le uniquement dans l'environnement isolé. Le but est de confirmer que les délais, l'annulation et les contrôles de santé contiennent la panne au lieu de laisser les files consommer toutes les ressources.

Configurez explicitement le serveur HTTP. La documentation net/http de Go précise que des valeurs nulles ou négatives pour ReadTimeout, WriteTimeout et IdleTimeout peuvent signifier aucun délai selon le champ. Les services générés appellent souvent http.ListenAndServe avec les valeurs par défaut sans prendre cette décision. Un http.Server dédié, des délais par requête et des corps bornés empêchent les clients lents et dépendances bloquées de garder des ressources indéfiniment.

Contrôlez la justesse après l'exécution. Comptez les enregistrements créés, vérifiez l'idempotence lors des nouvelles tentatives, assurez-vous que les tâches de fond ont terminé une fois et qu'aucun état partiel ne subsiste après les échecs. Dans mes projets, les tests de charge ont découvert davantage de travail dupliqué que des revues de code sophistiquées.

La première limite détermine le recrutement

Vous pouvez lancer sans ingénieur backend si le système réussit plusieurs fois les tests attendu et de croissance, si l'endurance atteint une base mémoire stable, si les files de la base restent contrôlées et si l'équipe sait expliquer le premier échec de stress. Une réussite chanceuse n'est pas une preuve. Exécutez le même commit au moins trois fois et étudiez les écarts importants.

Gardez un relevé compact pour chaque exécution :

  • Commit et environnement, avec tailles d'instance et configuration de base.
  • Taille des données, répartition du trafic, taux d'arrivée et durée.
  • p95 et p99 par route, débit obtenu, erreurs métier et itérations abandonnées.
  • Pics CPU et mémoire, tendance après ramassage et tendance des goroutines.
  • Attentes du pool, principales instructions SQL par temps total, attentes de verrous et point de rupture observé.

Recrutez ou mandatez une aide backend avant le lancement si personne ne sait expliquer la hausse des attentes, la mémoire retenue, la concurrence des verrous ou les écritures incohérentes. Recrutez aussi si la seule personne capable d'exécuter le test ne sait pas modifier le code généré sans danger. C'est un manque de responsabilité, pas un seuil de requêtes par seconde.

Un test échoué ne justifie pas automatiquement un poste à plein temps. Un index absent, une requête N+1 ou un export sans limite peuvent être des réparations circonscrites. Des échecs répétés dans les transactions, l'observabilité, l'annulation et le déploiement indiquent du travail continu. La distinction porte sur la découverte d'un défaut ou l'absence de responsable du comportement du système.

Koder.ai peut générer et exporter un backend Go, le déployer et conserver des snapshots pour revenir en arrière, mais la génération n'abolit pas la planification de capacité. Gardez le script de charge et les changements d'observabilité avec le code afin que chaque modification importante respecte le même contrat.

Ne promettez pas que 10 000 utilisateurs mensuels sont sûrs. Promettez un taux d'arrivée mesuré, une répartition de routes, un objectif de latence, un budget d'erreurs et une enveloppe de ressources. Quand le produit change, modifiez ces entrées et relancez le test.

FAQ

Un backend Go peut-il gérer 10 000 utilisateurs actifs mensuels ?

Souvent oui, mais le nombre mensuel ne décrit pas la charge backend. Convertissez la prévision en requêtes de pointe par seconde et en répartition de routes, puis testez des limites explicites de latence, d'erreurs, de base et de mémoire.

Combien de requêtes par seconde représentent 10 000 utilisateurs mensuels ?

Il n'existe aucune conversion fixe. Il faut connaître les sessions par utilisateur, les requêtes par session, la part du trafic dans la période la plus chargée et les événements qui rassemblent les utilisateurs.

Quelle latence p95 convient à une API Go ?

Fixez les objectifs par action utilisateur, pas par langage ou framework. Les lectures interactives peuvent demander quelques centaines de millisecondes et le travail de fond un autre objectif, mais l'équipe doit choisir avant les résultats.

Combien de temps doit durer un test de charge backend ?

Maintenez chaque palier au moins 30 minutes après la chauffe, puis exécutez un test plus long au pic attendu. Les tests courts ratent parfois les rotations de connexions, la mémoire retenue, les tâches de fond et les files croissantes.

Faut-il utiliser des utilisateurs virtuels ou un taux d'arrivée ?

Utilisez le taux d'arrivée pour une affirmation de capacité, car il continue d'offrir du travail quand le service ralentit. Un nombre fixe d'utilisateurs peut réduire le débit pendant le ralentissement et masquer la hausse des files.

Comment détecter l'épuisement du pool Go ?

Exportez DB.Stats() et surveillez InUse, OpenConnections, WaitCount et WaitDuration. Des compteurs d'attente croissants avec les connexions au maximum montrent que les requêtes attendent le pool.

Faut-il augmenter le pool SQL quand les requêtes attendent ?

Pas avant de savoir pourquoi les connexions restent occupées et quelle capacité possède PostgreSQL. Un pool plus grand peut déplacer la file dans la base et empirer la concurrence.

Comment trouver les requêtes PostgreSQL lentes pendant le test ?

Prenez des snapshots avant et après de pg_stat_statements, puis classez les écarts par temps total et moyen. Utilisez traces ou histogrammes pour la queue, car la vue agrégée ne fournit pas p95 par requête.

Comment reconnaître une fuite mémoire dans un service Go ?

Lancez un test stable et comparez la mémoire après ramassage à charge similaire. Une base qui continue de monter, surtout avec des objets ou goroutines en hausse, exige de comparer les profils.

Quand une startup doit-elle recruter un ingénieur backend ?

Recrutez quand les échecs révèlent un besoin continu de responsabilité sur base de données, observabilité, concurrence, justesse ou exploitation. Un index isolé peut ne pas exiger un poste, un comportement inexpliqué sous charge si.

Related posts