refacto(services): update all services to changes them singleton#185
Draft
thibaud-perrin wants to merge 2 commits into
Draft
refacto(services): update all services to changes them singleton#185thibaud-perrin wants to merge 2 commits into
thibaud-perrin wants to merge 2 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refactoring Services : Singletons partages + RequestContext
Probleme
Constat
Un load test sur
tool-document-managermontre un scaling lineaire : 7s pour 1 user, 168s pour 100 users, alors que le CPU est a seulement 0.3 vCPU. Le temps augmente lineairement non pas a cause du CPU ou du reseau, mais a cause de l'overhead de creation d'objets par requete.Cause racine
A chaque requete (
StartModule), le SDK cree :Chaque channel gRPC implique un handshake TCP + HTTP/2 + potentiellement TLS. Pour 100 requetes concurrentes, cela donne 700 channels, 900 instances de services, le tout en memoire simultanement.
Pourquoi les IDs etaient dans les constructeurs
L'ancienne architecture stockait
mission_id,setup_id,setup_version_iddans chaque instance de service viaBaseStrategy.__init__(). Ces IDs sont ensuite utilises dans les appels gRPC vers le gateway (ex:ReadRecordRequest(mission_id=self.mission_id, ...)).Le constat cle : le gateway applique l'isolation par mission/setup cote serveur. Le SDK client n'a pas besoin d'instances separees pour chaque requete — il suffit de passer les IDs au moment de l'appel.
Solution
Principe
Transformer les services en singletons partages qui recoivent le contexte de requete a chaque appel, comme un pool de connexions DB partage ou chaque query recoit le
user_iden parametre.Architecture
Composants
1. RequestContext (
base_strategy.py)Objet leger (
__slots__) qui porte les IDs de requete :2. BaseStrategy sans IDs (
base_strategy.py)Le constructeur ne prend plus aucun parametre d'identification :
3. Channel pool partage (
grpc_client_wrapper.py)Un seul channel gRPC par adresse cible, partage entre toutes les instances de services. HTTP/2 multiplex les requetes sur un seul channel.
4. Services Grpc* :
ctxen parametre de methodeLes methodes qui utilisaient
self.mission_idprennent maintenantctx: RequestContexten premier parametre :Services concernes :
GrpcStorage,GrpcFilesystem,GrpcUserProfile.Services non concernes (n'utilisent pas les IDs) :
Agent,Identity,Snapshot,Registry,Communication.5. Bound* wrappers (
bound_strategies.py)Pour que l'API publique reste identique (
context.storage.store(collection, record_id, data)sans passerctx), des wrappers injectent leRequestContextautomatiquement :Trois wrappers crees :
BoundStorageStrategy,BoundFilesystemStrategy,BoundUserProfileStrategy.6. ServicesConfig : shared vs per-request (
services_config.py)7. BaseModule._init_strategies : wrapping (
_base_module.py)A chaque requete, cree un
RequestContextet wrape les singletons :Cas special : CostStrategy
CostStrategyreste per-request car il accumule des compteurs de cout (_limits,_accumulated) lies a la session. C'est le seul service dans_PER_REQUEST_SERVICES.Categorisation des services
Gains attendus
Fichiers modifies
Infrastructure
src/digitalkin/services/base_strategy.py— RequestContext + BaseStrategy sans IDssrc/digitalkin/grpc_servers/utils/grpc_client_wrapper.py— Channel pool partagesrc/digitalkin/services/services_config.py— init_shared_services() + init_strategy() modifiesrc/digitalkin/services/bound_strategies.py— Nouveau : BoundStorage/Filesystem/UserProfilesrc/digitalkin/modules/_base_module.py— _init_strategies wrape les singletonssrc/digitalkin/models/module/module_context.py— Types Bound* dans ModuleContextServices adaptes (ctx en parametre)
src/digitalkin/services/storage/storage_strategy.pysrc/digitalkin/services/storage/default_storage.pysrc/digitalkin/services/storage/grpc_storage.pysrc/digitalkin/services/filesystem/filesystem_strategy.pysrc/digitalkin/services/filesystem/default_filesystem.pysrc/digitalkin/services/filesystem/grpc_filesystem.pysrc/digitalkin/services/user_profile/user_profile_strategy.pysrc/digitalkin/services/user_profile/default_user_profile.pysrc/digitalkin/services/user_profile/grpc_user_profile.pyServices adaptes (constructeur simplifie, pas de ctx)
src/digitalkin/services/registry/registry_strategy.pysrc/digitalkin/services/registry/grpc_registry.pysrc/digitalkin/services/communication/default_communication.pysrc/digitalkin/services/communication/grpc_communication.pyCas special
src/digitalkin/services/cost/cost_strategy.py— Garde les IDs dans le constructeur (per-request)Initialisation
src/digitalkin/core/job_manager/base_job_manager.py— Appel init_shared_services()src/digitalkin/core/job_manager/taskiq_broker.py— Appel init_shared_services()src/digitalkin/grpc_servers/module_servicer.py— GrpcRegistry(client_config)src/digitalkin/grpc_servers/module_server.py— GrpcRegistry(client_config)