L'architecture est centrée sur un service d'autorisation inspiré de Zanzibar distribué, composé de trois niveaux : des nœuds d'évaluation de Check Engine sans état, une base de données Snapshot stockée globalement et un pipeline Watch déclenché par des événements pour l'invalidation du cache. Cette conception sépare les vérifications d'autorisation lourdes en lecture des mises à jour de permissions lourdes en écriture, permettant une mise à l'échelle indépendante de la capacité d'évaluation tout en maintenant de fortes garanties de cohérence pour les mutations de relations. Le système utilise le modèle zookie de Google pour limiter la staleness sans sacrifier les avantages de performance de la mise en cache en périphérie.
Les nœuds Check Engine déployés dans des lieux de périphérie évaluent les requêtes d'autorisation en utilisant des caches en mémoire locaux et des bitmaps de permissions compacts. Ces nœuds chargent les configurations de l'espace de noms à partir d'un cluster etcd répliqué et résolvent les tuples de relations à partir d'une instance CockroachDB ou Spanner géo-partitionnée, qui fournit une cohérence externe grâce à TrueTime ou à des horloges logiques hybrides. Chaque nœud maintient des filtres de Bloom pour empêcher les échecs de cache d'atteindre la base de données lorsque des relations n'existent définitivement pas.
Pour gérer le "problème du nouvel ennemi" où les révocations récentes peuvent être invisibles aux caches de périphérie, le système met en œuvre des jetons zookie - des horodatages opaques encodant la cohérence des instantanés - qui forcent des échecs de cache pour les opérations sensibles dans une fenêtre d'incertitude configurable. Les clients reçoivent ces jetons lors des vérifications initiales et doivent les rejouer lorsqu'ils accèdent à des ressources de grande valeur, garantissant que les permissions récemment révoquées sont immédiatement visibles sans nécessiter une invalidation de cache globale. Ce mécanisme équilibre le besoin de faible latence avec l'exigence de sécurité d'une visibilité immédiate des révocations.
L'invalidation du cache s'appuie sur un pipeline Watch soutenu par Apache Kafka qui propage les changements de tuples à tous les nœuds Check Engine de périphérie en utilisant un hachage cohérent, garantissant que les tempêtes de révocation déclenchent des rafraîchissements de cache échelonnés au lieu d'un bombardement synchronisé de la base de données. Le pipeline utilise un backoff exponentiel jitteré pour empêcher les troupeaux tonitruants lorsque des objets largement partagés subissent des modifications de permissions. Cette architecture garantit que le système maintient une latence inférieure à 10 ms pour les frappes de cache tout en garantissant une cohérence causale pour les mises à jour de permissions à travers des nœuds géographiquement distribués.
Une plateforme de collaboration de documents globale servant 50 millions d'utilisateurs d'entreprise a connu des pics de latence catastrophiques pendant les heures de pointe lors de l'évaluation de hiérarchies de partage complexes. Chaque accès à un document nécessitait de traverser des adhésions de groupes imbriqués et des permissions héritées stockées dans un cluster PostgreSQL monolithique, résultant en des temps de requête supérieurs à 500 ms et des pannes fréquentes lors de mises à jour de permissions en masse lorsque les employés changeaient de départements ou de projets. L'équipe d'ingénierie nécessitait une solution capable de maintenir une latence inférieure à 10 ms tout en garantissant des garanties de sécurité strictes lors des révocations en cascade à travers des structures de dossiers imbriquées.
La première approche a évalué le maintien d'un cluster centralisé PostgreSQL avec des vues matérialisées Redis de chemins de permissions. Les avantages incluaient de fortes garanties ACID assurant une visibilité immédiate des révocations et des sémantiques de transactions simples pour des mises à jour complexes sur plusieurs tables. Les inconvénients impliquaient de graves goulets d'étranglement d'écriture lors de modifications massives de permissions, des risques inévitables de stampede de cache lorsque des documents populaires étaient mis à jour, et une incapacité fondamentale à faire évoluer le débit de lecture géographiquement sans réplicas de lecture complexes qui introduisaient des retards de réplication inacceptables pour des décisions critiques en matière de sécurité.
La deuxième approche a suggéré un déploiement Apache Cassandra entièrement dénormalisé avec résolution de permissions côté application et expiration de cache basée sur TTL. Les avantages comprenaient un excellent débit d'écriture pour les mutations de relations et une disponibilité multi-régionale inhérente sans points de défaillance uniques. Les inconvénients ont révélé des compromis de cohérence éventuelle inacceptables où les permissions révoquées demeuraient visibles pendant des minutes en raison de retards du protocole de gossip, et le manque de suppressions en cascade atomiques créait des failles de sécurité où les utilisateurs conservaient un accès à des ressources après avoir été retirés des groupes parent, violant le principe de moindre privilège.
L'équipe a finalement choisi une architecture de style Zanzibar utilisant CockroachDB pour le stockage de tuples de relation, des sidecars Envoy comme Points d'Application des Politiques, et des nœuds de Check Engine à évolutivité horizontale avec des caches Least Recently Used en façade par des filtres de Bloom. Ce choix a équilibré le besoin de forte cohérence dans les écritures de permissions via une isolation par défaut sérialisable avec des exigences de performance en périphérie grâce à des caches d'évaluation locaux et des flux d'invalidation dirigés par Apache Kafka. Le résultat a réduit la latence d'autorisation p99 de 500 ms à 4 ms, a supporté 15 millions de vérifications par seconde à l'échelle mondiale et a garanti que les révocations de permissions se propagent à tous les nœuds de périphérie dans les 150 millisecondes tout en maintenant 99,99 % de disponibilité.
Comment empêcher les vérifications d'autorisation de retourner des décisions "autoriser" périmées immédiatement après une révocation de permission sans sacrifier les avantages de performance de la mise en cache distribuée ?
Les candidats négligent souvent le modèle zookie ou les vecteurs de version, proposant plutôt une invalidation de cache globale ou des lectures de base de données pour chaque vérification. La solution exige que le service d'autorisation renvoie un jeton de cohérence avec chaque décision qui encode le timestamp de l'instantané des données utilisées. Pour les opérations sensibles ou après des événements de révocation récents, le client doit présenter ce jeton, forçant le Check Engine à vérifier par rapport au stockage central si son cache local est antérieur au timestamp du jeton. Cela garantit une cohérence causale sans nécessiter d'invalidation de cache globale ou sacrifier les performances de lecture pour la majorité des demandes.
Comment architectureriez-vous le mécanisme d'invalidation de cache pour éviter les effets de troupeaux tonitruants lorsque les permissions d'un objet largement partagé sont modifiées, déclenchant potentiellement des millions de rafraîchissements de cache simultanés ?
La technique clé implique un hachage cohérent des clés de cache combiné avec un backoff exponentiel jitteré et la coalescence des demandes aux nœuds de périphérie. Lorsque le pipeline Watch diffuse un changement de tuple, les nœuds de périphérie n'invalident pas immédiatement mais planifient plutôt l'invalidation en utilisant un hachage de l'ID d'objet pour échelonner les rafraîchissements sur une fenêtre temporelle. De plus, chaque Check Engine maintient un groupe de vol pour les vérifications en cours, garantissant que les demandes concurrentes pour le même objet partagent un seul résultat de requête backend, empêchant la surcharge de base de données pendant les mises à jour d'objets populaires.
Pourquoi l'utilisation d'un simple parcours de graphes dirigés est-elle insuffisante pour modéliser les politiques de ReBAC, et comment gérez-vous les contraintes d'intersection et d'exclusion dans un environnement d'évaluation distribué ?
Le parcours de graphes simple échoue à capturer les opérations d'ensemble nécessaires pour des politiques sophistiquées comme "autoriser seulement si l'utilisateur est dans le groupe A ET PAS dans le groupe B". La solution met en œuvre un système de réécriture où les configurations d'espace de noms sont compilées en arbres de décision évalués via des recherches d'index inversés, stockant à la fois les tuples de relations positifs et négatifs de manière explicite. Pour les contraintes d'intersection, le système interroge les deux ensembles et calcule l'intersection au Check Engine, tandis que les exclusions utilisent une évaluation par court-circuit avec une terminaison précoce. Cette approche garantit que la logique booléenne complexe s'évalue efficacement sans nécessiter plusieurs allers-retours vers la base de données.