sixty

pour les services Go

Deux lignes, et la requête a un appelant.

L’installation ici est explicite — un middleware, une enveloppe de pilote, et deux lignes en tête des fonctions qui méritent d’être mesurées — parce que Go n’a pas d’étape de build où s’accrocher et aucun moyen d’atteindre le contexte de l’appelant sans qu’on vous le passe. Ce que vous obtenez pour ces deux lignes, c’est ce dont dépend tout le reste : une requête avec une fonction au-dessus, pour qu’un N+1 ait où atterrir.

Ce qu’il mesure

Chaque fonction que vous marquez
ctx, span := sixty.Start(ctx, "orders.List") et une capture différée. Deux lignes, et il faut faire circuler le contexte — c’est le prix de l’absence de contexte ambiant en Go, et la raison pour laquelle le chiffre est digne de confiance quand il arrive.
Chaque requête, sur n’importe quel pilote database/sql
L’agent enveloppe le pilote plutôt que la connexion, donc pgx, lib/pq, MySQL et tout ce qui est enregistré auprès de database/sql sont mesurés — et chaque interface optionnelle que le vrai pilote implémente est reproduite, donc rien de ce qu’il savait faire ne cesse de fonctionner.
Les lignes, comptées à la lecture
Pas depuis une tranche renvoyée, parce que database/sql n’en a pas. Le span se ferme quand les lignes se ferment, et c’est ce qui rend le compte réel plutôt que déduit.
Quelle fonction a émis quelle requête
Le contexte porte le span courant, donc une requête est créditée à la fonction qui l’a émise. C’est ce qui transforme « le point d’entrée a ralenti » en « cette fonction s’est mise à faire vingt appels ».
Vos routes HTTP, en entrée et en sortie
sixty.Middleware enveloppe tout ce qui parle net/http — chi, gorilla, echo, le mux à motifs de Go 1.22. Là où le routeur connaît un motif que le chemin n’a pas, sixty.SetRoute le nomme.

Ce qu’il trouve

Chacune de ces choses est comparée à la version précédente : le constat nomme donc le changement plutôt que le jour.

Un N+1 qui n’était pas là à la version précédente

Une fonction qui faisait un appel base par requête en fait maintenant vingt. Ancré au déploiement, donc le constat nomme la version plutôt que l’heure.

Une requête qui s’est mise à lire toute la table

Les lignes par appel sont passées de 30 à 30 000 — et ici ce compte est pris pendant que l’appelant itère, c’est donc ce que votre code a réellement lu et non ce que la requête aurait pu renvoyer.

Votre propre code qui ralentit

Le temps dans la fonction elle-même, séparé du temps dans ce qu’elle a appelé, pour que vous ouvriez le bon fichier du premier coup.

Quelque chose désormais appelé dans une boucle

Même vitesse par appel, mêmes données, appelé des centaines de fois là où il l’était une seule.

Une requête qui ne renvoie plus rien, en silence

Pas d’erreur, pas d’appel lent, et vide là où il y avait des lignes. Rien dans un service Go ne signale cela comme un échec, parce que rien n’a échoué.

Les erreurs, groupées par leur provenance

Par emplacement dans le code plutôt que par message — un bug fait une entrée quel que soit le nombre de valeurs qu’il y formate.

Ce qui arrive dans votre fil

Pas un tableau de bord à lire. Une carte par constat, avec les chiffres, la version qui les a changés, et les preuves dont votre agent de code a besoin pour écrire le correctif.

Ce sont les deux régressions qu’apps/demo-go livre exprès, contre le jeu de données que crée sa commande de seed — environ 1 000 utilisateurs et 30 000 commandes. Vous pouvez le lancer et les regarder arriver.

lignesorders.GetUserOrders
30 lignes30 000 lignes1000×

Une refonte a laissé tomber la clause WHERE, donc chaque appel récupère toute la table et la filtre en Go. La latence a à peine bougé sur une base chaude — c’est le nombre de lignes qui trahit, et il est réel parce qu’il est pris au fil de la lecture.

depuis la version v2, contre la v1

N+1orders.EnrichOrders
1 requête20 requêtes20×

Une requête groupée unique est devenue une recherche par commande. Chacune est rapide et indexée, donc rien ne bouge dans un graphe de latence — seulement le nombre.

depuis la version v2, contre la v1

Comment ça s’installe

Remettez-le à l’agent de code que vous avez déjà ouvert. Il trouve main(), le handler et le sql.Open, fait circuler le contexte là où il faut, et marque les fonctions qui méritent d’être mesurées — l’étape facile à décrire et fastidieuse à faire à la main.

claude mcp add sixty \
  -e SIXTY_API_KEY=sixty_sk_… \
  -e SIXTY_ENDPOINT=https://ingest.sixty.sh \
  -- npx -y @sixty-sh/mcp

# or by hand:
go get github.com/sixty-sh/sixty-go

// main(), before the server starts
defer sixty.Init(sixty.Config{}).Shutdown(context.Background())

// requests — anything that speaks net/http
http.ListenAndServe(":8080", sixty.Middleware(mux))

// queries — the driver you already use
db, err := sixty.Open("pgx", dsn)

// functions — the ones worth measuring
func (s *Store) GetUserOrders(ctx context.Context, id int64) (_ []Order, err error) {
    ctx, span := sixty.Start(ctx, "orders.GetUserOrders")
    defer span.Capture(&err)
    ...
}

L’identifiant de version est récupéré tout seul chez Vercel, Render, Railway, Fly, Heroku ou GitHub Actions, et SIXTY_RELEASE le fixe partout ailleurs. La première comparaison arrive après votre prochain déploiement, parce qu’il n’y a rien à quoi comparer une version isolée.

Ce qu’il ne fait pas

  • Le contexte doit circuler. Une goroutine à qui l’on ne passe pas le ctx fait son travail en dehors de l’opération — correct pour du travail de fond, faux pour un fan-out que vous vouliez mesurer. Il n’existe pas de version à contexte ambiant de ceci et nous n’en construirons pas, parce que la version qui devine est celle qui attribue les requêtes d’une requête HTTP à une autre.
  • Les plans de requête ne sont pas capturés. C’est le constat qui nomme une cause plutôt qu’un symptôme — « ceci a cessé d’utiliser son index » — et sur un service Go il n’arrive pas.
  • Le CPU et l’attente ne sont pas séparés. Les goroutines migrent entre threads, donc une horloge par thread ne décrit pas un span, et en remonter une serait un chiffre faux exactement quand il compte.
  • Postgres est ce que le détecteur comprend en profondeur. Une autre base enregistrée auprès de database/sql remonte toujours les temps, les lignes et les comptes ; les conclusions au niveau des requêtes sont plus faibles.
  • Rien ici ne marque vos fonctions à votre place. Sauter cette étape laisse le fil avec des routes et des requêtes et rien entre les deux, ce qui est la moitié qui rend le reste digne d’être lu.

Si vous êtes ici pour l’une de ces raisons

Découvrez ce qu’a fait votre dernier changement.

Essayez !

Vous construisez autre chose ? Voyez services Node, services Python ou applications Ruby et Rails — ces pages sont différentes, et ce que nous voyons aussi.

Surveillance des performances pour Go — net/http, database/sql et Postgres — Sixty