ASA Compass · entrée K-47 · session hotel-saint-michel-97 · 1er septembre 2026
Un Worker Cloudflare et une base D1 rendent, à la requête, l'index, la page d'une collection et la page d'une pièce. Le site actuel n'a pas été touché. Les chiffres ci-dessous disent chaque fois sur quel geste ils ont été pris.
Le montage tient, et il règle les trois murs d'un coup. Le catalogue entier (16 150 collections, 1 891 551 pièces) tient dans une base D1 de 376 Mo, sous le plafond du plan gratuit, avec trois fichiers déployés au lieu de 18 575. Une page de collection froide répond en 98 ms depuis Montréal, une page de pièce en 137 ms, et l'une comme l'autre en 52 ms dès qu'elle est en cache. Un robot reçoit du HTML complet, un 404 quand la page n'existe pas, un 503 quand la base est absente.
Ce que la mesure a trouvé et que la question ne posait pas : le budget gratuit de 100 000 requêtes par jour est celui du compte, partagé avec le Worker qui sert les images. Ce sont les vignettes, pas les pages, qui fixeront le jour où Karl paie, et c'est déjà vrai du site statique. La bascule ne change pas ce jour-là ; elle change ce que coûte le catalogue quand il grossit : rien.
Recommandation : basculer, par paliers, en commençant par les pages de pièces, qui n'existeront jamais en statique et que K-48 exige. Rester sur le plan gratuit ; le premier dollar (5 $ US par mois) se justifie le jour où /sante ou le tableau de bord montre le compte à 100 000 requêtes par jour, et il achète 10 millions de requêtes par mois et 10 Go de base.
Un dossier séparé, rendu-demande/, avec son Worker (asa-compass-rendu), sa base D1 et ce projet Pages. Le Worker lit la base et compose le HTML avec le CSS du site, extrait tel quel d'une page engendrée. Il sert :
/, avec la recherche par nom, le filtre des collections dormantes (K-43), un filtre par standard (K-51) et l'étendue du catalogue en paramètre ;/c/{limace}, avec ses pièces (48 par page, pagination par curseur), ses ventes et son étiquette de distribution datée ;/a/{id}, pour n'importe laquelle des 1 891 551, avec ses voisines dans la série et ses ventes ;/sitemap.xml (un index de 49 fichiers, 1,9 million d'adresses), /robots.txt, /sante ;/c/x.html et /a.html?id=, redirigées en 301.Pas bâti, volontairement : le portefeuille, le Portfolio, le marché, les propriétés ARC-69 et la rareté (la session K-48 les produit ; la page-pièce leur réserve la place et le dit). Le prototype est en anglais comme le site.
K-47 demandait de distinguer ce que la sonde connaît, ce que le site retient, ce que le site publie. La base du prototype les sépare en tables :
Geste : curl avec l'agent Googlebot/2.1 sur /c/algokitten et /a/201269711, depuis Montréal, le 1er septembre à 12 h 40 UTC ; script robot.sh.
| Ce que Google exige | Ce que le prototype rend | |
|---|---|---|
| un code 200 et du HTML lisible sans exécuter de script | 200, 26 347 octets pour la collection, 7 114 pour la pièce, zéro balise <script>, 49 liens internes dans le HTML de la collection | oui |
| un titre, une description, un canonique | présents, canonique absolu sur l'adresse du Worker | oui |
une og:image absolue (défaut de K-44 sur 2 231 pages) | absolue, vers la vignette du cache de médias | réglé par construction |
| un temps de réponse court (Google : « responses over two seconds can cause Googlebot to reduce its crawling frequency ») | page froide 98 à 137 ms en médiane, 270 ms au pire sur 81 pages | oui |
| un sitemap tenu à jour (Google : « Keep your sitemaps up to date ») | index de 49 fichiers rendu depuis la base, 40 000 adresses par fichier, XML valide (xmllint), 3,5 Mo le plus gros | oui, et il ne peut pas se périmer : il lit la base |
| un 404 quand la page n'existe pas (Google : « a 404 status code is a strong signal not to crawl that URL again ») | 404 avec une page, noindex ; le site actuel rend l'accueil en 200 | réglé |
| pas de « soft 404 » (page vide en 200) | une base absente rend un 503 avec Retry-After, jamais une page vide en 200 | oui |
| que Cloudflare ne bloque pas le robot | aucun défi, aucun en-tête cf-mitigated, cf-cache-status: HIT sur la seconde visite | oui |
Ce que cette mesure ne prouve pas, et il faut le dire : qu'une page soit effectivement indexée par Google. Cela ne se mesure que dans la Search Console, sur un domaine, après des semaines. Le prototype vit sur workers.dev, un sous-domaine partagé que Google traite avec réserve. Ce qui est prouvé, c'est que rien dans le montage ne manque à ce que Google demande à une page statique : le HTML servi à un robot est identique à celui servi à un visiteur, et il est plus complet que celui du site actuel, dont la page-pièce a.html se remplit en JavaScript côté navigateur et n'offre donc rien à un robot.
Geste : 81 adresses tirées au hasard (l'index, 25 familles publiées, 15 non publiées, 40 pièces), chacune demandée à froid avec ?froid=1 (le Worker saute le cache et interroge la base) puis à chaud (adresse normale, servie par le cache de bordure), par curl depuis Montréal, le 1er septembre entre 12 h 45 et 13 h 05 UTC, après les deux correctifs de la section 4. Script mesurer.sh, relevé mesures-apres-correctif.csv. Le temps « base » vient de l'en-tête Server-Timing écrit par le Worker autour de son lot de requêtes D1.
| Page | froide, médiane | froide, p90 | dont base D1 | en cache, médiane | en cache, p90 |
|---|---|---|---|---|---|
| collection (n = 40) | 98 ms | 123 ms | 41 ms | 52 ms | 74 ms |
| pièce (n = 40) | 137 ms | 173 ms | 81 ms | 52 ms | 76 ms |
| index (n = 1) | 167 ms | 97 ms | 48 ms | ||
pire cas : page 6 250 de la famille de 614 133 pièces (?apres=23982241-897151367) | 142 ms | 52 ms |
Pourquoi la base coûte 41 ms pour une requête qui s'exécute en 0,3 ms : la base D1 vit dans la région ENAM (les réponses portent served_by_colo: ORD, Chicago), et le Worker s'exécute là où est le visiteur (ici YUL, Montréal). Chaque page froide paie un aller-retour Montréal-Chicago par lot de requêtes ; la page-pièce en fait deux (la pièce, puis ses voisines), d'où ses 81 ms. Un visiteur d'Europe paierait un aller-retour plus long sur sa première visite d'une page, et seulement sur celle-là.
Où ce modèle peut décevoir, et ce qu'on y a vu : le cache de bordure est par centre de données (Cloudflare : « the contents of the cache do not replicate outside of the originating data center »). Une page chaude à Montréal est froide à Paris. Sur un catalogue de 1,9 million de pages et un trafic modeste, la plupart des visites seront donc des visites froides, et c'est le chiffre froid, 98 à 137 ms, qui compte. Il reste sous ce que le site statique offre pour sa page-pièce, qui doit interroger l'indexeur public depuis le navigateur avant d'afficher quoi que ce soit.
Temps CPU, relevé par wrangler tail sur quatre requêtes : 3 ms pour une collection froide, 1 ms pour une pièce froide, 4 ms pour l'index froid, 0 ms en cache. Le plan gratuit permet 10 ms par invocation.
Sources tarifaires, lues le 1er septembre 2026 sur developers.cloudflare.com/workers/platform/pricing, …/workers/platform/limits et …/d1/platform/limits. Plan gratuit : 100 000 requêtes par jour pour le compte, 10 ms de CPU par invocation, D1 limitée à 500 Mo par base et 5 millions de lignes lues par jour ; « Requests to static assets are free and unlimited ». Au-delà, Cloudflare rend l'erreur 1027 jusqu'à minuit UTC. Plan Workers Paid : 5 $ US par mois, 10 millions de requêtes incluses puis 0,30 $ le million, 30 millions de ms CPU incluses puis 0,02 $ le million, D1 à 10 Go par base, 25 milliards de lignes lues incluses puis 0,001 $ le million.
Mesures d'unités, sur la campagne ci-dessus : une page de collection froide lit 33 lignes D1 en médiane (163 au p90, 2 581 pour AOWL, une famille de trois séries que la base doit trier entière) ; une page de pièce en lit 16 (438 au pire) ; l'index froid en lit 35 573 parce qu'il compte le catalogue. Une page en cache ne lit rien, mais compte quand même une requête Worker : sur workers.dev, le Worker s'exécute avant de répondre depuis le cache. Chaque vignette affichée est une requête au Worker du cache de médias, sur le même compte.
Les scénarios posés (hypothèses explicites : 30 % des pages vues par des visiteurs sont froides ; 60 % des pages vues sont des collections ; un robot voit toujours une page froide ; une page de collection montre 48 vignettes sur le prototype, 8 à 12 sur le site actuel : on retient 48, le pire) :
| Scénario | pages / jour | + vignettes / jour | lignes D1 / jour | plan gratuit | si plan payant |
|---|---|---|---|---|---|
| A · aujourd'hui : 200 visiteurs, 6 pages, 3 000 pages de robots | 4 200 | 34 560 | 457 000 | tient | 5,00 $ |
| B · le catalogue trouve son public : 2 000 visiteurs, 8 pages, 20 000 de robots | 36 000 | 460 800 | 1,1 M | les vignettes dépassent | 6,47 $ |
| C · 10 000 visiteurs, 8 pages, 20 000 de robots | 100 000 | 2,3 M | 1,7 M | dépasse | 23,64 $ |
| D · pointe de robots : 2 000 visiteurs et 100 000 pages de robots | 116 000 | 460 800 | 3,5 M | dépasse | 7,19 $ |
| E · gros trafic : 50 000 visiteurs, 8 pages, 100 000 de robots | 500 000 | 11,5 M | 7,0 M | dépasse | 110,18 $ |
Le point de bascule, en visiteurs par jour : sur les pages seules, le palier gratuit tient jusqu'à environ 10 000 visiteurs par jour à 8 pages. En comptant les vignettes servies par le Worker de médias, il tient jusqu'à environ 420 visiteurs par jour. Le second chiffre est le vrai, et il vaut déjà pour le site statique. Le premier dollar est l'abonnement de 5 $ US par mois ; les scénarios B et D, plausibles, restent entre 5 et 8 $. Un plan Pages supérieur (plafond à 100 000 fichiers) ne déplace qu'une date et ne couvre jamais les pièces ; le même abonnement de 5 $ donne aussi bien la limite Pages relevée que les Workers, D1 à 10 Go et Time Travel à 30 jours. Aucun abonnement n'a été engagé ni aucune ressource payante créée par cette session.
Ce qui ferait sortir les vignettes du compte : servir le seau R2 directement (adresse publique r2.dev ou un domaine sur le seau), sans passer par un Worker. R2 n'a pas de frais de sortie. Cela appartient au périmètre du cache de médias, aujourd'hui sans propriétaire ; c'est signalé, pas fait.
Ce que coûte la croissance du catalogue, dans ce modèle : zéro requête, zéro fichier. Seule la taille de la base bouge. Les 376 Mo mesurés laissent 124 Mo sous le plafond gratuit de 500 Mo, soit environ 600 000 pièces de plus au tarif de 200 octets la ligne ; les années 2023 et suivantes ne tiendraient donc pas sur le plan gratuit sans amincir les lignes (l'adresse du créateur, 58 octets, se répète 1,9 million de fois) ou sans passer à 10 Go. C'est l'endroit où le plan gratuit finira par céder, et il le dira : l'import refuse au-delà du plafond.
Geste : deux pannes simulées dans le Worker, ?panne=base (D1 refuse) et ?panne=lente (D1 ne répond jamais), sur une page jamais rendue, puis sur la même page après un rendu, le 1er septembre à 12 h 42 UTC. Ce sont les chemins de défaillance du Worker qui sont mesurés, pas une panne réelle de D1 : personne ne peut en déclencher une.
| Situation | Réponse | délai |
|---|---|---|
| base qui refuse, page jamais rendue | 503, page d'explication, Retry-After: 120, x-rendu: panne | 62 ms |
| base muette, page jamais rendue | 503, coupée par le délai de garde de 4 s au lieu d'attendre sans fin | 4 055 ms |
| base qui refuse, page déjà rendue une fois | 200, la dernière copie connue, x-rendu: secours, x-panne qui dit pourquoi, Cache-Control: no-store | 71 ms |
/sante, base qui refuse | 503 en JSON avec le motif | |
/sante, base présente mais table des pièces vide (mesuré avant l'import) | 500 en JSON : « la pièce témoin 201269711 manque » | 125 ms |
Est-ce acceptable ? Un site statique ne tombe jamais ; celui-ci tombe avec sa base, mais il tombe en le disant. Une page déjà vue dans ce centre de données survit sept jours à la panne (copie de secours en cache) ; une page jamais vue rend un 503 que Google traite comme temporaire (« 5xx and 429 server errors prompt Google's crawlers to temporarily slow down with crawling ») et que le visiteur lit comme une explication, pas comme une page blanche. La dégradation est donc franche et réversible. Ce qu'elle n'est pas : invisible. C'est ce que K-49 demande.
Deux limites de cette protection : la copie de secours est par centre de données et ne couvre que ce qui y a déjà été demandé ; et D1 étant gérée par Cloudflare, ses pannes se confondent avec celles des Workers, qui abattraient aussi le site statique.
| Mur | Site statique, plan gratuit (20 000 fichiers) | Site statique, plan payant (100 000 fichiers, depuis le 23 janvier 2026) | Rendu à la demande |
|---|---|---|---|
1 · aujourd'hui, 18 575 fichiers dont 15 314 d'img/ jamais servis | 93 %. Retirer img/ ramène à 16 %. | 19 % | 3 fichiers |
| 2 · le catalogue complet, environ 23 500 fichiers | franchi avant d'y arriver | 24 %, tient, mais 3 Go de HTML à téléverser à chaque régénération | 3 fichiers, 376 Mo de base, import en 217 s |
| 3 · les pièces, 1 891 551 pages, exigées par K-48 | jamais | jamais : 100 000 est dix-neuf fois trop petit | rendues, 137 ms à froid, sitemap de 49 fichiers |
La correction de la prémisse compte : K-47 disait « limite dure de 20 000 » et c'est vrai du plan gratuit, sur lequel Karl se trouve ; le plan payant la porte à 100 000 (variable PAGES_WRANGLER_MAJOR_VERSION=4 à poser dans le projet). Cela recule le mur 2 d'un ordre de grandeur et ne fait rien au mur 3. Payer pour le statique déplace une date ; le rendu à la demande supprime la variable.
Deux formes de requête, écrites naturellement, coûtaient des centaines de milliers de lignes lues, l'unité que D1 facture. Elles sont mesurées sur la plus grosse famille (D01-39, 614 133 pièces) avec wrangler d1 execute --json, qui rend meta.rows_read :
| Forme | lignes lues | Forme retenue | lignes lues |
|---|---|---|---|
pagination LIMIT 48 OFFSET 300000 | 300 048 | curseur (round, id) > (?, ?) … LIMIT 48 | 49 |
page 12 000 par ?page=, mesurée sur le Worker | 1 152 013 en 855 ms | même page par ?apres= | 112 en 52 ms |
voisines par round < ? OR (round = ? AND id < ?) avec paramètres liés, depuis le Worker | 614 146 | (round, id) < (?, ?) | 7 |
Sans ces deux correctifs, huit visites de pièces de cette seule famille auraient épuisé les 5 millions de lignes quotidiennes du plan gratuit. Avec, la campagne complète des 81 pages a lu 40 000 lignes. La leçon vaut pour tout ce qui s'écrira sur cette base : mesurer les lignes lues de chaque requête avant de la garder, la commande est dans le LISEZMOI.md.
/nimporte/quoi, /c/inexistant-xyz et /a/1 rendent chacun un 404 avec une page qui le dit, et noindex. Le site actuel rend l'accueil en 200 pour les trois.etat (les étiquettes écartées par défaut, lues dans distribution.json) ; changer le défaut est une mise à jour d'une ligne, pas une régénération de 2 222 pages. Le filtre annonce ce qu'il masque (« 494 hidden by the default filter »), la recherche par nom le traverse (/?q=goanna), l'adresse d'une collection masquée reste valide et le dit, et les compteurs disent sur quoi ils comptent.SELECT DISTINCT sur la base ; un standard nouveau y apparaît dès qu'une pièce le porte. Aujourd'hui ils sont devinés depuis l'URL (ARC-19, ARC-3, ipfs, http) : le champ réel viendra de K-48 et se branchera à la même place.Le critère : ce qui demande une intervention manuelle régulière est une dette mortelle ; six mois sans Karl ; échouer bruyamment.
| Ce qui, aujourd'hui, demande Karl | Dans ce montage |
|---|---|
| régénérer et republier le site à chaque changement de catalogue (3 261 fichiers, 1 Go) | disparaît : une ligne écrite dans la base est en ligne à la requête suivante |
| régénérer pour changer un réglage par défaut (K-43, K-51) | disparaît : c'est une ligne de la table etat |
| surveiller que le déploiement n'a pas dépassé une limite de fichiers | disparaît : trois fichiers |
| s'apercevoir qu'une page manque (aucun 404) | disparaît : le 404 existe, et le balayage de K-44 peut enfin compter |
| s'apercevoir que la donnée vieillit | /sante rend un 500 avec le texte « la donnée a N jours : le rafraîchissement ne tourne plus » au-delà de 45 jours, et un 503 si la base manque. Un contrôle externe gratuit (UptimeRobot ou équivalent, à brancher par Karl) lui écrit à sa place |
| voir venir la limite du plan gratuit | reste à faire : Cloudflare rend l'erreur 1027 au-delà de 100 000 requêtes par jour, bruyamment mais tard. Le tableau de bord montre la consommation ; /sante ne la connaît pas |
| réimporter la base quand la sonde a tourné | 217 s, une commande, mais une commande. Doit devenir l'étape finale automatique de reconstruire.sh, sinon c'est une intervention |
Le solde est net : le montage supprime quatre interventions régulières et en laisse deux, qui existent déjà sous d'autres formes. Ce qu'il ajoute de fragile, c'est une dépendance à D1, gérée par Cloudflare comme le sont déjà Pages, R2 et le Worker du marché ; pas un serveur, pas un indexeur, pas une équipe.
Si la bascule est retenue, catalogue.sqlite cesse d'être un fichier de travail qu'on régénère et la base D1 cesse d'être un essai : ce que les visiteurs voient en dépend. Elle entre donc dans ce qui ne se recalcule pas sans coût. Ce que le prototype a mesuré et ce qui est proposé :
wrangler d1 export --remote, 578 Mo de SQL, 68 Mo compressés) et une reconstruction depuis catalogue.sqlite coûte 217 s. La base est donc reconstructible en quatre minutes tant que la règle suivante tient.catalogue.sqlite, l'import copie dans D1 ; le rafraîchissement incrémental, quand il tournera dans un Worker planifié, écrit dans D1 et dans un journal R2 des faits récupérés. Alors D1 n'est jamais la seule copie de quoi que ce soit, et la sauvegarde n'est pas un geste de Karl : c'est la chaîne, et une reconstruction. C'est la même règle qu'« un fichier, un auteur ».marquage-manuel.json), les revendications et annonces du service de marché (base D1 asa-compass-market). Ce sont eux qui méritent une copie hors de Cloudflare, et ils la méritent déjà aujourd'hui, bascule ou non.img/ du déploiement : 15 314 fichiers et 564 Mo pour un repli que 13 807 médias contrôlés n'ont jamais déclenché. Le site passe de 93 % à 16 % de la limite. Appartient à la session chantier.catalogue.sqlite sur le disque externe : copier la base en local avant tout traitement (17 s), transmis au greffier.etat, tenue à l'import, le ramènerait à quelques dizaines.img/ (chantier). Gratuit, immédiat, achète le temps du reste./a/{id} est routée vers le Worker (Pages sait déléguer un chemin à un Worker).reconstruire.sh, puis du rafraîchissement incrémental un Worker planifié qui écrit dans D1 et dans un journal R2./sante, qui écrit à Karl quand la base tombe ou quand la donnée a plus de 45 jours.Ce qui appartient à Karl : décider de basculer ; décider du plan ; décider si les vignettes sortent du Worker. Rien de tout cela n'a été fait.
base D1 asa-compass-rendu créée, région ENAM catalogue.sqlite copié en local : 17 s (l'export sur le disque externe n'avait rien produit en 6 min) exporter.py : 16 150 collections, 14 554 familles, 50 568 ventes, 1 891 551 pièces en 19 fichiers : 20 s import familles + collections + état : 14 s · ventes : 9 s (après réduction à 120 lignes par ordre) import des pièces : 19 lots de 100 000, 9 à 15 s chacun, 217 s au total, 3,79 M lignes écrites taille finale de la base : 376 Mo (plafond gratuit : 500 Mo) campagne de 81 pages à froid puis à chaud : deux fois, avant et après correctifs sauvegarde complète wrangler d1 export : 92 s, 578 Mo, 68 Mo compressés captures Chrome sans fenêtre : index, collection, pièce, 404, mobile 375 × 667
Les relevés bruts (mesures.csv, mesures-apres-correctif.csv), les scripts et la marche à suivre pour tout rejouer sont dans rendu-demande/ sur le disque de Karl.