9 points par GN⁺ 2025-11-01 | 2 commentaires | Partager sur WhatsApp
  • 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

    • Ajouter à Cargo.toml :
      [dependencies]  
      fory = "0.13"  
      
  • 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

 
qlghwp123 2025-11-01

Ces performances sont-elles vraiment possibles ?

 
GN⁺ 2025-11-01
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

    • L’encodage Binary XML peut être utile dans certains cas, mais il n’est pas aussi efficace que les formats modernes de sérialisation binaire
      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
    • Je ne sais pas si W3C EXI ou ASN.1 BER est préférable, mais je pense qu’une approche DOP (conception orientée données) est la bonne
      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()

    • Ce benchmark n’est pas équitable
      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

    • Plus il y a de langages, plus un schéma officiel devient important
      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

    • Il existe un tableau de correspondance des types
      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
    • Il est déroutant de présenter les équipes polyglottes comme principal cas d’usage tout en affirmant qu’aucun fichier de schéma n’est nécessaire
      Voir ce billet de blog connexe
    • Je suis sceptique sur le fait que cette approche fonctionne bien sur le long terme
    • Le manque d’explications sur l’usage réel en production n’inspire pas confiance
  • 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

    • D’après les résultats de benchmark comparés à dill, Fory est 20 à 40 fois plus rapide et offre jusqu’à 7 fois plus de compression
      Voir le code du benchmark
    • Apache Fory peut aussi servir de remplaçant à pickle/cloudpickle et prend en charge la sérialisation d’objets de code comme les fonctions ou classes locales
      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

    • “Fury” était à l’origine le nom que j’avais choisi, donc j’y étais attaché, mais il a fallu le changer
  • 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

    • Fory prend aussi en charge la compression des entiers, et la taille des données est presque identique à celle de Protobuf
      Un tableau comparatif sur différents jeux de données est présenté
    • Il existe deux modes de compatibilité de schéma, mais il n’y a aucune garantie de compatibilité binaire entre versions mineures
  • Je me demande si la limite de 4096 types de Fory est suffisante
    Voir le code correspondant

    • Ce n’est probablement pas suffisant dans tous les cas, mais cela peut être étendu si nécessaire
      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