# Signal Shingle: une nouvelle architecture pour des sites multi-widgets haute performance ASP.NET

<!-- category -- AI,Architecture,ASP.NET,Performance,Patterns,StyloBot -->
<datetime class="hidden">2026-07-24T12:00</datetime>

**Comment livrer des réponses rapides pour des tableaux de bord complexes sans être dérangés? Vous cessez de les rendre sur le chemin de la demande.**

[TOC]

---


## Le problème: parM SK1request fan-out

Un tableau de bord est un type de page trompeurment coûteux.

![Un tableau de bord déployé: compteurs récapitulatifs, un calendrierM SK2 diagramme de sérieMSC3 liste des bots , vues finales et par pays tous partagent une page tout en conservant les surfaces d’actualisation indépendantes](/articleimages/signal-shingle-dashboard.jpeg)

L’opérateur voit un seul tableau de bord; le serveur voit des listes en direct et des vues à fenêtres coûteuses - les agrégats mobiles

```mermaid
flowchart TB
    P[One dashboard page] --> H[Headline counters]
    P --> T[Time-series chart]
    P --> B[Top bots]
    P --> E[Top content pages]
    P --> C[Countries / filters]
    H --> M[Shared hydrated page model]
    T --> M
    B --> M
    E --> M
    C --> M
    M --> S[Versioned widget shingles]
    S --> U[Independent HTMX OOB updates]

    classDef outline stroke:#cbd5e1,stroke-width:1.5px;
    classDef emphasis stroke:#38bdf8,stroke-width:2px;
    class P,H,T,B,E,C,M,S,U outline;
    class M,S,U emphasis;
    linkStyle default stroke:#94a3b8,stroke-width:1.3px;
```

L’application ASP.NET connue permet à chaque widget de saisir ses propres données , d’amorcer ces appels en parallèle , puis d’attendre tous ceux-ci avant de rendre la page

```mermaid
flowchart LR
    R[One page request] --> W1[Summary query]
    R --> W2[Time-series query]
    R --> W3[Countries query]
    R --> W4[Endpoints query]
    R --> W5[Bot query]
    W1 --> J[Task.WhenAll]
    W2 --> J
    W3 --> J
    W4 --> J
    W5 --> J
    J --> V[Render response]

    classDef outline stroke:#cbd5e1,stroke-width:1.5px;
    classDef danger stroke:#fb7185,stroke-width:2px;
    class R,W1,W2,W3,W4,W5,V outline;
    class J danger;
    linkStyle default stroke:#94a3b8,stroke-width:1.3px;
```

Il apparaît rapidement avec une personne et un petit ensemble de données : neuf appels indépendants se chevauchent , de sorte que la page prend environ aussi longtemps que son widget le plus lente plutôt que leur somme

