- Chez Meta, WebRTC alimente l'audio et la vidéo en temps réel sur diverses plates-formes. Mais intégrer un grand projet open source comme WebRTC au sein de notre monorepo présente des défis uniques – au fil du temps., une fourche interne peut dériver en arrière vers l'amont, se couper des mises à niveau de la communauté.
- Nous expliquons comment nous avons échappé à ce « piège à forking » : en créant une architecture à double pile qui a permis des tests A/B sécurisés à travers 50+ cas d'utilisation, aux flux de travail qui nous maintiennent désormais continuellement mis à niveau avec l'amont.
- Cette approche a amélioré les performances, taille binaire, et la sécurité – et nous continuons à l'utiliser aujourd'hui pour tester A/B chaque nouvelle version en amont avant de la déployer.
Chez Meta, communication en temps réel (RTC) alimente divers services, des chats vidéo mondiaux sur Messenger et Instagram au Cloud Gaming à faible latence et au casting VR immersif sur Meta Quest. Pour répondre aux exigences de performances de milliards d’utilisateurs, nous avons passé des années à développer un, variante haute performance de la bibliothèque open source WebRTC.
La création permanente d'un grand projet open source peut entraîner un piège courant dans l'industrie. Cela commence avec de bonnes intentions: Vous avez besoin d'une optimisation interne spécifique ou d'une correction rapide d'un bug. Mais avec le temps, à mesure que le projet en amont évolue et que vos fonctionnalités internes s'accumulent, les ressources nécessaires pour fusionner dans des commits externes peuvent devenir prohibitives.
Récemment, nous avons officiellement conclu une migration massive sur plusieurs années pour briser ce cycle. Nous avons déménagé avec succès 50 cas d'utilisation allant d'un fork WebRTC divergent à une architecture modulaire construite sur la dernière version en amont - en l'utilisant comme squelette tout en injectant nos propres implémentations propriétaires de composants clés.
Cet article détaille comment nous avons conçu une solution pour résoudre le « piège à fourche ».," nous permettant de créer deux versions de WebRTC simultanément au sein d'une seule bibliothèque à des fins de tests A/B, tout en vivant dans un environnement monorepo, avec des cycles de mise à niveau continus de la bibliothèque testée.
Le défi: Le Monorepo et le Linker statique
Mettre à niveau une bibliothèque comme WebRTC peut être risqué, en particulier lors de la mise à niveau tout en servant des milliards d'utilisateurs et en introduisant des régressions difficiles à annuler. Cela élimine également la possibilité d'une mise à niveau unique, ce qui pourrait perturber l'expérience de certains utilisateurs en raison de la variété des appareils et des environnements sur lesquels nous exécutons.
Pour atténuer cela, nous avons donné la priorité aux capacités de tests A/B afin d'exécuter l'ancienne version de WebRTC aux côtés de la nouvelle version en amont avec des correctifs propres et d'appliquer nos fonctionnalités dans la même application tout en étant capable de basculer dynamiquement les utilisateurs entre eux pour vérifier la nouvelle version.
En raison du graphique de construction de l'application et des contraintes de taille, nous avons également donné la priorité à la recherche d'une solution pour lier statiquement deux versions de WebRTC. Cependant, cela viole la règle de définition unique de l'éditeur de liens C++ (ODR), provoquant des milliers de collisions de symboles, nous avons donc cherché un moyen de faire coexister deux versions de la même bibliothèque dans le même espace d'adressage.
En outre, Meta utilise un monorepo et nous ne voulons pas subir le même processus encore et encore. Cela nous a motivé à trouver une solution pour maintenir des correctifs personnalisés pour les projets open source dans un environnement monorepo., tout en étant capable d'extraire de nouvelles versions en amont et d'appliquer les correctifs encore et encore.
Cela nous a amené à nous concentrer sur la résolution de deux défis:
- Nous souhaitions une capacité de test A/B. Pour y parvenir, nous avons construit deux copies de WebRTC dans la même bibliothèque en raison de contraintes d'application.
- Sans branches de fonctionnalités dans monorepo, comment pouvons-nous suivre les correctifs et les rebaser? D'autres projets OSS basés sur libwebrtc le font généralement en appliquant un ensemble de fichiers de correctifs stockés de manière séquentielle au-dessus du dépôt propre à chaque mise à niveau de la bibliothèque.. En raison de problèmes d'évolutivité, nous avons exploré des options plus nuancées.
Solution 1: La couche de calage et l'architecture double pile
Pour aborder la capacité de test A/B, nous avons choisi de créer deux copies de WebRTC dans la même application. Cependant, faire cela de manière statique au sein de la même bibliothèque globale d'orchestration d'appels crée des défis uniques. Pour aborder cela, nous avons construit une couche de calage entre la couche application et WebRTC. Il s'agit d'une bibliothèque proxy située entre notre code d'application et les implémentations WebRTC sous-jacentes.. Au lieu que l'application appelle directement WebRTC, il appelle l'API shim. La cale expose un seul, unifié, API indépendante de la version.
![image[1]-S'échapper de la fourchette: Comment Meta a modernisé WebRTC 50+ Cas d'utilisation pour Windows 7,8,10,11-Winpcsoft.com](https://winpcsoft.com/wp-content/plugins/wp-fastest-cache-premium/pro/images/blank.gif)
La couche de calage contient une configuration « saveur » et distribue chaque appel soit au héritage ou dernier Implémentation de WebRTC au moment de l'exécution. Cette approche – caler au niveau de la couche la plus basse possible – évite une régression significative de la taille binaire qu'aurait provoquée la duplication de la bibliothèque d'orchestration d'appels de couche supérieure.. La duplication aurait entraîné une augmentation de la taille non compressée d'environ 38 Mo, alors que notre solution n'ajoutait qu'environ 5 Mo - et 87% réduction.
Suivant, nous examinerons les obstacles introduits par cette architecture double pile et comment nous les avons résolus.
Résoudre les collisions de symboles
La liaison statique de deux copies de WebRTC en un seul binaire produit des milliers d'erreurs de symboles en double..
![image[2]-S'échapper de la fourchette: Comment Meta a modernisé WebRTC 50+ Cas d'utilisation pour Windows 7,8,10,11-Winpcsoft.com](https://winpcsoft.com/wp-content/plugins/wp-fastest-cache-premium/pro/images/blank.gif)
Afin de garantir que chaque symbole de chaque saveur est unique, nous avons exploité le changement d'espacement de nom automatisé: Nous avons construit des scripts qui réécrivent systématiquement chaque espace de noms C++ dans une version WebRTC donnée, donc le webrtc:: espace de noms dans le dernier la copie en amont devient webrtc_latest::, tandis que le héritage la copie devient webrtc_legacy::. Ce changement de nom a été appliqué à chaque espace de noms externe de la bibliothèque.
Mais tout dans WebRTC ne réside pas dans un espace de noms – fonctions C globales, variables libres, et les classes qui ont été laissées en dehors des espaces de noms, intentionnellement ou accidentellement, entrent également en collision.
Pour ceux, nous avons déplacé ce que nous pouvions dans des espaces de noms et manipulé les symboles du reste (comme les fonctions C globales) avec des identifiants spécifiques à la saveur.
Les macros et les indicateurs du préprocesseur présentaient un problème plus subtil. Des macros comme RTC_CHECK et RTC_LOG peut être utilisé en dehors de WebRTC dans les bibliothèques wrapper, donc inclure les en-têtes des deux versions dans la même unité de traduction déclenche des erreurs de redéfinition.
Nous avons résolu ce problème grâce à une combinaison de stratégies:
- Suppression des inclusions parasites.
- Renommer des macros rarement utilisées.
- Partage des modules WebRTC internes entre les versions lorsque cela est possible, comme rtc_base. Cette dernière approche présentait l'avantage supplémentaire de réduire à la fois la taille binaire et la surface de code à caler..
![image[3]-S'échapper de la fourchette: Comment Meta a modernisé WebRTC 50+ Cas d'utilisation pour Windows 7,8,10,11-Winpcsoft.com](https://winpcsoft.com/wp-content/plugins/wp-fastest-cache-premium/pro/images/blank.gif)
Compatibilité descendante
Renommer chaque symbole dans WebRTC briserait chaque site d'appel externe. Notre objectif était de maintenir le code existant en état de marche sans interruption.. Certains sites d'appel sont construits avec une saveur WebRTC constante, et pas double pile.
Notre approche initiale consistait à déclarer chaque symbole utilisé du nouvel espace de noms et à le connecter à l'ancien.. Cela a fonctionné, mais a produit un gros fichier d'en-tête fragile qui nécessitait un niveau élevé de maintenance.
Nous avons itéré vers une meilleure solution: importations groupées d'espaces de noms à l'aide de C++ à l'aide de déclarations. En important un espace de noms de saveur entier dans le langage familier webrtc:: espace de noms, nous avons obtenu un en-tête de déclaration concis où les nouveaux symboles sont gérés automatiquement, sans implications de taille binaire puisque ce sont de pures directives du compilateur. Les ingénieurs externes continuent d'écrire le code exactement comme avant : le câblage s'effectue en parallèle, où nous migrons uniquement les sites d'appels externes qui nous intéressent.
Arôme: Distribution de la version d'exécution
Avec la couche de cale enveloppant les deux versions WebRTC, la question suivante était: Comment envoyer vers la bonne version au moment de l'exécution? Chaque adaptateur et convertisseur doit instancier le bon objet sous-jacent – webrtc_legacy:: ou webrtc_latest::, basé sur un indicateur de configuration globale.
Nous avons résolu ce problème avec une bibliothèque d'assistance basée sur des modèles. Logique partagée (qui constitue une grande partie du code de l'adaptateur) est écrit une fois. Le comportement spécifique à la version est exprimé via les spécialisations de modèles C++. Cela maintient le code SEC tout en prenant en charge la compatibilité ascendante avec les versions à saveur unique pendant la période de transition.. Une énumération de saveurs globale, défini au début de la séquence de démarrage de chaque application, détermine quelle saveur activer.
Nous utilisons adaptateurs directionnels en tant qu'objets intermédiaires qui implémentent l'API unifiée et sont distribués à l'objet WebRTC sous-jacent, ou vice versa. Nous utilisons convertisseurs directionnels en tant que fonctions utilitaires pour traduire les structures et les énumérations entre les systèmes de type shim et WebRTC.
![image[4]-S'échapper de la fourchette: Comment Meta a modernisé WebRTC 50+ Cas d'utilisation pour Windows 7,8,10,11-Winpcsoft.com](https://winpcsoft.com/wp-content/plugins/wp-fastest-cache-premium/pro/images/blank.gif)
Génération de cales
La couche de cale elle-même nécessitait des adaptateurs et des convertisseurs. Avec un grand nombre d'objets à répartir sur des dizaines d'API, chacune nécessitant une définition d'API abstraite, implémentations d'adaptateurs et de convertisseurs, et tests unitaires – l’effort manuel estimé était énorme!
Nous nous sommes tournés vers l'automatisation. Utiliser l'arbre de syntaxe abstraite (AST) analyse, nous avons construit un système de génération de code qui produit du code de base pour les classes, structures, énumérations, et constantes. Le code généré est entièrement testé unitairement et facile à étendre. Cela a augmenté notre vitesse d'une cale par jour à trois ou quatre par jour tout en réduisant le risque d'erreur humaine.. Pour les cales simples où l'API est identique d'une version à l'autre, le code généré nécessitait une intervention manuelle proche de zéro. Pour les cas plus complexes – divergences API entre les versions, modèles d'usine, méthodes statiques, sémantique du pointeur brut, et transferts de propriété d'objets : les ingénieurs ont affiné la référence générée.
Câblage et création d'applications double pile
Avec la couche de cale en place, nous avons commencé le travail minutieux de recâblage de toutes les références d'applications, des types WebRTC directs à leurs équivalents shim. Par exemple, webrtc::Fou devenu webrtc_shim::Fou. Cela a introduit des complexités en matière de propriété d'objet et le potentiel de bugs subtils autour de la gestion des valeurs nulles et de la gestion de la mémoire.. Nous avons atténué ce problème grâce à des tests unitaires complets qui reproduisaient des scénarios problématiques de transfert de propriété et de durée de vie des objets., complété par des tests de bout en bout pour les différences particulièrement risquées.
Nous avons ensuite travaillé de manière itérative pour créer des applications complètes en mode double pile., en commençant par de petites cibles et en progressant. Chaque itération a fait apparaître de nouveaux problèmes: cales manquantes, objets mal aromatisés, et de nouvelles collisions de macros ou de symboles.
Certains composants internes injectés dans WebRTC de l'extérieur posaient un défi particulier en raison de leurs profondes dépendances vis-à-vis des composants internes de WebRTC.. Puisque caler ces composants signifierait proxy WebRTC contre lui-même, nous les avons plutôt « dupliqués » à l’aide d’une macro C++ et Mâle construire des machines – changer dynamiquement les espaces de noms au moment de la construction, dupliquer la cible de build de haut niveau, et exposer les symboles des deux saveurs via un seul en-tête.
Une fois terminé, nous avions notre application interne, ainsi que quelques applications externes, tous créent et exécutent des appels audio et vidéo en mode double pile pour les versions anciennes et récentes.
Sur 10,000 des lignes de code de cale ont été ajoutées, et des centaines de milliers de lignes ont été modifiées dans des milliers de fichiers. Malgré la portée, des tests et un examen minutieux ne signifiaient aucun problème majeur.
Utiliser cette approche, nous avons pu tester A/B l'ancienne version WebRTC par rapport à la dernière, application par application, atténuer les régressions, bateau, et supprimez l'ancien code. Aujourd'hui, l'approche shim est utilisée dans certaines applications afin que nous puissions continuellement mettre à jour le code WebRTC interne avec les dernières mises à jour en amont.
Solution 2: Les branches de fonctionnalités
Puisque nous utilisons un monorepo sans support généralisé pour les succursales, nous avons cherché un moyen de suivre les correctifs au fil du temps qui serait continuellement rebasé sur les versions en amont.. Notre exigence claire était que chaque patch ait un objectif et une équipe clairement définis..
Nous avions deux choix ici: Nous pourrions suivre les fichiers de correctifs archivés dans le contrôle de code source et les réappliquer un par un dans le bon ordre., ou nous pourrions suivre les correctifs dans un référentiel distinct prenant en charge le branchement.
En fin de compte, nous avons choisi d'opter pour le suivi des branches de fonctionnalités dans un référentiel Git distinct.. L'une des raisons en était d'établir un bon pipeline pour rendre très facile la soumission de branches de fonctionnalités et de correctifs en amont..
En les basant sur le dépôt Git libwebrtc, nous pourrions facilement réutiliser les outils Chromium existants en amont pour créer, essai, et soumettre (`gn`, ``client`', `git cl`, et plus).
Pour chaque version de Chromium en amont (comme M143 qui a une étiquette 7499 dans Git), on crée une branche « base/7499 ». Alors, pour chacun de nos patchs (par exemple. "outils de débogage") nous créons une branche « debug-tools/7499 » au-dessus du commit base/7499. Lors d'une mise à niveau de version, nous fusionnons toutes les branches de fonctionnalités, debug-tools/7499 est fusionné dans debug-tools/7559, hw-av1-fixes/7499 dans hw-av1-fixes/7599, et ainsi de suite.
Une fois que toutes les fonctionnalités sont fusionnées avec les conflits résolus et les versions fonctionnelles + essais, nous fusionnons toutes les branches de fonctionnalités séquentiellement pour créer la branche release candidate r7559.
![image[5]-S'échapper de la fourchette: Comment Meta a modernisé WebRTC 50+ Cas d'utilisation pour Windows 7,8,10,11-Winpcsoft.com](https://winpcsoft.com/wp-content/plugins/wp-fastest-cache-premium/pro/images/blank.gif)
Certains avantages intéressants de cette approche sont qu'elle est hautement parallélisable s'il existe de nombreuses branches., il préserve automatiquement tout l'historique/contexte de Git, et il est bien adapté aux améliorations futures de la résolution automatique des conflits de fusion basée sur LLM.. En plus, les branches de fonctionnalités facilitent la soumission de la branche dans son ensemble en tant que contribution en amont dans OSS.
Le résultat: Mises à niveau continues
Cette architecture nous a permis de livrer un binaire contenant à la fois l'ancienne et la nouvelle pile WebRTC.. Nous avons lancé webrtc/latest sur la version M120 et avons depuis progressé vers M145. Au lieu d'avoir des années de retard, nous restons désormais au courant des dernières versions stables de Chromium, ingérer immédiatement les mises à niveau en amont.
Principales victoires en ingénierie
- Performance: Nous avons constaté une baisse de l'utilisation du processeur jusqu'à 10% et les taux d'accidents s'améliorent jusqu'à 3% sur les principales applications.
- Taille binaire: La nouvelle version amont est plus performante, résultant en un 100-200 Ko (comprimé) réduction de la taille en fonction de l'application.
- Sécurité: Nous avons éliminé les bibliothèques obsolètes (comme usrsctp) et correction des vulnérabilités de sécurité présentes dans la pile existante.
- Tout ce qui précède a entraîné des améliorations observables de l'engagement des utilisateurs tout en fonctionnant sur une pile moderne.
Ce projet prouve que même dans un environnement monorepo complexe avec diverses contraintes, il est possible de moderniser la dette technique sans une réécriture complète. La couche de calage avec une approche à double pile offre un modèle à toute organisation cherchant à échapper au piège de la bifurcation..
Travaux futurs: Maintenance basée sur l'IA
Une fois la migration terminée, nous entrons dans une nouvelle ère de maintenance. Alors que nous vivons désormais « à la tête,« Nous appliquons toujours des correctifs internes en amont. Pour gérer cela efficacement, nous exploitons des outils pour automatiser nos flux de travail:
- Construire la santé: Nous développons des agents pour corriger automatiquement les erreurs de build dans nos branches Git.
- Résolution des conflits: Lors du rebasage de nos correctifs sur les nouvelles versions WebRTC, nous rencontrons des conflits de fusion. Nous formons des agents IA pour résoudre automatiquement la majorité de ces conflits, ne laissant aux ingénieurs humains que les changements architecturaux les plus complexes.
Remerciements
Ce travail a été réalisé par une petite équipe d'ingénieurs qui ont reconnu la valeur de ce projet stratégique et se sont lancés tête première malgré sa complexité.. Ils ont apporté des idées et des solutions créatives, a fait le gros du travail, et a finalement conduit le projet à son terme malgré des obstacles inattendus et des défis uniques en cours de route.: Dor Poule, Guy Hershenbaum, Jared Tarin, Liad Rubin, Tal Benesh, et Yossef Twaik.
