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.
orders.GetUserOrdersUne 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
orders.EnrichOrdersUne 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.