À l’égard de la [Stylo.Bot](https://stylo.bot) dashboard, une charge normale de page signifiait environ neuf appels parallèles à travers le site–/bordement de la passerelle–. La vue d’aujourd’hui pouvait parcourir plus d’un million de rangéesM SK4 Une seule charge silencieuse était déjà multipleMSC5seconde. Sous un trafic concomitante , le pMST8 a passé sept secondes+, parce que la page attendait `Task.WhenAll` d’environ dix opérations coûteuses et chaque visiteur a commencé une autre dizaine

Le parallèle est utile. Par - fan de demandes- la sortie est le problèmeM SK3 À cinq téléspectateurs concurrents , neuf appels sont devenus quarante MSC5 cinq appels concurrents~ . A cinquante téléspectators il s’agit de quatre cent cinquant M SK7 La base de données de connexion voit une tempête croissante d’activités indépendantement coûteuses.

> Fan parallèle-out permet de faire apparaître plus rapidement une seule demande en multipliant la pression qui détruit le système sous charge.

Ce n’est pas la bonne courbe d’échelle pour un tableaux de bord.

---


## La solution: Signal Shingle

Signal Shingle traite chaque widget du tableau de bord comme un fragment de page préliminaire.

Son empreinte digitale est simplement `widget + normalised parameters + data-surface generation`. Une mise à jour de pays ne doit pas invalider une carte d’affichage en direct -viseurs; un filtre modifié ne devrait pas faire froid tous les autres widgets

Ces fragments vivent dans un cache LFU limité. Les lectures chaudes conservent les widgets surveillés résidents

SignalR dit qu'une surface a changé; le client demande la quantité affectée de widgetsM SK1 HTMX applique les fragments de bande retournés-ofMSC3avec IdiomorpheMNK4 Le signal porte un indice nuisible , jamais une copie des données du tableaux de bordMMK6

Dans le modèle, **signal** signifie “considérer la mise à jourM SK1 **Ã‰tablissement d'un rÃ©seau de distribution** est le résultat adressable indépendamment.

Rendre-Les seuls shingles à côté sont insuffisants. Si chaque erreur déclenche un nouveau scan de base de données , le travail coûteux reste en placeM SK3 Le signal Shingle utilise une cache de deux niveaux - avec une main délibérément étroite

```mermaid
flowchart LR
    D[Gateway<br/>authoritative events] --> B[Incremental window buckets<br/>data stays single-source]
    B --> C[Level 1 : content cache<br/>hydrated page result]
    C --> S[Level 2 : shingle cache<br/>versioned OOB widget HTML]
    S --> R[Request path<br/>read + Razor shell]

    T[Schedule tick/poll + LFU demand] --> W[Bounded prewarm waves]
    W --> B
    W --> C
    X[Coalesced SignalR dirty beacon] --> U[HTMX batch update]
    U --> S

    classDef outline stroke:#cbd5e1,stroke-width:1.5px;
    classDef emphasis stroke:#38bdf8,stroke-width:2px;
    class D,B,C,S,R,T,W,X,U outline;
    class C,S,W emphasis;
    linkStyle default stroke:#94a3b8,stroke-width:1.3px;
```

---


## Les deux niveaux de cache : les données demeurent authentiques , la présentation reste prête

La distinction est faite entre un **Modèle** et son rendu **unités de livraison des widgets**. Aucun cache ne devient une autorité pour les données de détectionM SK1

### Layer 1 : buckets incrémentiels

La passerelle est propriétaire des données. Elle contient une projection LFU limitée de buckets à fenêtres incrémentales M SK3 dans la mémoire - avec une rétention journalière de glissement 30-, un ensemble de travail, et non un deuxième entrepôt de données.

Utiliser des buckets fins pour six -horaires et 24-charts d'heuresM SK2 roulement horaire pour les fenêtres longues . Le point est qu'une fenêtre demandée fusionne les buquets plutôt que de le répéter.

```text
last 24 hours  → merge 288 five-minute buckets
last 30 days   → merge 720 hourly buckets

not

last 30 days   → scan and aggregate 1,000,000+ event rows again
```

Nouvelles observations mettent à jour la plus récente bucket. La partie historique n’est pas réexaminéeM SK1 parce qu’une page a été chargée . La passerelle demeure le seul écrivain et le seul endroit qui décide ce que signifie une détection.

### Niveau de cache 1 : le cache du contenu hydraté

En amont, le site web cache les modèles de tableaux de bord hydratés , clé par un ensemble de widgets normalisésM SK2 fenêtre, filtres et générationMSC4 Il permet d’établir des liens entre les données authentiques et la traduction

Cacher un modèle plutôt que le HTML complet permet de conserver les valeurs du CSRF, chrome d'authentification , drapeaux et nonces des fonctionnalités sur la demandeM SK2 coque remise où elles appartiennent.

Sur une clé froide, retourner un état de réchauffement clair plutôt que de recomputer synchronement. Seul le matérialisateur tick obtient un nouveau résultat de pageM SK2 Les lectures ultérieures servent la dernière génération réussie , même alors qu’une nouvelle est en volMSC4

### Niveau de cache 2 : le cache référentiel shingle

Le tableau de bord est délibérément divisé : la passerelle détient les données ; le site Web détient Razor et la relation avec le navigateur

Une fois que le modèle de page existe, un widget peut être rendu comme élément OOB et détenu dans la cache de sangle LFU délimitée avec une sécurité TTL. Un sangle chaud passe à côté tant de la composition qu’au rendering RazorM SK2 une erreur lit le résultat de page déjà -de page chaude lorsque possibleMSC4 ne rend que ce widget `hx-swap-oob="morph"`, et stocke le résultat sous son empreinte digitale

Le point final du lot partitionne les widgets en empreintes digitales chaudes et erreurs.

Ce n'est pas un caching redundant. Niveau 1 réponses “qui est le modèle cohérent de tableaux de bordM SK3 Niveau

---


## Limite du cache: pourquoi ce n'est pas le caching de réponse

Le cache de réponse vaut la peine d’être comparé directement. Un cache normal de réponse stocke une réponse HTTP opaque; Des widgets cachés indépendamment développent des modes d’expiration et de défaillance indépendants . La page peut être chargée rapidement alors qu’elle ne décrit plus un état cohérent.

Les modèles de cache connus sont tous utiles. Ils protègent simplement les différentes frontières :

| Méthode | Ce qu'il stocke MSC2 Le meilleur emplacement MSC3 Pourquoi il n'est pas suffisant par lui-même ici SMC4
|---|---|---|---|
| Cache de réponse HTTP | Réponse HTTP, normalement saisie par URL et demandeM SK3règles variables ♫| Points d'entrée publics GET et CDN ♫
| ASP.Cache de sortie NET | Produit du serveur généréM SK3 avec politique explicite de variation et d'évacuation || Pages coûteuses avec une petite demande stable | , | Requête stable \- |espace de clé |
| DonutM SK1 trou / cache de fragments | Une coque cachée avec des trous dynamiques, ou fragments cachés indépendamment ♫| Pages dont le chrome est stable et dont les petites régions dynamices sont véritablement indépendantes ♫ | Il fait à chaque trou son propre cycle de vie du cache ♫
| Signal Shingle | Une génération hydratée,M SK3modal de page assortie de fragments versionnés d'un widget de livraison | Un tableau de bord dont les parties doivent être mises à jour indépendamment **et** Continuer à décrire un état | La limite du cache est la projection composite ; les échelons sont dérivés de celle-ci

Niveau-2 shingles *sont* un cache de sortie, délibérément petit et fragmentéM SK1 en forme de . La différence est que les fragments ne déterminent pas la réalité des données indépendamment. Le modèle du contenu est constitué d’abord , sous une seule générationMSC5 chaque shingle est un rendering de ce modèle

```mermaid
flowchart LR
    D[One authoritative<br/>gateway dataset] --> G[Generation 184]
    G --> M[One hydrated<br/>page model]
    M --> H[Headline shingle<br/>generation 184]
    M --> T[Chart shingle<br/>generation 184]
    M --> B[Bot-list shingle<br/>generation 184]
    M --> C[Country shingle<br/>generation 184]
    H --> P[One coherent page]
    T --> P
    B --> P
    C --> P

    classDef outline stroke:#cbd5e1,stroke-width:1.5px;
    classDef emphasis stroke:#38bdf8,stroke-width:2px;
    class D,G,M,H,T,B,C,P outline;
    class G,M,P emphasis;
    linkStyle default stroke:#94a3b8,stroke-width:1.3px;
```

Lorsque les données changent, la composition affectée reçoit une nouvelle génération.1 Un navigateur peut voir brièvement celle précédente alors qu’une mise à jour est en vol.2 Mais cette âge est explicite et limité.3 Il n’est jamais un ensemble séparé de caches appartenant à - qui font des allégations contradictoires au sujet de “.

**Une autorité, une génération composée, plusieurs fragments de livraison réutilisablesM SK2**

---


## SSR première, Amélioration progressive deuxième

Signal Shingle n'est pas une API JSON avec un client- panneau de bord peint sur le dessus . Le Stylo.Boot de bord est la première Razor SSR MSC3 compteurs significatifsM SK4 tableauxMNK5 tables MNK6 liens et navigation avant que JavaScript ne fonctionneMRK7 HTMXMMK8 SignalR et Alpine améliorent ensuite cette page plutôt qu'en la remplaçant.

```mermaid
flowchart LR
    R[GET dashboard] --> S[Razor SSR<br/>complete useful HTML]
    S --> V[Operator can read<br/>and navigate now]
    S --> H[HTMX enhancement]
    S --> A[Alpine enhancement]
    S --> G[SignalR enhancement]
    G --> D[Dirty beacon<br/>not dashboard JSON]
    D --> H
    H --> O[GET OOB widget batch]
    O --> W[Versioned HTML shingles]
    W --> M[HTMX / Idiomorph morph]
    A --> I[Local UI state<br/>menus, filters, affordances]

    classDef outline stroke:#cbd5e1,stroke-width:1.5px;
    classDef emphasis stroke:#38bdf8,stroke-width:2px;
    class R,S,V,H,A,G,D,O,W,M,I outline;
    class S,W,M emphasis;
    linkStyle default stroke:#94a3b8,stroke-width:1.3px;
```

HTMX demande le HTML et échange l'île retournée. SignalR fournit un petit signal polluant plutôt que les modèles JSONM SK1 Alpine possède l'état local de l'interface : menus , toggles et prix qui ne méritent pas une rondeMSC4tripMNK5 Retirer n'importe lequel d'entre eux et la page SSR reste utileMST6 ajouter tous les trois et elle gagne en temps réelMSL7 faibleMS- mise à jour des bureauxMSV9

---


## Renouveler est un travail régulier, pas un effet secondaire accidentel du trafic

Le renouvellement se produit à l’extérieur de la demande de page. Il s’agit d’une limite dure : une lecture froide retourne un résultat de réchauffementM SK2 alors que seul le matérialisateur tick compose . La demande de pages est une lecture d’un projet cohérentMSC4 jamais une excuse pour commencer un travail coûteux sur les donnéesMNK5

Les coordonnateurs se sont rangés et, récemment, les enveloppes ont été lues. `Task.WhenAll` portant un nouveau nom.

La limite de coïncidence fait partie de la correction : le boucle refresh obtient un budget de capacité connuM SK1 jamais un ensemble sans limites de connexions. Une mise à jour pleinement chaude n’a aucune composition ou rendering Razor en tout

**Orchestre, non une tempêteM SK1** C’est la règle opérationnelle.

### Préchauffage est couvert, non caching souhaité

Un cache ne peut pas protéger la première demande si elle se réchauffe seulement *après* que la demande arrive. Le matérialisateur a donc deux tâches. **Défaults de pinnage** conserver les choix de fenêtres réelles 6hM SK1 24h+, \7d et \ 30d chaudes **Demande-enveloppes comptabilisées** filtres de couverture que les gens utilisent réellement, classé par nombre d'accès et recevabilitéM SK1 Une seule demande réelle permet de garder une personne vivante.

```mermaid
flowchart TB
    P[Pinned default windows<br/>6h · 24h · 7d · 30d] --> Q[Warm queue]
    L[Recently read filtered views<br/>LFU hotness order] --> Q
    Q --> G{Tick budget<br/>still available?}
    G -->|yes| W[Bounded warm wave<br/>compose once, store model]
    G -->|no| N[Defer to next tick<br/>serve prior generation]
    W --> H[Level 1 content cache]
    H --> Z[Level 2 shingle cache<br/>rendered on demand]

    classDef outline stroke:#cbd5e1,stroke-width:1.5px;
    classDef emphasis stroke:#38bdf8,stroke-width:2px;
    class P,L,Q,G,W,N,H,Z outline;
    class Q,W,H emphasis;
    linkStyle default stroke:#94a3b8,stroke-width:1.3px;
```

Le préchauffage doit correspondre à la surface réelle du produit. Si l’interface offre quatre fenêtres normales et que le chauffeur en sait une, les trois quarts de la première fenêtre attendue -le chemin de clique est encore froidM SK3

---


## LFU n’est pas seulement l’éviction : il est le signal de la demande

LFU est plus que l’évacuation ici. Accès est le signal de la demande : il décide ce qui reste résident , qui se rafraîchère d’abord par les travaux requisM SK3 et ce qui peut disparaître en toute sécuritéMSC4 Ce qui rend l’espace de filtre sans limites géréableMska5 Un filtre de pays surveillé tout le jour demeure chaudM Ska6 un filtre essayé une fois vieillissanteM ska7 Il n’y a pas de tableau distinct Mska8tableau populaire M Ska9 table nécessaireMSKA10

---


## Adaptable à l'état statique

Les tableaux live traditionnels réagissent à un plus grand trafic en faisant davantage de travail, alors ils deviennent moins utiles au point où les opérateurs les ont le plus besoin . Le signal Shingle inverse cette relation. Le contrôleur de rafraîchissement mesure le coût du rafraichement par rapport à un budgetM SK3 les cycles coûteux prolongent des intervalles efficacesMSC4 ceux peu coûteux contractent vers la cadence requiseMST5

```text
refresh cost <= budget  → preserve requested cadence
refresh cost >  budget  → lengthen effective cadence
cost settles            → cautiously shorten it again
```

Il s’agit d’une boucle fermée sur la ressource protégée.

> La charge rend le tableau de bord moins efficace, non plus. C'est pourquoi le modèle décroît plutôt que s'effondre

Il s’agit de la dernière projection cohérente connue.

### Un hybride statique/dynamique , de conception

La moitié statique est le contrat de réponse : un snapshot cohérent à partir des niveaux du cacheM SK2 la moitié dynamique est un procédé budgétaire distinct : scheduler- contrôles dûment dirigésMSC5 accélération optionnelle SignalR pour les widgets vivantsMST6 et réconnexion resyncMst7 La réponse demeure rapide même lorsque le snapshot devient plus vieuxM st8 et l'âge reste visibleMstr9

Ainsi, le système peut glisser continuellement entre deux modes utiles:

```text
quiet / cheap refreshes       → dynamic dashboard, short effective cadences
busy / expensive refreshes    → static-like snapshot, longer declared cadences
```

Il s’agit d’une escalation adaptative intégrée à l’architecture.

---


## La fraîcheur appartient au widget: une invariante non négociable

Non tous les widgets méritent la même fraîcheur. Un graphe de tendance ou un classement des points culminants peut être à quelques minutes ou une heure de retard ; Une liste des visiteurs récents ou des notes courantes a besoin d’une cadence plus étroite

| Classe | Widgets typiques MSC2 Cadence de base MSC3 Pourquoi SMC4
|---|---|---:|---|
| Sommaire | Synthèse, TendancesM SK3 PaysMSC4 Points finals MSC5 minutes à l’heure MSC6 Le signal change assez lentement pour que la stabilité soit plus précieuse qu’une agitation.
| En direct | visiteurs récentsM SK2 séances , noms+, points=, menaces | environ une minute | L'opérateur cherche le mouvement courant

Deux widgets visiblement identiques peuvent demander des cadences différentes. Mettre la cadence dans la clé de cache dupliquait les données

L'invariante est simple:

> Une entrée de cache partagée ne doit jamais servir un staler que par l’un ou l’autre de ses consommateurs actifs demandés.

Formellement:

```text
effective cadence(entry) = MIN(requested cadence of every active consumer)
```

Cadence est un consommateur par *plancher*, ne fait pas partie de la signatureM SK1 Si un consommateur demande 60 secondes et un autre pour cinq minutes M SK3 une entrée partagée rafraîchère chaque

---


## Règles d'honnêteté et de cohérence en chrome

Un système adaptatif doit être honnête quant à son adaptation. Le chrome de widget montre la cadence efficace actuelle M SK1 “à jour tous les SSK3s”, \“à date de toutes les données 5mMSC7 | | “ |

Il faut quelques règles pour garder une projection honnête:

- **Cotisations de génération.** Chaque chunk/shingle enregistre la génération de données qu’il a construite à partir de cette source. Un balisage sec et une réponse peuvent être comparés , afin que l’ancienne mise à jour ne puisse pas superscrire une vue plus récenteM SK3
- **Signales couplés.** Un détecteur chargé peut produire de nombreux événements par seconde. Le chemin du signal les batte en un seul faisceau polluant et une seule décision de renouveler plutôt que d’allumer le navigateur ou la boucle de réfrigération.
- **Resync lors de la réconnexion.** Un raccord n’est pas considéré comme une preuve que rien n’a été fait. Une vérification dueM SK1complete la manquante -déterministement l’écart de impulsion.
- **Recul de TTL limité.** Un morceau conservé n’est pas autorisé à devenir immortuel. Une fois qu’il dépasse la classe - il est lié à une impasse spécifique et ne peut se rafraîchir, il devient froidM SK3 il se réchauffe plutôt que de reposer silencieusement pour toujoursMSC4
- **Aucune fuite de contenu.** Les shingles vivants peuvent contenir des noms, empreintes digitales ou cotes de pointe. Leur contenu demeure en mémoireM SK2 ne sont pas enregistrés , et tout diagnostic les rédige par défaut

Il s’agit d’une projection dont l’âge et la génération sont limités.

---


## Problème et solution

Voici l’ensemble du commerce dans un tableau

| | Per-request fanM SK3out MSC4 Signal Shingle double couche S|
|---|---|---|
| Demande de page | Démarre le prélèvement et l'attente des données N | Lecte un modèle prêt/chunk et rend la coque M|
| Travaux de données | Réponses par visiteur | partagésM SK3 incrémentales, bouclierMSC5remboursé M|
| Mise à jour des widgets | Chaque widget peut être téléchargé indépendamment de lui-même.
| Concurrence  | `Task.WhenAll` élargit avec le trafic | ondes de rafraîchissement limitées avec un budget de coût mesuré |
| Vues filtrées | Non cachés ou non liés SSK2 Combinations chaudes demeurent vivantesM SK3 froides LFU- évités |
| Réponse à la charge | Plus de trafic crée plus de travail
| Frique | Implicite et souvent incohérente | Explicite par widgetM SK3 min- sécuritaire pour les entrées partagées M|
| Modèle d'échec | Demandes lentesM SK2 Épuisement du bassin, Collapse MSC4 Projection plus ancienne mais marquéeMSC5 Travail limité SMC6

Signal Shingle compose des pièces d’ASP connues.NET : un LFU, SignalRM SK3 composition en lotsMSC4 RazorMNK5 HTMX et un calendrier avec un budget de convergence réelleMRK6 La passerelle demeure authentiqueMMK7 le site web dessert une projectionMBK8 le navigateur reçoit des fragments de petites versionsMZK9

Déplacer la reproduction hors du chemin de demande. Laisser fonctionner le travail de réfrigération des gouttelettes de charge plutôt que la réponse.