En une phrase
Protocol Buffers (Protobuf) est un format binaire ultra-rapide et agnostique (indépendant du langage) pour sérialiser des données structurées. Voyez-le comme le cousin gym-bro de JSON, férocement optimisé, qui ne communique qu'en flux binaires hyper compressés.
Le problème qu'il résout
Au début des années 2000, Google avait un problème massif : ils avaient des tonnes de services internes qui discutaient entre eux, et le trafic devenait beaucoup trop lourd. À l'époque, le standard de l'industrie pour la communication de machine à machine, c'était XML. XML est génial si vous adorez lire des chevrons, mais il est notoirement obèse et lent à parser. Chaque fois qu'un serveur voulait envoyer une donnée toute bête, comme un ID utilisateur, il devait envoyer les balises texte autour <userId>12345</userId>, puis le serveur d'en face devait parser cette string, faire correspondre les balises, et convertir "12345" en entier.
Quand JSON est arrivé sur le marché, c'était plus léger, mais ça souffrait toujours de la même inefficacité de base : ça restait un format texte transmettant des strings brutes. Si vous envoyez un million d'enregistrements avec le champ "emailAddress", vous envoyez la string "emailAddress" un million de fois sur le réseau.
Les ingénieurs de Google ont réalisé que c'était un gâchis monumental de bande passante et de cycles CPU. Ils avaient besoin d'un format qui soit :
- Minuscule sur le réseau : N'envoyez pas le nom des champs, juste la donnée.
- Rapide à parser : Les ordis préfèrent lire des bits et des bytes, pas parser des caractères texte.
- Strictement typé : Si j'envoie un integer, tu dois recevoir un integer, sans avoir à croiser les doigts pour que la string se parse correctement.
- Rétrocompatible : Je dois pouvoir mettre à jour un service vers un nouveau format de données sans casser les vieux clients qui n'ont pas encore été mis à jour.
Leur solution : les Protocol Buffers. Au lieu d'envoyer du texte auto-descriptif, Protobuf repose sur un "contrat" (un schéma) partagé à l'avance. Comme l'émetteur et le récepteur connaissent déjà le schéma, les données réelles envoyées sur le réseau sont dépouillées de toutes leurs métadonnées, ne laissant que les valeurs binaires brutes, hautement compressées.
Comment ça marche sous le capot
Pour comprendre Protobuf, il faut le découper en trois phases : le contrat, le compilateur et le wire format (format réseau).
Le Schéma (.proto)
Tout commence avec un fichier .proto. C'est un contrat agnostique où vous définissez la forme exacte de vos données.
syntax = "proto3";
message Person {
string name = 1;
int32 id = 2;
bool is_active = 3;
}
Vous remarquez ces chiffres (= 1, = 2) ? Ce sont les Field Tags (tags de champ). C'est la sauce secrète de Protobuf. En JSON, la clé "name" identifie le champ. En Protobuf, c'est le chiffre 1 qui l'identifie.
L'étape de compilation
Vous n'allez pas écrire le code de parsing Protobuf à la main. Vous lancez un outil de compilation (protoc) sur votre fichier .proto, en lui disant quel langage vous voulez en sortie. Il vous recrache des classes natives pour C#, TypeScript, Python, Go, ou n'importe quoi d'autre.
Quand votre app veut envoyer une Person, vous instanciez simplement cette classe générée, vous la remplissez, et vous appelez une méthode Serialize(). Le code généré se charge du sale boulot d'empaquetage binaire.
Le Wire Format (La magie du binaire)
Quand vous sérialisez cet objet Person, qu'est-ce qui passe réellement sur le réseau ?
Il utilise une approche [Clé] [Valeur], mais au niveau du byte (octet). La Clé n'est pas une string ; c'est un seul byte (ou quelques bytes) qui combine votre Field Tag et le Wire Type (type de réseau) de la donnée.
Le Wire Type dit au récepteur quelle est la longueur de la valeur qui arrive. Voici les principaux wire types :
| Wire Type | Signification | Utilisé pour |
|---|---|---|
0 |
Varint | int32, int64, uint32, bool, enum |
1 |
64-bit | fixed64, sfixed64, double |
2 |
Length-delimited | string, bytes, messages imbriqués |
5 |
32-bit | fixed32, sfixed32, float |
Par exemple, si vous mettez is_active = true (Field 3, Wire Type 0), Protobuf encode la clé (en combinant 3 et 0) et la valeur (1 pour true) en seulement deux bytes. En JSON, "is_active": true prend 17 bytes.
L'astuce du Varint
Protobuf utilise des "Varints Base-128" pour compresser les nombres. Un entier 32-bit standard prend toujours 4 bytes en mémoire, même si le nombre vaut juste 5. Protobuf se dit : "Pourquoi gâcher 3 bytes de zéros ?"
Avec les Varints, chaque byte réserve 1 bit (le Most Significant Bit, ou MSB) pour servir de drapeau qui dit : "Est-ce qu'il y a d'autres bytes qui arrivent ?". Si le nombre est petit, le MSB vaut 0, et le nombre tient dans exactement 1 byte. Ça veut dire que les petits nombres prennent considérablement moins de place sur le réseau.
Décoder sans schéma
Comme le wire format inclut le Wire Type, un parser peut parcourir un flux brut de bytes Protobuf et deviner sa structure même sans le fichier .proto. Il ne saura pas que le Field 1 s'appelle "name", mais il verra : "Le Field 1 est de type length-delimited de 4 bytes contenant 'John'". C'est pour ça que vous pouvez balancer des données Protobuf brutes en hexa ou en Base64 dans un décodeur et obtenir quand même un arbre de tags et de valeurs façon JSON totalement exploitable.
Dans la vraie vie
La flotte IoT à bout de souffle
Une startup a construit une flotte de capteurs agricoles intelligents mesurant l'humidité du sol sur des centaines de kilomètres. Au départ, les capteurs pinguaient un serveur central chaque minute en envoyant des payloads JSON via les réseaux cellulaires. En moins d'un mois, deux choses se sont produites : leurs factures de data 3G ont explosé, et les batteries des capteurs mourraient en quelques semaines au lieu de plusieurs mois.
Ils ont réécrit le firmware des capteurs pour sérialiser les données en Protocol Buffers. Comme ils envoyaient de petits nombres et des booléens, le fait de virer toutes les strings JSON du type {"sensorId": 14, "moistureLevel": 42} a fait chuter la taille de leurs payloads de plus de 80 %.
La leçon : Quand la bande passante coûte cher et que l'autonomie de la batterie est limitée, transmettre des strings brutes est un luxe que vous ne pouvez pas vous permettre. La sérialisation binaire sauve des ressources physiques.
Le grand bouchon des microservices de 2018
Un géant du e-commerce a décidé d'éclater son backend monolithique en 50 microservices distincts. Ils ont choisi des API REST avec du JSON pour la communication. Soudainement, la latence globale a explosé. Le profiling des serveurs a révélé un truc choquant : les services backend passaient 30 % de leurs cycles CPU juste à stringifier et parser du JSON pendant que la data sautait du service Auth au service Cart (panier), puis au service Inventory.
Ils ont migré leurs communications internes vers gRPC, un framework entièrement basé sur Protobuf. Comme parser du Protobuf revient essentiellement à lire des offsets mémoire et à faire du bit-shifting, l'utilisation du CPU s'est effondrée. Les données filaient à toute vitesse d'un service à l'autre. La leçon : Dans les systèmes backend internes à haut débit, le parsing de texte est une taxe invisible. La sérialisation binaire, c'est la niche fiscale légale.
Le payload mystère dans le dump réseau
Une chercheuse en sécu essayait de reverse-engineer l'API non documentée d'une app mobile pour voir quelles données elle collectait. Elle a fait passer le trafic de l'app par un proxy, s'attendant à voir du beau JSON bien lisible. À la place, elle a obtenu une bouillie infâme. Les headers HTTP ont confirmé ses soupçons : Content-Type: application/x-protobuf.
Elle n'avait pas le schéma .proto (les devs de l'app le gardaient sur leurs serveurs). Mais elle a chopé le payload en Base64 et l'a fait passer dans un décodeur Protobuf brut. Ce qui en est sorti, c'est un arbre structuré : Field 1: "johndoe", Field 2: "user@example.com", Field 3: 40.7128, Field 4: -74.0060. Elle a immédiatement grillé que les champs 3 et 4 étaient des coordonnées GPS.
La leçon : Protobuf n'est pas chiffré, il est juste encodé. Même sans la carte, les petits poucets structurels laissés par les Wire Types vous permettent de reverse-engineer le squelette de la donnée.
Pièges et erreurs classiques
- Réutiliser les tags de champs : Le numéro est la source absolue de vérité. Si vous supprimez un champ (disons,
string name = 1;), vous ne devez jamais réutiliser le numéro1pour un nouveau champ commeint32 age = 1;. Si un vieux client envoie une string, et qu'un nouveau serveur essaie de la lire comme un entier, tout explose. - Oublier de réserver les champs supprimés : Pour éviter l'erreur ci-dessus, Protobuf fournit le mot-clé
reserved. Si vous supprimez le champ2, vous devez écrire explicitementreserved 2;dans votre schéma. Ça force le compilateur à hurler sur n'importe quel dev qui essaierait de le réutiliser dans le futur. - Changer les types de données n'importe comment : Vous pouvez changer certains types en toute sécurité (comme passer de
int32àint64car ils utilisent le même wire type Varint), mais changer unestringenint32est un breaking change. Les wire types sont fondamentalement incompatibles. - Partir du principe que Protobuf est sécurisé : C'est un format de sérialisation, pas du chiffrement. Si vous envoyez des données utilisateur sensibles sur HTTP en utilisant Protobuf sans TLS/SSL, n'importe qui interceptant le trafic peut banalement décoder les valeurs brutes.
- L'utiliser à l'aveugle dans le navigateur : Bien qu'il existe des technos comme gRPC-web, les navigateurs web parlent nativement JSON. Importer Protobuf dans une app web frontend oblige à embarquer une grosse librairie de parsing dans votre bundle JavaScript, ce qui peut parfois annuler l'économie de bande passante apportée par le format binaire lui-même.
Pourquoi ça doit être sur votre radar
Vous ne devriez pas utiliser Protobuf pour vos API publiques — JSON y reste le roi incontesté car il est lisible par les humains, flexible et compris nativement par tous les navigateurs web de la planète.
En revanche, vous devriez dégainer Protobuf direct quand vous contrôlez les deux bouts du tuyau. Si vous construisez des microservices qui discutent entre eux, des apps mobiles qui parlent à un backend, des jeux multijoueurs en temps réel ou des appareils IoT, Protobuf est un super-pouvoir. Il impose des contrats de données stricts entre les équipes (parce que tout le monde compile à partir du même fichier .proto), attrape les erreurs de typage dès la compilation, et presse votre infrastructure réseau jusqu'à la dernière goutte de performance.
Pour aller plus loin
- Documentation officielle de Protocol Buffers - La source de vérité de Google sur l'écriture de schémas et l'utilisation du compilateur.
- Spécifications d'encodage Protobuf - Une plongée profonde et glorieuse dans les opérations bit à bit exactes et les wire types utilisés dans le format.
- Site officiel de gRPC - Le framework RPC haute performance qui utilise Protobuf par défaut pour son routage et ses payloads.
- Variable-length quantity (Varints) sur Wikipédia - Les maths et l'histoire derrière la façon dont Protobuf compresse les entiers en volant le Most Significant Bit.