- Apache Fory Rust est un framework de sérialisation cross-language qui offre des performances de sérialisation ultra-rapides et une gestion automatique des références
- Basé sur les techniques zero-copy et la sûreté de typage de Rust, il gère automatiquement les références circulaires, les objets de trait et l’évolution de schéma
- Prend en charge l’échange de données entre Rust, Python, Java, Go et d’autres langages sans fichier IDL ni génération de code
- Selon les benchmarks, il atteint une vitesse de traitement plus de 10 à 20 fois supérieure à JSON et Protobuf
- Il présente une forte valeur d’usage dans des environnements haute performance comme les microservices, pipelines de données et systèmes temps réel
Le dilemme de la sérialisation et l’arrivée d’Apache Fory Rust
- Les approches de sérialisation existantes ont la limite de devoir sacrifier la vitesse, la flexibilité ou la compatibilité entre langages
- Les formats binaires manuels sont rapides, mais fragiles face aux changements de schéma
- JSON/Protobuf sont flexibles, mais avec un surcoût de performance supérieur à 10x
- Les solutions existantes prennent mal en charge les fonctionnalités propres à chaque langage
- Apache Fory Rust réunit performance et flexibilité, sans besoin d’IDL ni de gestion manuelle des schémas
Principales caractéristiques
-
1. Véritable prise en charge cross-language
- Partage le même protocole binaire avec Java, Python, C++, Go, etc.
- Des données sérialisées en Rust peuvent être désérialisées telles quelles en Python
- Aucun fichier de schéma, aucune génération de code, aucun problème d’incompatibilité de version, ce qui simplifie les échanges de données entre microservices multilingues
-
2. Gestion automatique des références circulaires et partagées
- Suit et préserve automatiquement les structures à références circulaires sur lesquelles la plupart des frameworks échouent
- Même si un même objet est référencé plusieurs fois, il n’est sérialisé qu’une seule fois, avec conservation de l’identité des références
- Convient aux bases de données graphe, ORM et modèles de domaine complexes
-
3. Sérialisation des objets de trait
- Prend en charge la sérialisation d’objets de trait comme
Box en Rust
- Le macro
register_trait_type! permet d’enregistrer des types polymorphes
- Prend en charge différentes formes comme
Box, Rc, Arc, dyn Any, etc.
- Permet d’implémenter des systèmes de plugins, collections hétérogènes et architectures extensibles
-
4. Évolution de schéma (mode compatibilité)
- Le mode Compatible autorise les changements de schéma entre versions de service
- Ajout, suppression, réordonnancement de champs et conversion de types optionnels possibles
- Changement de type non autorisé
- Utile pour les déploiements sans interruption et l’évolution indépendante des microservices
Fondements techniques
-
Conception du protocole
- Structure :
| fory header | reference meta | type meta | value data |
- Utilise des entiers à longueur variable, métadonnées compressées, suivi des références et layout little-endian
- Les performances sont améliorées grâce à la déduplication des objets partagés et à la compression des métadonnées de type
-
Génération de code à la compilation
- La génération de code basée sur des macros au lieu de la réflexion élimine le surcoût à l’exécution
- Le macro
#[derive(ForyObject)] génère automatiquement les fonctions de sérialisation et désérialisation
- Garantit la sûreté de typage, réduit la taille du binaire et prend en charge l’autocomplétion IDE
-
Architecture
fory/ : API de haut niveau
fory-core/ : moteur de sérialisation (buffer I/O, enregistrement de types, compression des métadonnées, etc.)
fory-derive/ : définition des macros procédurales
- Une structure modulaire qui améliore la maintenabilité et l’extensibilité
Résultats des benchmarks
- Une vitesse de traitement plus de 10 à 20 fois supérieure à JSON et Protobuf
- Exemples :
simple_struct(small) → Fory 35,729,598 TPS / JSON 10,167,045 / Protobuf 8,633,342
person(medium) → Fory 3,839,656 TPS / JSON 337,610 / Protobuf 369,031
- Dans tous les cas de test, Fory a enregistré les meilleures performances
Scénarios d’utilisation
-
Cas d’usage adaptés
- Microservices multilingues : échange de données sans fichier de schéma
- Pipelines de données haute performance : traitement de millions d’enregistrements par seconde
- Modèles de domaine complexes : prise en charge des références circulaires et des structures polymorphes
- Systèmes temps réel : latence inférieure à 1 ms, désérialisation zero-copy
-
Alternatives à envisager
- Si un format lisible par l’humain est nécessaire → JSON/YAML
- Si un format de stockage longue durée est nécessaire → Parquet
- Pour des structures de données simples → serde + bincode
Pour commencer
-
Installation
-
Exemple de sérialisation de base
- Enregistrer une struct avec
#[derive(ForyObject)], puis utiliser serialize() / deserialize()
- L’enregistrement des identifiants de type permet de préserver la cohérence des données
-
Sérialisation cross-language
- Activer le mode de compatibilité multilingue avec
compatible(true).xlang(true)
- Prise en charge de l’enregistrement par identifiant ou par nom (
register_by_namespace, register_by_name)
Types pris en charge
- Types de base : bool, entiers, flottants, String
- Collections : Vec, HashMap, BTreeMap, HashSet, Option
- Pointeurs intelligents : Box, Rc, Arc, RcWeak, ArcWeak, RefCell, Mutex
- Date/heure : types chrono
- Objets définis par l’utilisateur : ForyObject, ForyRow
- Objets de trait : Box/Rc/Arc, Rc/Arc
Feuille de route
-
Disponible dans la v0.13
- Génération de code statique, format Row zero-copy, suivi des références circulaires, sérialisation d’objets de trait, mode de compatibilité de schéma
-
Fonctionnalités prévues
- Sérialisation cross-language des références, mises à jour partielles de Row
Considérations de production
- Thread safety : après l’enregistrement, partage possible via
Arc (Send + Sync)
- Gestion des erreurs : basée sur
Result, avec distinction explicite des erreurs comme incompatibilité de type ou tampon insuffisant
Documentation et communauté
- Documentation officielle : fory.apache.org/docs
- Documentation API : docs.rs/fory
- Communauté : GitHub, Slack et Issue Tracker
- Open source sous Apache License 2.0
Conclusion
- Apache Fory Rust est un framework de sérialisation de nouvelle génération qui élimine les compromis entre performance, flexibilité et compatibilité entre langages
- Son automatisation basée sur des macros, la prise en charge des objets de trait et la gestion des références circulaires maximisent l’efficacité de développement
- Il peut être utilisé immédiatement dans les microservices, pipelines de données et systèmes temps réel
2 commentaires
Ces performances sont-elles vraiment possibles ?
Avis sur Hacker News
J’aimerais qu’on se concentre davantage sur l’amélioration de l’outillage autour de technologies existantes comme W3C EXI (Binary XML) plutôt que de créer de nouveaux formats
Le simple fait d’être rapide ne suffit pas, et un format sans écosystème comme Aeron/SBT a du mal à se diffuser. XML possède déjà cet écosystème
De plus, il ne représente pas naturellement des graphes d’objets complexes comme les références partagées ou circulaires
Le format Fory a été conçu dès le départ pour résoudre ces problèmes tout en prenant en charge la compatibilité interlangage et l’évolution du schéma
Autrement dit, il vaut mieux concevoir d’abord l’encodage, puis l’étendre ensuite vers les langages ou les clients
Je doute de l’équité du benchmark
En regardant le lien vers le code, on voit que lorsque ce n’est pas une struct Fory, le processus de sérialisation inclut une conversion to/from
Cette conversion entraîne des copies de chaînes ou des réallocations de tableaux
Dans un système réel, tonic fournit un tampon de 8 KB, ce qui serait plus efficace qu’un simple Vec::default()
Sur un CPU Xeon Gold 6136, on a l’impression d’un gain de 10x, mais en supprimant la conversion to/from et la copie du Vec, et en préallouant un tampon de 8 KB, on est en réalité plutôt autour de 3x
Le benchmark devrait être réécrit dans un style tower service/codec sans aucun code spécifique à Fory
Fory utilise un writer pool pendant les tests
Voir le code correspondant
Je pense que pour maintenir à long terme une compatibilité interlangage, il faut un contrat spécifié sur la base d’un IDL
Une approche qui part du langage pour aller vers la sérialisation est pratique au début, mais devient avec le temps vulnérable aux évolutions du runtime du langage
Un projet dans un seul langage peut rester simple sans IDL, mais à partir de trois langages ou plus, l’IDL sert de source unique de vérité
Apache Fory prévoit d’ajouter une prise en charge optionnelle de l’IDL, afin de permettre aux équipes de choisir entre une approche language-first ou schema-first selon leur contexte
Je me demande comment ils maintiennent des types partagés entre langages sans schéma
Dans les langages typés, le schéma est déduit des définitions de classes, et dans les langages non typés, on ajoute directement des annotations dans le code
Un exemple Python est disponible ici
Voir ce billet de blog connexe
Je me demande pourquoi utiliser Fory plutôt que des formats sans sérialisation comme CapnProto ou Flatbuffers
Si une compression est nécessaire, il suffit d’utiliser zstd
Cela dit, la large prise en charge des langages et la facilité d’usage de Fory sont impressionnantes
En Python, je préfère toujours dill — parce qu’il peut sérialiser jusqu’aux objets de code
Lien vers dill
Voir le code du benchmark
Lien vers un exemple
pyfory offre un taux de compression 3 fois supérieur à cloudpickle et dispose de fonctions d’audit de sécurité pour prévenir les attaques malveillantes de désérialisation
Le lien du benchmark renvoyait une 404, mais j’ai trouvé le bon lien
C’est dommage d’avoir changé le nom de “Fury” en “Fory”
Fury était un nom parfait pour un framework de sérialisation rapide
La plupart des protocoles binaires cherchent à réduire la taille des données
Protobuf utilise la compression des entiers (varint, zigzag)
Si on ne compare que le TPS brut, l’approche consistant à envoyer telle quelle une struct C sans rien faire gagnera toujours
Un tableau comparatif sur différents jeux de données est présenté
Je me demande si la limite de 4096 types de Fory est suffisante
Voir le code correspondant
En pratique, j’ai rarement vu des cas définissant plus de 4096 messages de protocole
Le lien du benchmark Rust renvoie une erreur 404
Je n’ai pas trouvé le répertoire benchmark depuis la racine de la documentation