# 06. Synchro et exploitation **Le but :** comprendre exactement ce que fait la synchro, la régler, la surveiller, et savoir quoi faire quand quelque chose cloche. --- ## 1. Une passe de synchro, pas à pas Le job `rag-kit-sync` exécute `python sync_job.py` (fichier `app/kit.py`, fonction `run_sync`) : 1. Lit la liste des dossiers dans `gs:///config.json`. 2. Pour chaque dossier : lit le dossier lui-même (s'il est **illisible** → avertissement, et **aucune suppression** pendant cette passe), puis liste tous ses fichiers, sous-dossiers compris (hors corbeille). 3. Liste les fichiers déposés (`gs:///uploads/`). 4. **Souhaité** = (2) + (3). **Présent** = fichiers du corpus, chacun identifié par son ID Drive ou son URI `gs://`. 5. **Supprime** du corpus ce qui est présent mais plus souhaité. 6. **Importe**, avec les mêmes réglages (découpage 512/100, layout parser) : - Drive : **seulement** les fichiers nouveaux ou modifiés dans Drive depuis leur dernière indexation (date `modifiedTime` de Drive comparée à la date d'indexation du corpus), par lots de 25. Raison (vérifiée) : RAG Engine réimporte **tout** fichier Drive à chaque import, même inchangé ; sans ce filtre, chaque passe re-parserait (et refacturerait) tout le dossier. - Dépôts : le préfixe `uploads/` entier ; là, RAG Engine ignore lui-même l'inchangé (vérifié). 7. **Contrôle** : un dossier dont aucun fichier n'est dans le corpus → « Le dossier est-il partagé avec l'agent RAG ? ». 8. Écrit le rapport `gs:///sync/last.json` (lu par l'interface) ; le détail par fichier est dans `gs:///sync/runs/-.ndjson` (gardé 30 jours). **Un seul import à la fois par corpus** (vérifié : sinon `FailedPrecondition: There are other operations running`). Chaque action (dépôt, suppression, dossier, bouton) lance **une nouvelle exécution** du job, même si une autre tourne. Une passe qui trouve le corpus occupé **attend 15 s et recommence entièrement** (listing frais), jusqu'à 15 min : la dernière passe lancée voit donc toujours le dernier état, sans rapport écrasé (statut `occupe` non écrit). (Une première version posait une demande « à refaire » pendant la passe : elle se perdait quand la passe finissait au même instant — vérifié en test navigateur, d'où ce choix plus simple.) ## 2. Quand elle tourne | Déclencheur | Délai | |---|---| | Cloud Scheduler `rag-kit-sync` | `SYNC_SCHEDULE` (défaut toutes les 10 min, fuseau Europe/Paris) | | Ajout/retrait d'un dossier, dépôt ou suppression d'un fichier dans l'interface | immédiat | | Bouton « Synchroniser maintenant » | immédiat | | À la main | `gcloud run jobs execute rag-kit-sync --region=europe-west4 --wait` | **Changer la fréquence :** modifiez `SYNC_SCHEDULE` dans le `.env`, rechargez (`source scripts/config.sh …`) puis `terraform apply` (ou relancez `deployer_shell.sh`). Une passe sans changement coûte quelques secondes de Cloud Run et de listing ; toutes les 5 min est raisonnable, toutes les minutes non (passes qui se chevauchent). **Mettre en pause :** `gcloud scheduler jobs pause rag-kit-sync --location=europe-west4` (et `resume`). ## 3. Surveiller ```bash # Le dernier rapport gcloud storage cat gs://$BUCKET/sync/last.json # Les 5 dernières exécutions du job et leur état gcloud run jobs executions list --job=rag-kit-sync --region=$REGION --limit=5 # Les journaux d'une passe (le rapport JSON est la dernière ligne) gcloud logging read 'resource.type="cloud_run_job" AND resource.labels.job_name="rag-kit-sync"' --limit=20 \ --format='value(textPayload)' # Les fichiers en échec de la dernière passe gcloud storage cat "gs://$BUCKET/sync/runs/*.ndjson" | grep -v '"Status": "OK"' ``` **Alerte conseillée :** dans Cloud Monitoring, une alerte sur les exécutions en échec du job (`run.googleapis.com/job/completed_execution_count` avec `result = failed`). ## 4. Opérations courantes | Besoin | Comment | |---|---| | Tout réindexer (après changement de parsing, par exemple) | Supprimer les fichiers du corpus puis synchroniser : la passe réimporte tout. Avec Terraform : `terraform apply -replace=google_vertex_ai_rag_corpus.docs` (nouveau corpus vide) puis « Synchroniser » | | Changer le modèle de réponse | `MODEL` / `MODEL_LOCATION` dans le `.env` puis `apply` : pas de réindexation | | Changer le modèle d'embeddings | Impossible sur un corpus existant : nouveau corpus (`-replace`, voir au-dessus) | | Désactiver le layout parser | `LAYOUT_PARSER=false` puis `apply` ; **tout est réindexé** à la passe suivante | | Ajouter des collègues | `USERS="user:a@x.com group:equipe@x.com"` puis `apply` | | Mettre à jour le code | Modifier `app/…` puis `apply` : nouvelle image (hachage du code), nouvelle révision | ## 5. Limites | Limite | Valeur | Au-delà | |---|---|---| | Fichiers par dossier (listing complet à chaque passe) | quelques milliers | API `changes` de Drive (jeton stocké dans le bucket) | | Taille d'un dépôt par l'interface | 30 Mo | Mettre le fichier dans le dossier Drive | | Durée d'une passe | 1 h (timeout du job) | Plusieurs corpus, ou passes plus fréquentes | | Conversations | en mémoire, 1 instance | Base de sessions (Cloud SQL) via `SESSION_SERVICE_URI`, puis plus d'instances | | Décalage Drive → index | ≤ `SYNC_SCHEDULE` | Bouton « Synchroniser » | ## 6. Sources « Google Drive connecté » (v2) Pour un compte connecté ([04-bis](04-bis-connexion-google.md)), la passe (fichier `app/gdrive.py`) : 1. Prend le **verrou** de la source (`locks/` dans le bucket : une seule passe à la fois par source, sinon deux canaux push seraient ouverts — vérifié). 2. **Incrémental** : lit seulement les changements depuis le dernier jeton de page (`changes.list`), même sur un Drive entier. **Complet** (1re fois, changement de périmètre, une fois par jour, ou dossier déplacé en mode « dossiers ») : liste tout et réconcilie. 3. Recopie le nouveau et le modifié dans `gs:///mirror///` (Docs → .docx, Slides → .pptx, Sheets et Dessins → .pdf), supprime du miroir ce qui a disparu, est sorti du périmètre ou a été renommé. 4. Ouvre ou renouvelle le **canal push** (`changes.watch`, 7 jours) si la source est en « auto ». Puis la passe commune indexe `uploads/` et `mirror/` (RAG Engine saute l'inchangé) et retire du corpus ce qui n'est plus dans le miroir. **Une source en panne** (jeton expiré…) garde son miroir tel quel : rien n'est retiré de l'index. **Déclencheurs** : notification push (`TRIGGER=auto`), planification (`TRIGGER=auto`, toutes les 5 min), bouton et actions de l'interface (`TRIGGER=manuel`). Une source dont l'« auto » est décochée n'est traitée qu'en manuel (vérifié : passe auto → document absent ; « Synchroniser maintenant » → présent). État par source : `gs:///state/.json` (jeton de page, fichiers suivis, canal).