Perché questa pagina Drupal non va in cache?

Drupal lo scrive nelle intestazioni della risposta, e la risposta sparisce appena arriva. Native Observability conserva per ogni richiesta l'esito della page cache e della dynamic page cache, i tag, i contesti e il max-age, e le invalidazioni di tag che la richiesta ha causato. Registra anche le richieste servite dalla page cache, che Drupal non gestisce.

Come si scopre perché una pagina Drupal non è andata in cache?

Si legge il motivo che Drupal stesso scrive sulla risposta, a patto di averlo conservato. La page cache e la dynamic page cache scrivono il loro esito nelle intestazioni X-Drupal-Cache e X-Drupal-Dynamic-Cache: HIT, MISS oppure UNCACHEABLE con la regola che l'ha deciso fra parentesi. Quelle intestazioni arrivano al browser e lì finiscono. Il giorno dopo, quando qualcuno chiede perché una pagina era lenta, la risposta è già persa.

Native Observability è un modulo Drupal che registra dall'interno che cosa succede a ogni richiesta. Il sottomodulo Cache Observer, native_observability_cache_observer, salva nel database l'esito di ogni strato di cache, i metadati di cacheabilità della risposta e le invalidazioni di tag, legati alla rotta e alla traccia della stessa richiesta. Le query lente della stessa richiesta sono nell'articolo sulle query che rallentano una rotta, le chiamate verso servizi esterni in quello sul servizio esterno che rallenta una rotta.

Le righe riportate qui vengono da un ambiente di misura che ho costruito io, con Drupal 11.4.5 in DDEV. Come rifarlo sta in fondo, nella sezione «Come rifare la misura».

Che cosa registra Cache Observer per ogni richiesta

Tre tipi di evento, ognuno nella tabella native_observability_cache_event con l'identificativo della richiesta che l'ha prodotto.

Tipo di evento Strato Che cosa contiene Da dove lo prende
response_cache page_cache HIT, MISS o UNCACHEABLE (motivo) intestazione X-Drupal-Cache, letta da un middleware
response_cache dynamic_page_cache stessi valori intestazione X-Drupal-Dynamic-Cache, letta in kernel.response
cacheability response_cacheability numero ed elenco di tag e contesti, max-age metadati della risposta, getCacheableMetadata()
tag_invalidation cache_tags i tag invalidati da una chiamata un invalidatore aggiunto a quelli di core

I metadati di cacheabilità vengono letti dalla risposta, dagli stessi dati da cui core ricava i debug header. Il parametro http.response.debug_cacheability_headers resta quindi a false, come chiede il commento di default.services.yml: «Enabling cacheability debugging is not recommended in production environments». Tutte le prove di questa pagina sono state fatte con quel parametro spento.

Una pagina che non va in cache: le tre righe che lo spiegano

Una GET /node/XXX/edit fatta da un utente autenticato lascia tre righe con lo stesso identificativo di richiesta, XXXXXXXXXXXXXXXXXXXXXXMJ7M, e la stessa rotta, entity.node.edit_form:

Strato Esito registrato
page_cache UNCACHEABLE (REQUEST POLICY)
dynamic_page_cache UNCACHEABLE (POOR CACHEABILITY)
response_cacheability 22 tag, 11 contesti, max-age 0

Ogni etichetta corrisponde a una regola precisa del core.

  • REQUEST POLICY sulla page cache: la richiesta ha una sessione. La page cache serve solo gli

anonimi, e la decisione avviene prima ancora di cercare la pagina.

  • POOR CACHEABILITY sulla dynamic page cache: la risposta rientra in una delle

auto_placeholder_conditions di renderer.config. Il metodo shouldCacheResponse() la scarta quando il max-age è 0, quando c'è un contesto ad alta cardinalità come user o session, oppure un tag che si invalida troppo spesso.

  • La riga di cacheabilità dice quale delle tre condizioni è scattata: max-age 0. Nel campo

payload ci sono anche i nomi, fra cui i contesti user e session.exists.

Altri due esiti visti nella stessa misura completano il quadro. user.reset.login, il link di accesso monouso, dà UNCACHEABLE (RESPONSE POLICY): la pagina è stata prodotta, e una regola sulla risposta ne ha impedito il salvataggio. Una risposta che non porta metadati di cacheabilità dà UNCACHEABLE (NO CACHEABILITY) su entrambi gli strati.

Il HIT della page cache, che Drupal non vede

Una pagina servita dalla page cache viene registrata anche se Drupal non l'ha mai gestita. La page cache è il middleware http_middleware.page_cache, a priorità 200, e su un HIT restituisce la risposta salvata prima che il kernel HTTP parta: niente routing, niente kernel.response, niente kernel.terminate. Per il resto di Drupal quella richiesta non è esistita.

Cache Observer si mette sopra, con un middleware a priorità 210, CachePageObserverMiddleware, legge X-Drupal-Cache sulla risposta che torna indietro e scrive la riga. Visto che il kernel non ha assegnato un identificativo, lo conia il middleware e lo mette sulla risposta in X-Native-Observability-Request-Id.

Due richieste anonime alla stessa pagina, una dopo l'altra:

a1.txt:x-drupal-cache: MISS
a1.txt:x-native-observability-request-id: XXXXXXXXXXXXXXXXXXXXXXP9EJ
a2.txt:x-drupal-cache: HIT
a2.txt:x-native-observability-request-id: XXXXXXXXXXXXXXXXXXXXXXP40A

E nel database, con il numero di righe di traccia che ogni identificativo ritrova:

id    request_id                  status  path                                 route_name             trace_rows
XXXX  XXXXXXXXXXXXXXXXXXXXXXP9EJ  MISS    /review/what-changed-in-drupal-11-4  entity.node.canonical  1
XXXX  XXXXXXXXXXXXXXXXXXXXXXP40A  HIT     /review/what-changed-in-drupal-11-4                         0

Il HIT ha zero righe di traccia e la rotta vuota, ed è la risposta esatta: Drupal non ha instradato quella richiesta e non l'ha tracciata. Per un HIT il dato che resta è il percorso. Contare i HIT di una pagina si fa quindi per path, perché route_name c'è solo sulle richieste arrivate al kernel.

L'identificativo di un'altra richiesta, dentro una risposta in cache

La page cache salva la risposta intera, intestazioni comprese. Un sito che aggiunge a ogni risposta un identificativo di correlazione, per ritrovare la richiesta nei log, se lo ritrova salvato insieme alla pagina: al secondo visitatore arriva l'identificativo del primo. Cercandolo nei log si trova la richiesta di un'altra persona, magari di ore prima.

Cache Observer lo toglie. Il middleware riconosce una risposta servita dalla cache dall'assenza dell'attributo che il kernel mette su ogni richiesta gestita, rimuove l'intestazione vecchia e scrive quella nuova. Le due richieste qui sopra lo mostrano: identificativi diversi, uno per richiesta.

Lo stesso effetto tocca un'intestazione di core. Sul HIT la risposta riporta ancora x-drupal-dynamic-cache: MISS, che è l'esito della dynamic page cache per la richiesta che ha riempito la page cache. Su un HIT la dynamic page cache non ha lavorato affatto. Chi legge le intestazioni a mano per capire la cache di una pagina anonima deve saperlo: su un HIT della page cache, X-Drupal-Dynamic-Cache descrive un'altra richiesta.

Quale invalidazione ha svuotato la pagina

Cache Observer affianca agli invalidatori di core un suo invalidatore, ObservedCacheTagsInvalidator, che per ogni chiamata a invalidateTags() scrive una riga con i tag e la richiesta che li ha invalidati. Salvando un nodo dal form, lo stesso POST produce queste righe:

tag_count  tags
1          node:XXX:revisions
4          node_list, node_list:article, 4xx-response, node:XXX
1          native_observability_trace:list

La seconda riga è quella che spiega perché le liste di articoli e la pagina del nodo sono state ricostruite alla visita successiva. La terza è del modulo stesso, e se ne parla fra i limiti.

Le invalidazioni hanno un tetto per richiesta, max_stored_invalidations_per_request, 50 di default. Il tetto conta le chiamate, non i tag, e tiene le prime: l'invalidazione avviene sempre, si ferma solo la registrazione. Lo stesso salvataggio con il tetto a 1 e a 50:

Tetto Righe salvate Tag registrati Che cosa resta
1 1 1 node:XXX:revisions
50 3 6 tutte e tre le chiamate

Con il tetto a 1 sono spariti proprio node:XXX e node_list, cioè i tag utili.

Il totale vero non viene registrato da nessuna parte. Per le query lente il modulo conserva anche quante ne ha viste, e il confronto fra righe salvate e righe viste prova la troncatura. Per le invalidazioni quel confronto manca. L'unico indizio è una richiesta con esattamente tante righe quante ne consente il tetto, e la prova si ottiene soltanto rifacendo la misura con un tetto più alto. È un limite di Cache Observer, e lo scrivo qui invece di lasciarlo scoprire a chi ci si affida.

I limiti di Cache Observer

Ognuno è stato osservato nella stessa misura.

  • Un POST lascia solo la riga di cacheabilità. Il core mette X-Drupal-Cache soltanto sulle

richieste con metodo cacheabile, come prescrive la RFC 7231 §4.2.3, e senza intestazione il middleware non scrive la riga della page cache.

  • CACHEABLE è la cacheabilità dichiarata. L'etichetta della riga di cacheabilità guarda

soltanto tag, contesti e max-age. Un POST sul form di login esce CACHEABLE, benché un POST resti sempre fuori dalla cache. L'esito vero sta nelle righe response_cache.

  • Il HIT ha il percorso e la rotta vuota, per il motivo spiegato sopra.
  • Le invalidazioni da drush e da cron lanciato da riga di comando restano fuori. L'invalidatore

scrive solo quando la richiesta corrente ha un identificativo di correlazione, e sotto drush quella richiesta è sintetica e l'identificativo manca. Un drush cr svuota tutto e lascia la tabella com'era.

  • Il modulo registra le proprie invalidazioni. Ogni richiesta che scrive una traccia invalida

native_observability_trace:list, e quella chiamata finisce in tabella come le altre: nella misura erano 8 righe di invalidazione su 12. Con un tetto basso occupano posti che spetterebbero ai tag del sito.

  • Il tetto sulle invalidazioni taglia in silenzio, come descritto nella sezione precedente.

Confronto con gli altri strumenti

Ognuno risponde a una domanda diversa. La tabella riporta quello che ogni progetto dichiara sulla sua pagina: gli altri strumenti non sono stati provati nell'ambiente di misura.

Strumento A quale domanda risponde Per la produzione
debug_cacheability_headers di core quali tag, contesti e max-age ha la risposta che sto guardando sconsigliato dal core
Log Cache Tags quali tag sono stati invalidati, scritti nel dblog nessuna avvertenza, con un interruttore per il volume
Trace Cache Tags quali tag sono stati invalidati, un avviso a ogni invalidazione «Not recommended for production sites»
Cache review come funzionano page cache e dynamic page cache, con pagine dimostrative strumento didattico, lo dichiara
WebProfiler che cosa ha fatto la cache nella pagina che ho davanti nessuna indicazione
Native Observability che cosa ha fatto ogni strato di cache per una richiesta passata, e chi ha invalidato che cosa nato per restare acceso

I debug header e WebProfiler servono quando la pagina è davanti a te. I moduli di log sulle invalidazioni servono quando la domanda è solo «chi ha svuotato la cache». Nessuno dei primi cinque registra i HIT della page cache, che sono proprio le richieste di cui Drupal non sa niente.

Come si comincia

  1. Installa il sottomodulo: drush en native_observability_cache_observer -y. Richiede

native_observability e funziona su Drupal 10 e 11.

  1. Dai il permesso access native observability cache observer a chi deve leggere i dati, e

administer native observability cache observer a chi deve cambiare le impostazioni o cancellare le righe.

  1. Controlla le impostazioni su /admin/config/development/native-observability/settings/cache-observer.

Tutte le catture sono accese per default, il tetto sulle invalidazioni è 50 e le righe restano 72 ore.

  1. Verifica con due richieste anonime alla stessa pagina: la seconda deve rispondere

X-Drupal-Cache: HIT con un X-Native-Observability-Request-Id diverso dalla prima.

Il rapporto si legge su /admin/reports/native-observability/cache-observer, filtrabile per strato e per stato, con il dettaglio di ogni evento e il suo payload.

Le impostazioni stanno nell'oggetto di configurazione native_observability_cache_observer.settings:

drush cget native_observability_cache_observer.settings
drush cset native_observability_cache_observer.settings max_stored_invalidations_per_request 50 -y

Come rifare la misura

Le due richieste della page cache, da anonimo e senza cookie:

URL=https://example.ddev.site/some-published-page
curl -sk -D a1.txt -o /dev/null "$URL"
curl -sk -D a2.txt -o /dev/null "$URL"
grep -iE 'x-drupal-cache:|x-drupal-dynamic-cache:|x-native-observability-request-id' a1.txt a2.txt

La query che lega ogni evento della page cache alla sua traccia, se esiste:

SELECT c.id, c.request_id, c.status, c.path, c.route_name,
       (SELECT COUNT(*) FROM native_observability_trace t
         WHERE t.request_id = c.request_id) AS trace_rows
FROM native_observability_cache_event c
WHERE c.cache_layer = 'page_cache'
ORDER BY c.id;

Le invalidazioni salvate per ogni richiesta, da confrontare con il tetto configurato:

SELECT request_id, method, route_name,
       COUNT(*) AS stored_rows, SUM(tag_count) AS stored_tags
FROM native_observability_cache_event
WHERE event_type = 'tag_invalidation'
GROUP BY request_id, method, route_name
ORDER BY MIN(id);

Per il salvataggio del nodo serve una richiesta HTTP vera, fatta dal form: un salvataggio lanciato con drush resterebbe fuori, per il motivo scritto fra i limiti.

Che cosa questa prova non dimostra

  • È una misura fatta a mano, una richiesta alla volta, in un ambiente DDEV. Mostra il meccanismo, non

il comportamento con il traffico di un sito vero.

  • L'ambiente di misura gira in modalità sviluppo. Per la prova ho spento a mano

debug_cacheability_headers e il debug di Twig, e li ho riaccesi alla fine.

  • Le tabelle del modulo avevano già righe di prove precedenti e non sono state svuotate. Tutti i

conteggi sono filtrati sulle righe scritte dopo l'inizio della prova.

Fonti e riferimenti

Tutte le fonti sono state consultate il 27 settembre 2026.

  1. Pagina di progetto di Native Observability (si apre in una nuova scheda). Ramo 2.0.x, sottomoduli e requisiti. Fonte primaria.
  2. Sorgente del modulo, ramo 2.0.x (si apre in una nuova scheda). CachePageObserverMiddleware per i HIT e l'identificativo, CacheResponseObserverSubscriber per la cacheabilità e la dynamic page cache, ObservedCacheTagsInvalidator per le invalidazioni e il tetto, config/install per i valori di default. Fonte primaria.
  3. Sorgente del core di Drupal 11: PageCache per le etichette UNCACHEABLE della page cache e la regola sui metodi non cacheabili, DynamicPageCacheSubscriber::shouldCacheResponse() per POOR CACHEABILITY, default.services.yml per l'avvertenza sui debug header. Fonte primaria.
  4. Improve X-Drupal-Cache and X-Drupal-Dynamic-Cache headers, even for responses that are not cacheable (si apre in una nuova scheda), issue di core #2951814, aperta. Letta dal DOM reso.
  5. Cache tags, guida di Drupal (si apre in una nuova scheda). Letta dal DOM reso.
  6. Pagina di progetto di Log Cache Tags (si apre in una nuova scheda). Letta dal DOM reso.
  7. Pagina di progetto di Trace Cache Tags (si apre in una nuova scheda). Letta dal DOM reso.
  8. Pagina di progetto di Cache review (si apre in una nuova scheda). Letta dal DOM reso.
  9. Pagina di progetto di WebProfiler (si apre in una nuova scheda). Letta dal DOM reso.
  10. I numeri di questa pagina vengono da un ambiente di misura che ho costruito io, con Drupal 11.4.5 in DDEV e Native Observability 2.0.x. Chi vuole controllarli li rifà con i comandi e le query riportati sopra.
Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

Quale servizio esterno sta rallentando questa rotta Drupal?

Drupal non registra le chiamate che fa verso altri servizi. Native Observability misura ogni chiamata in uscita e la lega alla pagina che l'ha fatta: per ogni rotta mostra quali servizi chiama, quante volte, in quanto tempo e con quanti errori. Funziona anche al contrario, dal servizio lento alle pagine che lo interrogano.

Come si scopre quale servizio esterno rallenta una pagina Drupal?

Si scopre misurando ogni chiamata in uscita mentre la pagina è in corso, e scrivendo accanto alla misura il nome della pagina. Il servizio esterno non lo sa, perché vede arrivare una richiesta da un indirizzo IP, e Drupal di suo non conserva niente di quello che ha chiamato.

Native Observability è un modulo Drupal che registra dall'interno che cosa succede a ogni richiesta. Il sottomodulo Spans cronometra le chiamate HTTP in uscita, e il Dashboard le raggruppa per pagina: per ogni rotta dice quali servizi ha chiamato, quante volte, con quale durata media e quanti errori. Lo stesso problema, per le query al database, è nell'articolo sulle query che rallentano una rotta.

Le misure di questa pagina vengono da un ambiente di misura che ho costruito io, con un servizio lento finto di cui decido il ritardo. Come riprodurlo sta in fondo, nella sezione «Come rifare la misura».

Perché vede le chiamate di tutti i moduli senza modificarli

Perché si aggancia al punto da cui passano tutte. Drupal crea il suo client HTTP, il servizio http_client, da una fabbrica che gli consegna una pila di gestori condivisa, http_handler_stack. Ogni modulo che chiama un servizio esterno attraverso http_client, o attraverso un client creato con http_client_factory, passa da quella pila.

Il modulo aggiunge alla pila un suo middleware Guzzle, GuzzleSpanMiddleware, dichiarato con il tag http_client_middleware. Il middleware fa partire il cronometro prima della chiamata e lo ferma quando arriva la risposta, oppure l'errore. Il codice dei moduli che chiamano resta com'è.

Resta fuori solo chi si costruisce il client da sé:

<?php

// Measured: goes through Drupal's shared handler stack.
$response = \Drupal::httpClient()->request('GET', $url);

// Not measured: a hand-built client has its own handler stack.
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', $url);

La scelta di aggiungere un middleware invece di sostituire il servizio http_client è voluta, e il motivo è scritto nella definizione del servizio: alcuni moduli pretendono proprio la classe GuzzleHttp\Client, e un servizio sostituito li romperebbe.

Dare un nome a una chiamata del proprio modulo

Un modulo che integra un servizio esterno può dare un nome alle sue chiamate con l'opzione Guzzle native_observability, che accetta subtype e name:

<?php

$response = \Drupal::httpClient()->request('POST', $url, [
  'json' => $payload,
  'native_observability' => [
    'subtype' => 'payments',
    'name' => 'Payment gateway: authorize',
  ],
]);

Senza opzione la chiamata si chiama GET <host> e sta nella categoria http.client. Con l'opzione porta il nome scelto e la categoria http.client.payments, e resta fra le dipendenze esterne della rotta. Nel ramo 2.0.x la ripartizione dei tempi della panoramica conta solo la categoria http.client esatta, quindi una chiamata con sottotipo lì non compare.

Quanto tempo passa fuori una rotta, e verso chi

La risposta sta nella tabella External Dependency Signals della Forensic Route Analysis, sotto /admin/reports/native-observability/dashboard/forensic-route-analysis. Una riga per ogni destinazione, con metodo, indirizzo, numero di chiamate, durata media ed errori.

Nell'ambiente di misura la pagina /nos-bench/http fa tre chiamate: una verso un servizio che risponde dopo 800 millisecondi, una verso lo stesso servizio che risponde dopo 20, e una verso un dominio che non esiste. Dopo quattro visite la tabella dice questo:

Metodo Destinazione Chiamate Durata media Errori
GET http://this-host-does-not-resolve.invalid/ 4 13,5 ms 4
GET http://127.0.0.1:8099/?ms=20 4 25,8 ms 0
GET http://127.0.0.1:8099/?ms=800&api_key=BENCH-KEY-12345 4 813 ms 0
POST http://grafana-alloy:4318/v1/traces 4 2,5 ms 0

La pagina risponde in circa un secondo, e la riga da 813 millisecondi spiega da sola quasi tutto quel tempo. È la risposta alla domanda del titolo: su questa rotta il tempo se ne va lì, verso quel servizio.

L'ultima riga non è una chiamata della pagina. È il modulo stesso che spedisce le tracce a un collettore OpenTelemetry, attivo in questo ambiente di misura, e anche quella spedizione passa dal client HTTP di Drupal. Chi usa l'esportazione se la ritroverà fra le dipendenze di ogni rotta.

Per leggere il dashboard serve il permesso access native observability dashboard.

Dal servizio lento alle pagine che lo chiamano

La Forensic Route Analysis accetta anche l'indirizzo di un servizio esterno al posto di una pagina. Nel campo in cui si aggiunge il soggetto da analizzare si incolla l'indirizzo del servizio, e il modulo risale a tutte le rotte che lo hanno chiamato nell'intervallo di tempo scelto.

Incollando 127.0.0.1:8099 nel campo, la risposta è questa:

The search term "127.0.0.1:8099" matched 1 candidates.
/nos-bench/http · nos_bench_http.page (4 requests)

Serve quando si conosce il servizio e non si sa chi lo usa: si parte dall'indirizzo e si arriva alle pagine.

La ricerca scorre i dati salvati di ogni chiamata, che non hanno un indice, e si ferma alle prime mille richieste trovate (SPAN_PAYLOAD_SEARCH_REQUEST_ID_CAP). Durante la misura le richieste erano quattro, quindi il tetto non è entrato in gioco. Su un sito con molto traffico e una finestra di giorni, il risultato può essere tagliato.

Una chiamata fallita per DNS non ha codice di stato

Una chiamata che fallisce prima di arrivare al servizio non riceve risposta, quindi non ha nessun codice di stato. Il modulo la conta come errore lo stesso, perché la riconosce dal campo outcome che scrive accanto a ogni chiamata, e non dal codice.

Le quattro chiamate verso il dominio inesistente sono salvate così:

status_code   null
outcome       error
error         cURL error 6: Could not resolve host: this-host-does-not-resolve.invalid

Un conteggio degli errori basato sui codici 4xx e 5xx queste chiamate le perde senza avvisare. Un fallimento DNS, una connessione rifiutata e un timeout sono proprio i casi in cui un servizio esterno fa più danni, perché la pagina aspetta fino allo scadere del tempo massimo.

Le chiamate fatte da cron e da drush non appartengono a nessuna pagina

Una chiamata partita da cron o da drush viene misurata, ma non ha una pagina a cui appartenere, perché dietro non c'è nessuna richiesta HTTP. Il modulo la salva senza identificativo di richiesta.

Una chiamata lanciata con drush php:eval compare così: una sola riga da 67 millisecondi, con l'identificativo della richiesta vuoto. Non si trova sotto nessuna traccia e non entra nella tabella di nessuna rotta. Entra soltanto nella ripartizione dei tempi della panoramica generale.

Vale la pena saperlo prima di confrontare i numeri: un'importazione notturna che chiama un servizio esterno migliaia di volte non comparirà in nessuna pagina.

Che cosa il modulo non fa

Misura soltanto quello che succede dentro Drupal: quanto ha aspettato, chi ha chiamato, com'è finita. Tutto il resto lo lascia ad altri strumenti.

  • Non collega la traccia di Drupal a quella del servizio chiamato. Sulle chiamate in uscita non aggiunge nessuna intestazione, né traceparent né una sua. Per seguire una richiesta attraverso più sistemi serve il distributed tracing, cioè OpenTelemetry (si apre in una nuova scheda) con un backend che raccoglie le tracce di tutti i servizi.
  • Raggruppa per indirizzo intero, non per host. Nella tabella qui sopra lo stesso servizio occupa due righe, perché la query string è diversa. Un servizio chiamato con un parametro che cambia a ogni richiesta si spezza in tante righe quante sono le chiamate.
  • Salva l'indirizzo con la query string, chiavi comprese. La chiave finta api_key=BENCH-KEY-12345 usata nella misura si legge nel database e nella tabella del dashboard. Il modulo toglie le credenziali dalle intestazioni e dal corpo, non dall'indirizzo. Se un servizio vuole la chiave nell'URL, quella chiave finisce nei dati di osservabilità.
  • Una pagina servita dalla cache non chiama nessuno. Nell'ambiente di misura la prima visita anonima ha impiegato circa 3,3 secondi, le due successive circa 0,5 e 0,1, e solo la prima ha lasciato una traccia. La Internal Page Cache ha servito le altre senza eseguire il codice della pagina. È il comportamento giusto, ma chi prova a misurare ricaricando la stessa pagina da anonimo vede una chiamata sola dove si aspettava di vederne tre.

Confronto con gli altri strumenti

Ognuno risponde a una domanda diversa, e la scelta dipende dalla domanda. La tabella riporta quello che ogni progetto dichiara sulla sua pagina: gli altri strumenti non sono stati provati nell'ambiente di misura.

Strumento A quale domanda risponde
HTTP Client Logs che cosa ha chiesto e che cosa ha risposto una chiamata precisa
HTTP Client logger quali chiamate sono partite, scritte nei log del sito
WebProfiler che cosa ha fatto la pagina che sto guardando adesso
OpenTelemetry che strada ha fatto una richiesta attraverso più sistemi
Native Observability quanto aspetta ogni rotta i servizi esterni, e quali rotte chiamano un servizio

Un log serve quando vuoi leggere il corpo di una risposta sbagliata. Una toolbar serve quando hai la pagina davanti. Un backend OpenTelemetry serve quando hai più servizi e qualcuno che li amministra. Nessuno dei primi tre raggruppa le chiamate per rotta, e nessuno parte dal servizio per arrivare alle pagine.

Come si comincia

Tre passi.

  1. Installa il dashboard, che si porta dietro Spans e gli altri sottomoduli di cui ha bisogno: drush en native_observability_dashboard -y.
  2. Dai il permesso access native observability dashboard a chi deve leggere i dati.
  3. Verifica che le chiamate vengano misurate con il comando di prova del modulo, che fa cinque chiamate verso destinazioni note, compreso un dominio che non si risolve:
drush native-observability-span:http-test

Dopo il comando, le cinque chiamate si leggono su /admin/reports/native-observability/execution. Il comando chiama anche httpbin.org, quindi serve la rete. Da lì in avanti ogni chiamata del sito finisce in tabella, e la Forensic Route Analysis mostra le dipendenze di ogni rotta.

Come rifare la misura

Il servizio lento è un file PHP che dorme per il numero di millisecondi chiesto. Non serve internet, e il ritardo lo decide chi fa la prova:

<?php

// Bench-only stub of a slow third party service.
// ?ms=800 sleeps for 800 milliseconds, ?status=500 answers with an error.
$ms = isset($_GET['ms']) ? max(0, (int) $_GET['ms']) : 0;
$status = isset($_GET['status']) ? (int) $_GET['status'] : 200;

usleep($ms * 1000);

http_response_code($status);
header('Content-Type: application/json');
echo json_encode(['slept_ms' => $ms, 'status' => $status]);
php -S 127.0.0.1:8099 /tmp/slow-service.php

La pagina misurata è un controller che fa le tre chiamate con \Drupal::httpClient(). Dopo qualche visita, questa query conferma sul database quello che mostra il dashboard:

SELECT t.route_name,
       COUNT(*) AS calls,
       ROUND(AVG(s.duration_ms), 1) AS avg_ms,
       MAX(s.duration_ms) AS max_ms
FROM native_observability_spans s
JOIN native_observability_trace t ON t.request_id = s.request_id
WHERE s.category LIKE 'http.client%'
GROUP BY t.route_name;

E questa rifà a mano la ricerca inversa, dall'indirizzo del servizio alla rotta:

SELECT DISTINCT t.route_name, t.path
FROM native_observability_spans s
JOIN native_observability_trace t ON t.request_id = s.request_id
WHERE s.category LIKE 'http.client%'
  AND CAST(s.payload AS CHAR) LIKE '%127.0.0.1:8099%';

Con la cattura spenta, capture.enabled a zero in native_observability.settings, una nuova visita alla pagina non aggiunge nessuna riga. È il controllo che conferma che le righe le scrive il modulo.

Che cosa questa prova non dimostra

Il servizio lento è finto, ed è giusto saperlo.

  • Il ritardo è un usleep deciso da me. La prova mostra il meccanismo, non il comportamento di un servizio vero sotto carico, con le sue code e i suoi timeout variabili.
  • Una richiesta alla volta, in un ambiente di misura DDEV con Xdebug acceso. I tempi assoluti non vanno letti come misura di prestazioni.
  • Le tabelle del modulo avevano già righe di prove precedenti e non sono state svuotate. Tutti i conteggi sono filtrati sulle righe scritte dopo l'inizio della prova, alle 19:07:50 UTC del 27 settembre 2026.
  • Quattro visite, non un giorno di traffico. La media su quattro chiamate descrive l'ambiente di misura, non un sito in produzione.

Fonti e riferimenti

Tutte le fonti sono state consultate il 27 settembre 2026.

  1. Pagina di progetto di Native Observability (si apre in una nuova scheda). Ramo 2.0.x, sottomoduli e requisiti. Fonte primaria.
  2. Sorgente del modulo, ramo 2.0.x (si apre in una nuova scheda). GuzzleSpanMiddleware per la misura delle chiamate e il campo outcome, ForensicRouteAnalysisBuilder per la tabella delle dipendenze e la ricerca inversa con il suo tetto di mille richieste, docs/usage/spans.md per le chiamate da cron e drush e per il comando di prova. Fonte primaria.
  3. Sorgente del core di Drupal 11, core.services.yml e Drupal\Core\Http\ClientFactory: http_client e http_client_factory condividono http_handler_stack. Fonte primaria.
  4. Pagina di progetto di HTTP Client Logs (si apre in una nuova scheda). Letta dal DOM reso.
  5. Pagina di progetto di HTTP Client logger (si apre in una nuova scheda). Letta dal DOM reso.
  6. Pagina di progetto di WebProfiler (si apre in una nuova scheda). Letta dal DOM reso.
  7. Pagina di progetto di OpenTelemetry (si apre in una nuova scheda). Letta dal DOM reso.
  8. Trace Context, W3C Recommendation del 23 novembre 2021 (si apre in una nuova scheda). Definisce l'intestazione traceparent. Fonte primaria.
  9. I numeri di questa pagina vengono da un ambiente di misura che ho costruito io, con Drupal 11 in DDEV e Native Observability 2.0.x. Le misure sono state rilette sul database con le query riportate sopra. Chi vuole controllarla la rifà con il codice e le query riportati in questa pagina.
Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

Come applico l'articolo 50(4) dell'AI Act ai contenuti di un sito Drupal?

Chi pubblica con Drupal applica l'articolo 50(4) dell'AI Act rispondendo, per ogni tipo di contenuto, a quattro domande: che cosa è stato prodotto, se è un'opera artistica, se informa il pubblico, se tratta questioni di interesse pubblico. Il modulo AI Disclosure registra quelle risposte insieme al grado di coinvolgimento dell'AI, calcola se l'etichetta è dovuta e la mostra sulla pagina.

Questo articolo non è un parere legale. Riporta che cosa dicono il Regolamento (UE) 2024/1689 e la FAQ della Commissione europea, e che cosa fa il modulo con quelle regole. Se un contenuto va etichettato lo decide chi lo pubblica.

Che cosa sia AI Disclosure e come sia costruito sta nella scheda del progetto. Qui si guarda solo come il modulo traduce l'articolo 50(4) in dati e in un'etichetta sulla pagina.

Come decide AI Disclosure se un contenuto va etichettato?

AI Disclosure tiene separate due informazioni: quanto l'AI è entrata nel contenuto e se la norma si applica a quel contenuto.

La prima è il grado, uno su nove. Sei gradi dicono che l'AI ha generato o manipolato il contenuto pubblicato: traduzione, riassunto, riscrittura parziale, testo prodotto con una persona che guida, testo prodotto senza nessuno, deep fake. Solo per questi sei la domanda sulla norma ha senso. Gli altri tre, cioè nessuna AI, metadati scelti dall'AI e contenuto valutato dall'AI, restano fuori.

La seconda sono le quattro risposte di ambito:

Domanda A che cosa serve
È un deep fake in immagine, audio o video, oppure è un testo? sceglie quale delle due regole dell'articolo 50(4) vale
Fa parte di un'opera palesemente artistica, satirica o di finzione? per i deep fake, cambia il modo dell'etichetta
È pubblicato per informare il pubblico? per i testi, primo requisito
Riguarda questioni di interesse pubblico? per i testi, secondo requisito

Ogni risposta vale sì, no o non valutato. Il modulo non ne deduce nessuna: né dal tipo di contenuto, né dal grado.

Che cosa chiede l'articolo 50(4) dell'AI Act?

L'articolo 50(4) contiene due obblighi per chi pubblica, con presupposti diversi.

Il primo riguarda immagini, audio e video che costituiscono un deep fake: chi li diffonde deve dichiarare che sono stati generati o manipolati artificialmente. La revisione umana non esime dall'obbligo. Quando il deep fake fa parte di un'opera palesemente artistica, creativa, satirica o di finzione, l'obbligo si riduce a segnalare che il contenuto è generato o manipolato, in un modo che non ostacoli la fruizione dell'opera.

Il secondo riguarda i testi generati o manipolati con l'AI e pubblicati per informare il pubblico su questioni di interesse pubblico. Qui c'è un'esenzione, che richiede due condizioni insieme: il testo è passato da una revisione umana o da un controllo editoriale, e una persona fisica o giuridica ne ha la responsabilità editoriale. Secondo la FAQ della Commissione, responsabilità editoriale significa la responsabilità legale ultima della pubblicazione.

Da quando si applica l'articolo 50 dell'AI Act?

L'articolo 50 si applica dal 2 agosto 2026, la data generale fissata dall'articolo 113 del Regolamento. I contenuti generati prima di quella data non vanno etichettati retroattivamente, anche se la Commissione invita a farlo dove possibile.

Il Regolamento (UE) 2026/1744, il Digital Omnibus sull'AI pubblicato in Gazzetta il 24 luglio 2026, non ha spostato questa data per chi pubblica. Ha aggiunto all'articolo 111 un paragrafo che vale solo per i fornitori: i sistemi generativi immessi sul mercato prima del 2 agosto 2026 devono rispettare l'articolo 50(2), cioè la marcatura del file, entro il 2 dicembre 2026. È questa la data che circola spesso come rinvio generale. L'articolo 50(4) non compare fra le modifiche, e chi pubblica contenuti resta sulla data di agosto.

Che cosa significa «non valutata» nel verdetto?

Il verdetto di AI Disclosure ha tre valori: etichetta richiesta, non richiesta, non valutata. Un no su una qualsiasi condizione chiude la questione anche se le altre risposte mancano, e lo stesso vale per l'esenzione dei testi. «Non valutata» compare solo quando manca una risposta che cambierebbe l'esito.

Sulla pagina un contenuto non valutato mostra comunque la scheda, con una dicitura dedicata e un bordo tratteggiato. Nasconderla direbbe al lettore che nessuna etichetta è dovuta, cioè proprio la cosa che il sito non ha ancora stabilito.

Come si risponde una volta sola per un intero tipo di contenuto?

Le risposte si danno sul profilo, e ogni contenuto eredita il profilo di default del suo tipo. Il singolo contenuto interviene solo per le eccezioni. Grado e quattro risposte arrivano sempre dallo stesso livello: il modulo non mescola risposte prese da livelli diversi.

Un profilo che dichiara la revisione umana senza un responsabile non si salva, né dal form né da un import di configurazione.

Su questo sito il profilo editorial_default, impostato il 20 settembre 2026, dichiara testo, pubblicato per informare, con revisione umana e responsabile nominato.

Alla domanda sull'interesse pubblico il profilo risponde no, ed è una scelta mia, non un dato. La FAQ della Commissione fa rientrare nell'interesse pubblico anche gli sviluppi economici, scientifici o culturali che possono diventare oggetto di dibattito pubblico, e un articolo tecnico sull'AI Act potrebbe starci dentro. Il verdetto non cambierebbe: con un sì, revisione umana e responsabile nominato fanno comunque scattare l'esenzione.

Con queste risposte il verdetto è «non richiesta», e la pagina lo scrive nel meta tag:

<meta name="ai-disclosure" content="ai_assisted_hitl; required=not-required; icon=ai_modified">

La scheda si vede lo stesso, perché la policy del sito è impostata su «mostra sempre».

Che cosa succede quando l'etichetta è dovuta?

Quando l'etichetta è dovuta compaiono l'icona ufficiale UE e la frase del grado, e il sito non può nasconderle: la visibilità della scheda si configura, ma un'etichetta richiesta si mostra comunque. Il resto della scheda, cioè dicitura legale, descrizione e link alla policy editoriale, segue le impostazioni del sito.

Che cosa succede se cambio la configurazione dei gradi?

I contenuti in eredità leggono grado e profilo dalla configurazione al momento della visualizzazione. Cambiare la frase di un grado cambia quindi ciò che mostrano tutti i contenuti che lo usano, anche quelli pubblicati mesi prima. Il sottomodulo Audit esiste per questo: a ogni salvataggio copia nel registro il significato del grado in quel momento, e le modifiche successive alla configurazione non riscrivono la storia.

Che cosa AI Disclosure non fa?

AI Disclosure non decide se un contenuto va etichettato e non controlla le risposte. Accetta il nome inserito come responsabile editoriale senza giudicarlo: un ruolo generico come «la redazione» non regge un audit. Non marca il file: il JSON-LD è un segnale a livello di pagina, e la marcatura dell'articolo 50(2) spetta al fornitore del sistema di AI, non a chi pubblica.

Fonti e riferimenti

Tutte le fonti sono state consultate il 27 settembre 2026.

  1. Regolamento (UE) 2024/1689, testo in Gazzetta ufficiale (si apre in una nuova scheda), EUR-Lex. Articolo 50, paragrafo 4, e articolo 113 sulla data di applicazione. Letto dal DOM reso, fonte primaria.
  2. Regolamento (UE) 2026/1744, Digital Omnibus sull'AI (si apre in una nuova scheda), EUR-Lex, Gazzetta ufficiale del 24 luglio 2026. Il punto 39 aggiunge all'articolo 111 il paragrafo 4 sul 2 dicembre 2026 per l'articolo 50(2). Dell'articolo 50 è modificato solo il paragrafo 7. Letto dal DOM reso, fonte primaria.
  3. FAQ sugli obblighi di trasparenza dell'articolo 50 (si apre in una nuova scheda), Commissione europea, aggiornata il 24 luglio 2026. I tre criteri dei testi, gli ambiti di interesse pubblico, che cosa conta come revisione umana, la responsabilità editoriale, il 2 dicembre 2026 limitato al 50(2), l'etichettatura retroattiva. Letta dal DOM reso, fonte primaria.
  4. AI Disclosure, pagina di progetto (si apre in una nuova scheda), drupal.org. Release 1.0.0-alpha2 del 12 settembre 2026, per Drupal 10.4 e 11. Letta dal DOM reso.
  5. Documentazione di AI Disclosure sull'articolo 50 (si apre in una nuova scheda), pagina «Article 50, and what this module does not do», aggiornata l'11 settembre 2026. Fonte primaria sul comportamento del modulo.
  6. AI Disclosure Module Adds Article 50 Labelling Tools for Drupal (si apre in una nuova scheda), The Drop Times, 16 settembre 2026. La notizia sull'uscita delle due alpha e sulle funzioni del modulo. Letta dal DOM reso.
  7. Il meta tag citato nella sezione sul profilo è quello che questa stessa pagina scrive nel suo <head>: si verifica aprendo il sorgente.
Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

Quali query rallentano una rotta Drupal, e come le isoli in produzione

Il database registra le query lente ma non sa da quale pagina arrivino, e un profiler di sviluppo non era acceso quando quella pagina era lenta. Native Observability è un modulo Drupal che scrive le due informazioni insieme mentre la richiesta è ancora in corso: per ogni query oltre una soglia salva una riga con la query e la pagina che l'ha eseguita. Le righe si leggono dall'area di amministrazione, e un controllo dice se quello che vedi è completo o se ne è stata scartata qualcuna.

Quale query rallenta una pagina Drupal, e come si scopre?

Si scopre solo se qualcosa ha scritto il nome della pagina accanto alla query mentre la richiesta era in corso. Dopo non si ricostruisce più: il database conserva le query lente, ma per lui sono istruzioni arrivate da una connessione, non pagine di un sito.

Native Observability è un modulo Drupal che registra dall'interno che cosa succede a ogni richiesta. Il suo sottomodulo Database Observer fa una cosa sola: quando una query supera una soglia di durata, salva una riga con due informazioni accostate, la query e la pagina che l'ha eseguita.

Questa pagina racconta tre cose: che cosa ci si trova dentro quella riga, quanto ci si può fidare di quello che si legge, e da dove si comincia. Quanto rallenta il sito tenere acceso il modulo è un'altra domanda, e la risposta è misurata nell'articolo dedicato al suo costo. Lo stesso problema, per le chiamate HTTP verso servizi esterni, è nell'articolo sul servizio esterno che rallenta una rotta.

Perché gli strumenti che hai già non rispondono

Rispondono a domande vicine, e nessuna è questa.

Il log lento di MySQL conosce le query e ignora le pagine. Non è un limite di configurazione, è la sua definizione: il manuale lo descrive come l'elenco delle istruzioni SQL che superano una certa durata. Istruzioni, non pagine. I campi che salva sono quelli del database, cioè durata, righe lette e righe restituite, e nessuno di questi dice da dove sia partita la richiesta: il database riceve comandi da una connessione e non sa che sopra di lui esista un sito. Alla fine hai la query colpevole e vai a cercarla a mano dentro il codice.

Gli strumenti di sviluppo guardano la pagina che hai aperto adesso, non quella lenta di ieri che non riesci a riprodurre.

Strumento A quale domanda risponde
Devel quali query sta eseguendo la pagina che sto guardando ora
WebProfiler quanto tempo e quante query ha speso la pagina che sto guardando ora
XHProf in quali funzioni se ne va il tempo, e chi chiama chi
DB Performance quali query sono lente sul sito, raggruppate per forma della query
Native Observability quali query hanno rallentato una certa pagina, anche ieri, anche mentre non la guardavo

Due precisazioni, perché girano capovolte. La prima: Devel dichiara di essere sicuro in produzione, alla lettera, sulla sua pagina di progetto, a patto di dare il permesso giusto solo a chi sviluppa. Il confine quindi non è fra sviluppo e produzione: è fra la richiesta che stai facendo tu e la storia delle richieste degli altri. La seconda: XHProf scende in un dettaglio che qui non troverai, perché ricostruisce l'albero delle chiamate, mentre Native Observability cronometra i servizi e si ferma lì.

DB Performance è quello che arriva più vicino, e la differenza è netta: raggruppa le query lente per forma e suggerisce indici, ma sulla sua pagina di progetto non compare mai la parola rotta né la parola URL. Nessuno di questi strumenti scrive da quale pagina la query sia arrivata.

Che cosa si vede, quando il collegamento c'è

Una riga per ogni query lenta, e niente per le altre.

In un ambiente di misura con Drupal 11, con le impostazioni predefinite del modulo, una pagina costruita per essere lenta risponde in circa 0,6 secondi. In tabella resta questo:

route_name        testing_native_observability.probe_slow_db
duration_ms       603.968
query_text        SELECT SLEEP(:s)
query_hash        0f907bc4...

Quattro informazioni, e servono tutte:

  • la rotta è il nome che Drupal dà a una pagina dal punto di vista del codice. Dice quale parte del sito ha generato la query, anche quando l'indirizzo cambia a ogni visita;
  • la durata è in millisecondi, quindi circa 0,6 secondi;
  • il testo della query è, accanto, la classe e il metodo che l'hanno eseguita;
  • l'impronta è un codice calcolato sul testo SQL ripulito dai valori. Serve a ritrovare la stessa query su altre pagine e in altri giorni, anche quando i valori cambiano.

Nella stessa richiesta girava anche una seconda query pesante, e non compare. È durata meno della soglia predefinita di cento millisecondi, quindi non è stata salvata. La soglia si comporta da cancello, non da registratore.

Le stesse righe si leggono a video su /admin/reports/native-observability/database-observer, con il permesso access native observability database observer.

Quanto ti puoi fidare di quella tabella

Dipende da due impostazioni, e la seconda merita attenzione.

La prima è la soglia, cento millisecondi di default: decide quanto deve durare una query per finire in tabella. Abbassarla non porta più verità, porta più righe, e sotto qualche millisecondo stai registrando il funzionamento normale del sito.

La seconda è il massimo di righe per richiesta, venti di default. Se una pagina produce più di venti query lente, le altre vengono scartate, e per ora nessuna schermata te lo dice. Venti righe in tabella sono indistinguibili da venti query lente davvero avvenute.

C'è però un modo di accorgersene, perché il modulo salva anche quante query lente ha visto, prima di scartarne qualcuna. Questa query mette in fila le richieste in cui è successo, confrontando le righe salvate (stored_rows) con quelle viste (slow_queries_seen):

SELECT request_id, route_name, COUNT(*) AS stored_rows,
       MAX(JSON_EXTRACT(payload, '$.request_summary.slow_query_count')) AS slow_queries_seen
FROM native_observability_database_query
GROUP BY request_id, route_name
HAVING slow_queries_seen > stored_rows;

Nell'ambiente di misura il taglio si vede a occhio nudo: una pagina che esegue venticinque query lente lascia in tabella esattamente venti righe, e le cinque in più non esistono da nessuna parte.

Un'ultima cosa, per non cercarle altrove: il Database Observer non scrive niente nel log di Drupal. Quello che ha visto sta nella sua tabella e nel suo report, e in nessun altro posto.

Se la pagina lenta la chiama un altro sistema

Il collegamento si mantiene con due intestazioni HTTP. Chi chiama mette il proprio identificativo in X-Native-Observability-Parent-Request-Id, Drupal risponde con X-Native-Observability-Request-Id, e il modulo conserva il legame fra le due richieste. Da lì si arriva alle query lente di quella pagina.

Una avvertenza per non restare delusi: verso un dominio diverso dal tuo, l'identificativo lo deve mettere chi chiama. Il modulo lo aggiunge da solo solo alle chiamate che restano sullo stesso sito.

Da dove si comincia

Quattro passi, e dopo il primo la tabella comincia a riempirsi.

  1. Installa il sottomodulo: drush en native_observability_database_observer -y. Si porta dietro il modulo principale.
  2. Dai il permesso access native observability database observer a chi deve leggere il report, e administer native observability database observer a chi deve cambiarne le impostazioni.
  3. Lascia la soglia a cento millisecondi per qualche giorno, poi guarda il report. Se la tabella resta vuota, sulle pagine che stai guardando non ci sono query lente: è un risultato, non un guasto.
  4. Prima di trarre conclusioni, fai girare la query di controllo qui sopra. Se restituisce righe, stai leggendo solo una parte di quello che è successo.

Che cosa questa prova non dimostra

La lentezza dell'ambiente di misura è finta, ed è giusto saperlo.

  • Il ritardo è iniettato da una pagina di test. La prova mostra il meccanismo, non che il modulo sappia diagnosticare da solo una lentezza vera, che di solito nasce da un indice mancante o da una query ripetuta dentro un ciclo.
  • Per mostrare il taglio delle righe la soglia è stata abbassata di proposito. Il caso in cui sia la lentezza vera a produrre più di venti query lente in una sola richiesta non è stato riprodotto.
  • Nessuna concorrenza e nessuna correzione rimisurata: una richiesta alla volta, su una pagina lenta per costruzione. I tempi assoluti non vanno letti come misura di prestazioni.

Fonti e riferimenti

Tutte le fonti sono state consultate il 27 settembre 2026.

  1. The Slow Query Log, MySQL 8.4 Reference Manual (si apre in una nuova scheda). Definizione del log lento e campi scritti. Letta dal DOM reso: a curl il sito risponde 403.
  2. Devel, pagina di progetto (si apre in una nuova scheda). La frase sulla sicurezza in produzione e il permesso da concedere. Letta dal DOM reso.
  3. WebProfiler, pagina di progetto (si apre in una nuova scheda). La toolbar in fondo a ogni pagina. Letta dal DOM reso.
  4. XHProf, pagina di progetto (si apre in una nuova scheda). Profiler gerarchico, scomposizione per chiamanti e chiamati. Letta dal DOM reso.
  5. DB Performance, pagina di progetto (si apre in una nuova scheda). Aggregazione per forma della query e suggerimento di indici, release 1.0.0-alpha2. Letta dal DOM reso.
  6. Native Observability, pagina di progetto (si apre in una nuova scheda). Requisiti e sottomoduli.
  7. Sorgente del modulo, ramo 2.0.x (si apre in una nuova scheda). Le classi citate in questa pagina si aprono da lì: DatabaseQueryObserverSubscriber per il conteggio delle query lente prima del limite per richiesta, DatabaseObserverSettingsForm per i vincoli delle due impostazioni, TraceSubscriber per le due intestazioni HTTP.
  8. I numeri di questa pagina vengono da un ambiente di misura con Drupal 11 che ho costruito io, non da un sito pubblico: comandi e risultati sono riprodotti qui sopra per intero, e le condizioni in cui valgono stanno nella sezione «Che cosa questa prova non dimostra». Non è una fonte che puoi aprire, è una misura che puoi rifare.
Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

Quanto rallenta Drupal il modulo Native Observability, e come lo misuri sul tuo server

Native Observability è il modulo Drupal che registra dall'interno cosa succede a ogni richiesta. Strumentare un sito con lui costa circa 4,7 millisecondi per richiesta su una macchina e circa 7,0 su un'altra, e scende a circa 1,8 spegnendo due sottomoduli. La cifra dipende dal server: la tua la ottieni con drush no:overhead:measure.

Quanto rallenta Drupal il modulo Native Observability?

Native Observability (si apre in una nuova scheda) è il modulo Drupal che registra dall'interno cosa succede a ogni richiesta, e strumentare un sito con lui costa circa 4,7 e circa 7,0 millisecondi per richiesta sulle due macchine dove l'ho misurato. Stesso codice, stessa versione, stesso comando di misura, due numeri che differiscono del 48%.

Cosa faccia il modulo e come sia costruito sta nella scheda del progetto; qui si parla soltanto di quanto costa e di come si misura.

Il primo è pubblicato nella documentazione del modulo (si apre in una nuova scheda), che ne dichiara le condizioni e la provenienza. Il secondo l'ho preso io su un ambiente DDEV in container, il 25 settembre 2026.

Valore Dove Condizioni dichiarate
~4,7 ms/req laptop di sviluppo, database in Docker dodici sottomoduli attivi, tabelle vuote, 15 coppie da 6 richieste; da ~4,6 a ~5,7 su otto ripetizioni in due giorni
~7,0 ms/req DDEV in container su iMac Drupal 11.4.5, PHP 8.3, Xdebug spento, 15 coppie, tabelle non vuote
~1,8 ms/req stesso laptop di sviluppo senza i sottomoduli Execution e Spans; confronto appaiato a sé, 9 coppie da 6 richieste, contro un controllo di ~4,7 ms/req misurato nella stessa sessione con undici sottomoduli

La terza riga è quella che conta di più: spegnere due sottomoduli porta il costo da circa 4,7 a circa 1,8 millisecondi, cioè lo taglia del 62%. Le due cifre del taglio vengono dalla stessa sessione appaiata, non dal confronto fra la prima e la terza riga di questa tabella. Nessuno dei tre numeri è la risposta alla tua domanda, perché nessuna di quelle installazioni è la tua.

Perché due macchine danno due numeri diversi

Il costo di strumentare un'applicazione dipende dalla macchina che esegue l'istruzione in più, e le due macchine qui sopra differiscono su tutto ciò che conta: frequenza e generazione della CPU, stato dell'opcache, tipo di storage, carico concorrente al momento della misura, e lo strato di virtualizzazione che c'è o non c'è sotto.

Va detto anche quello che non ho tenuto costante: le due installazioni Drupal non avevano lo stesso contenuto. La documentazione dichiara che la misura piu' bassa è stata presa a tabelle vuote; la mia no. Il volume di dati sposta il lavoro di alcune strumentazioni, quindi lo scarto fra le due cifre va letto come differenza fra due ambienti nel loro insieme, non fra due processori. Dichiararlo costa una riga e rende il numero utilizzabile: una cifra senza le sue condizioni è lo stesso genere di dato che i fornitori di APM pubblicano senza dire dove l'hanno preso.

Perché il modulo ha smesso di dichiarare una percentuale

Prima della 2.0.0, e cioè fino alla 1.x compresa, native_observability pubblicava una stima che nessuno aveva mai misurato. Le note di rilascio della 2.0.0, che quel codice lo hanno rimosso, lo dicono senza giri di parole:

The report used to sum four hardcoded constants and print a made-up "Estimated overhead: 10%".

Quattro costanti scritte a mano nel sorgente, sommate, stampate come percentuale. Quel codice è stato rimosso nella 2.0.0. The Drop Times (si apre in una nuova scheda) ha dedicato un articolo a quella decisione e riporta la mia risposta scritta, in cui definivo quel 10% una sovrastima ricavata dalle mie prove, che non si poteva trasferire da un server all'altro. La frase è mia, non loro: è una citazione, non una conferma indipendente. La loro copertura si ferma al costo del modulo, che è una sola delle cose che il modulo fa: qui resto sullo stesso terreno, e cosa faccia per il resto sta nella scheda del progetto.

Al suo posto la 2.0.0 ha messo un comando. La scelta costa qualcosa a chi cerca una cifra sulla pagina del progetto e non la trova, e rende qualcosa a chi deve decidere se installare il modulo su un server preciso.

Sono io il maintainer del modulo, quindi questa sezione è il resoconto di una decisione mia. Le misure le puoi rifare con lo stesso comando. Il giudizio sulla decisione resta mio.

Le misure di questa pagina sono state prese sulla 2.0.1. La release stabile pubblicata sul progetto è 2.0.1, e se è più recente conviene rifare la misura prima di fidarsi dei numeri qui sotto.

Come si misura: il comando

Un comando, nessuna configurazione:

drush no:overhead:measure

Il comando confronta due stati della stessa installazione: cattura accesa e cattura spenta. Misurare entrambi gli stati sullo stesso sito isola il costo del modulo dalle differenze fra un server e un altro, che è esattamente ciò che rende inutile confrontare il tuo numero col mio.

Il modulo include due rotte di calibrazione, escluse da una misura ordinaria: una che non esegue nessuna query e nessun rendering, una che esegue un carico dichiarato e costante. Servono a far sì che la misura ponga la stessa domanda su qualunque installazione.

Come fa il comando a non mentire

Il protocollo è quello del benchmarking in ambiente rumoroso, applicato a una richiesta HTTP. Le note di rilascio della 2.0.0 lo descrivono per intero, e sono cinque accorgimenti:

  1. Blocchi appaiati. Ogni coppia esegue un blocco con la cattura accesa e uno con la cattura

spenta, così un rallentamento del server colpisce entrambi i lati.

  1. Alternanza. Il lato che parte per primo cambia da una coppia alla successiva, perché il primo

dei due paga l'ingresso a freddo.

  1. Prima coppia scartata. Serve a riscaldare cache e opcache e non entra nel calcolo.
  2. Mediana delle differenze appaiate. La mediana regge un valore anomalo, la media no.
  3. Test dei segni esatto bilaterale, con soglia p ≤ 0,05, più una passata separata con la

cattura spenta su entrambi i lati per misurare il rumore di fondo della macchina.

L'ultimo punto è quello che distingue una misura da un numero. Senza il rumore di fondo non sai se il delta che hai ottenuto è l'effetto del modulo o il respiro del tuo server.

Come leggo il risultato

Quattro valori decidono se il numero vale, e stanno tutti nell'output. Questo è il mio, tagliato alle righe che contano:

Fixed cost per request (a): 6.953 ms/req

calibration-minimal:
  - budget: 0.87% of the declared TTFB budget (good)
  - N 15 | IQR [6.908, 7.020] | noise 0.040 | signs 7/7 | p=0.01562
Valore Il mio Come si legge
N 15 coppie effettive dopo lo scarto del riscaldamento. Sotto 10 il test dei segni non arriva alla significatività
IQR [6.908, 7.020] metà centrale delle differenze. Se è larga quanto il valore, la misura non è stabile
noise 0,040 ms rumore di fondo della macchina. Il mio effetto ci sta sopra centosettantatré volte
p 0,01562 probabilità che i segni osservati vengano dal caso. Sopra 0,05 lo strumento marca il risultato unclear

Un delta che non superi il rumore viene dichiarato unclear invece di meas, e a quel punto la cosa utile da fare è rifare la misura su una macchina più tranquilla.

Circa 7 millisecondi sono tanti o pochi?

Dipende dal budget di risposta del tuo sito, e contro il mio sono circa lo 0,9%.

La soglia di riferimento arriva da Solving Big Data Challenges for Enterprise Application Performance Management (si apre in una nuova scheda) (Rabl, Gómez-Villamor, Sadoghi, Muntés-Mulero, Jacobsen, Mankovskii, VLDB 2012), che la formula così: «As a rule of thumb, a maximum tolerable overhead is five percent, but a smaller rate is preferable». Sopra quella soglia il monitoraggio degrada ciò che dovrebbe osservare.

Quanto la soglia venga rispettata è un'altra questione. groundcover osserva che il 3-5% ricorre nei benchmark che i fornitori di APM conducono su sé stessi, e cita una misura di Scout APM, che di New Relic è concorrente, in cui il suo agente aggiungeva oltre il 44% in uno scenario Ruby.

Budget TTFB del sito ~7,0 ms valgono Verdetto contro la soglia del 5%
800 ms ~0,9% largamente dentro
300 ms ~2,3% dentro
140 ms ~5% al limite

Gli 800 millisecondi non li ho scelti io: sono il valore di overhead.budget_ms nella configurazione del modulo, e il commento nel sorgente dice che vengono dalla soglia TTFB «good» documentata da web.dev. Serve a colorare il verdetto, e va sostituito col budget reale del tuo sito: una cifra in millisecondi senza il budget contro cui confrontarla non dice se il modulo sia sostenibile.

La scrittura sul database il visitatore non la paga

Le righe non vengono scritte durante la richiesta. DeferredPersistenceBuffer le accumula in memoria e le scarica con una sola INSERT multi-riga agganciata a KernelEvents::TERMINATE, l'evento che Drupal emette dopo che la risposta ha raggiunto il client. Il commento nel sorgente dice da dove si veniva: una richiesta carica spediva «circa 130 INSERT singole».

Tre dettagli del meccanismo, perché la differenza fra buffering e scrittura differita sta lì:

  • il buffer si svuota da sé oltre le 1000 righe, così una richiesta anomala non tiene in memoria un

volume illimitato;

  • flush() ingoia le eccezioni, perché un errore dell'osservabilità non deve far cadere la risposta

che stava osservando;

  • l'impostazione deferred_persistence_enabled riporta il modulo alla scrittura immediata, quando

serve per capire cosa stia succedendo al buffer.

Quello che invece il visitatore paga è la strumentazione, e resta dentro la richiesta. Nel censimento a livello di richiesta pubblicato nella documentazione del modulo (si apre in una nuova scheda) e ripreso da The Drop Times (si apre in una nuova scheda), su circa 6,3 millisecondi di lavoro del modulo circa 0,4 stavano nel flush differito (il 6,1%) mentre il restante 93,9% girava prima che la risposta partisse: tracciamento dell'esecuzione, collegamento degli span, osservazione della cache.

Quel censimento risponde a una domanda diversa dal costo fisso per richiesta, e i due numeri non vanno messi nella stessa colonna. Il censimento conta il lavoro che il modulo attribuisce a sé stesso. La misura appaiata conta la differenza fra un sito strumentato e lo stesso sito senza strumentazione, ed è quella che il visitatore paga.

Quanto raccoglie il modulo lo decidi tu, e i limiti sono già impostati

Ogni fonte di dati ha un tetto scritto nella configurazione predefinita, e nessuno di questi valori è cablato nel codice. Come impostarli sta nella pagina di configurazione della documentazione (si apre in una nuova scheda); questi sono i default, da config/install:

In native_observability.settings:

Impostazione Default Cosa limita
retention.max_rows 50.000 righe totali conservate
trace_retention_hours 72 vita delle tracce
spans_retention_hours 24 vita degli span di esecuzione
metrics_retention_days 7 vita delle metriche aggregate
cleanup_batch_limit 5.000 righe cancellate per giro di pulizia

I due observer sono sottomoduli e hanno un oggetto di configurazione proprio. In native_observability_database_observer.settings:

Impostazione Default Cosa limita
slow_query_threshold_ms 100 sopra quanti millisecondi una query viene registrata
max_stored_queries_per_request 20 query conservate per richiesta
retention_hours 72 vita delle query registrate

In native_observability_cache_observer.settings:

Impostazione Default Cosa limita
max_stored_invalidations_per_request 50 invalidazioni di cache conservate per richiesta
retention_hours 72 vita degli eventi di cache registrati

Le due soglie per richiesta sono quelle che tengono il costo costante quando una pagina si comporta male: una rotta che esegue trecento query ne fa registrare venti, non trecento.

Sul lato dei dati personali i default partono chiusi, e questo abbassa anche il lavoro: il corpo della richiesta, la query string e gli header non vengono raccolti (body_mode, query_mode, headers_mode sono a off), l'indirizzo IP è anonimizzato e dello user agent resta solo la famiglia. Il modulo esclude inoltre le proprie rotte dalla cattura (exclude_own_routes), così aprire la dashboard non gonfia i contatori che stai leggendo.

Cosa dichiarano gli altri strumenti

Nessuno dei fornitori di APM per PHP pubblica una cifra ottenuta sulla macchina del cliente, e due dei quattro che ho consultato non pubblicano alcuna cifra. La colonna che conta non è quanto dichiarano, è a quali condizioni.

Strumento Cosa pubblica A quali condizioni
Tideways 4,93% su PHP 5.6 e 17,11% su PHP 7 per Timeline al 10% di campionamento, 13,41% e 23,86% per XHProf benchmark oss-performance contro WordPress, con la macchina spinta al limite e tutti i core saturi. Il fornitore avverte che «for actual applications the overhead is smaller» e che il risultato «is just representive of WordPress»
Blackfire «close-to-no overhead» sulle trace standard, e fino al 15% misurato sulle Extended Traces massimo rilevato dal fornitore, con Drupal nominato esplicitamente insieme a Symfony, Prestashop 1.7+ e Ibexa DXP
New Relic nessuna cifra nella documentazione consultata (si apre in una nuova scheda), che spiega come ridurre l'overhead non applicabile
Datadog nessuna cifra nella documentazione consultata (si apre in una nuova scheda), che copre i limiti di frequenza dell'agente non applicabile

Le percentuali di Tideways non si confrontano con la mia. Misurano WordPress su PHP 5.6 e PHP 7, sotto un carico costruito per saturare la macchina, mentre il mio numero viene da Drupal 11.4.5 su PHP 8.3. Altro CMS, due versioni maggiori di PHP di distanza, condizioni opposte.

Quello che si confronta è il comportamento. Tideways e Blackfire pubblicano numeri misurati e ne dichiarano i limiti, e quella è la cosa giusta da fare anche quando il numero che esce non è lusinghiero. New Relic e Datadog, sulle pagine che ho letto, non pubblicano niente. Il confronto che regge è questo, e non ha percentuali dentro.

Che cosa chiedere a chi pubblica un numero di prestazioni

Quattro domande, e valgono per qualunque strumento, non solo per questo. Non sono teoriche: le ho scritte dopo aver trovato tre difetti che falsavano la misura del mio stesso modulo, elencati con i loro numeri nelle note di rilascio della 2.0.0 (si apre in una nuova scheda).

  1. Lo strumento si esclude dalla propria misura? Chi traccia anche le rotte con cui si misura

conta sé stesso, e il segno dell'errore non è prevedibile: nel mio caso il costo risultava più basso del 55%, non più alto.

  1. La ripetizione è protetta dalla cache? Se la seconda esecuzione può essere servita dalla

cache della prima, il numero pubblicato è il tempo della cache: circa due ordini di grandezza più basso, cioè un costo che non esiste.

  1. La configurazione viene riletta? Un servizio che legge le impostazioni una volta sola, per

la vita della propria istanza, funziona sotto PHP-FPM e mente in un runtime persistente. È la classe di problema del meta issue del core sui server applicativi persistenti (si apre in una nuova scheda), che elenca ReactPHP, PHP-PM, PHPFastCGI, FrankenPHP e Swoole.

  1. Il rumore di fondo è dichiarato? Senza, non si distingue l'effetto misurato dal respiro della

macchina, ed è il motivo per cui noise sta dentro il risultato del comando insieme al valore.

Se chi pubblica un numero non risponde a queste quattro, quel numero vale quanto valeva il «10%» stampato dalla 1.x.

Cosa faccio con questo numero

Misuro prima di installare in produzione, e rimisuro dopo ogni cambio di macchina o di versione di PHP. La procedura sta in quattro passi:

  1. Installare il modulo su un ambiente identico a quello di produzione per versione di PHP, di

Drupal e di database, con Xdebug spento: Xdebug strumenta ogni chiamata di funzione e qualunque misura fatta con lui acceso va buttata.

  1. Eseguire drush no:overhead:measure senza altro carico sulla macchina.
  2. Leggere N, IQR, noise e p prima del valore. Se il verdetto è unclear, il valore non si

usa.

  1. Dividere il costo per il budget di risposta del sito e confrontare con la soglia che ti sei dato.

I passaggi di installazione stanno nella documentazione del modulo (si apre in una nuova scheda), e cosa il modulo faccia davvero nella scheda del progetto su questo sito. Il numero che ottieni vale per la tua macchina e per quella installazione. Il mio l'ho pubblicato perché serva da termine di paragone sull'ordine di grandezza, e perché si veda che due misure dello stesso codice possono stare a mezzo millisecondo o a due millisecondi di distanza.

Fonti e riferimenti

Tutte le fonti sono state consultate il 27 settembre 2026.

  1. Native Observability, pagina del progetto (si apre in una nuova scheda). Requisiti, comando di misura, release stabile 2.0.1.
  2. Native Observability, documentazione: il costo del modulo (si apre in una nuova scheda). Le cifre 4,684, 1,795, 6,253 e 0,379 con le loro condizioni e la tabella di provenienza.
  3. Native Observability, documentazione: configurazione (si apre in una nuova scheda) e installazione (si apre in una nuova scheda).
  4. Native Observability 2.0.0, note di rilascio (si apre in una nuova scheda). Rimozione della stima a costanti, protocollo di misura appaiata, test dei segni.
  5. Native Observability 2.0.0 Measures Its Own Performance Cost, The Drop Times (si apre in una nuova scheda). Censimento a livello di richiesta, 6,253 ms e scomposizione del flush differito.
  6. APM Overheads: Is Your APM Slowing You Down?, groundcover (si apre in una nuova scheda). Il 3-5% nei benchmark dei fornitori, e la misura indipendente oltre il 44% su uno scenario Ruby.
  7. Profiling Overhead and PHP 7, Tideways (si apre in una nuova scheda). Overhead misurato dal fornitore su PHP 5.6 e PHP 7, da 4,93% a 23,86%.
  8. Configuring Blackfire Monitoring (si apre in una nuova scheda). «close-to-no overhead» sulle trace standard, fino al 15% misurato sulle Extended Traces.
  9. PHP agent overhead reduction tips, New Relic (si apre in una nuova scheda). Assenza di una cifra dichiarata.
  10. Agent Rate Limits, Datadog (si apre in una nuova scheda). Assenza di una cifra dichiarata; la pagina copre i limiti di frequenza dell'agente.
  11. Robust benchmarking in noisy environments (si apre in una nuova scheda). Rumore del sistema operativo, riscaldamento, filtro degli anomali.
  12. Make Drupal compatible with persistent app servers like ReactPHP, PHP-PM, PHPFastCGI, FrankenPHP, Swoole, core #2218651 (si apre in una nuova scheda). La classe di difetto della configurazione letta una volta per istanza.
  13. Solving Big Data Challenges for Enterprise Application Performance Management, Rabl et al., VLDB 2012 (si apre in una nuova scheda). La soglia del 5% come regola pratica.
Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

AI Disclosure

AI Disclosure è un modulo Drupal che registra, per ogni contenuto e per ogni traduzione, quanto l'AI ha contribuito al testo o al media. Il lettore lo vede in una scheda con le icone ufficiali dell'Unione europea, mentre motori e agenti lo leggono nel codice della pagina. Il modulo segnala anche quando l'articolo 50(4) dell'AI Act rende l'etichetta obbligatoria.

AI Disclosure è nato nella Drupal AI Initiative ad agosto 2026, quando l'articolo 50 dell'AI Act aveva appena cominciato ad applicarsi e Drupal non aveva ancora un modo per dire al lettore come un contenuto era stato scritto. L'ho sviluppato io, a partire da un piano di Marcus Johansson.

Sector
AI, editoria, compliance
Role
Autore e maintainer, con @marcus_johansson e @joevagyok
Year
2026
Tech Stack
Drupal
PHP
schema.org
IPTC
Tool API

Che cosa chiede l'AI Act a chi pubblica contenuti

Dal 2 agosto 2026 l'articolo 50 dell'AI Act chiede a chi pubblica di dichiarare due tipi di contenuto: le immagini, gli audio e i video che imitano persone, luoghi o fatti reali fino a sembrare autentici, e i testi generati con l'AI che informano il pubblico su questioni di interesse pubblico. Per i testi c'è un'eccezione: se una persona li ha rivisti e se ne assume la responsabilità editoriale, l'etichetta non è obbligatoria.

La norma quindi non vieta l'AI e non chiede di dichiararla sempre. Chiede di sapere, contenuto per contenuto, se ricorre uno di quei casi.

Come AI Disclosure decide se l'etichetta è dovuta

AI Disclosure classifica ogni contenuto su nove gradi, da «Solo umano» a «Deep fake AI». Sei dei nove portano la premessa dell'obbligo: senza quella il modulo non pone nemmeno le domande successive, e il verdetto è «non dovuta». L'elenco completo, con la frase che ogni grado mostra al lettore e l'icona che porta, sta nella documentazione del modulo (si apre in una nuova scheda).

Con la premessa, la regola si divide in due rami a seconda del mezzo.

Deep fake audiovisivo: paragrafo 1

L'etichetta è dovuta senza condizioni. Revisione umana, responsabilità editoriale e interesse pubblico non vengono nemmeno consultati.

Testo generato con l'AI: paragrafo 2

L'etichetta è dovuta se il contenuto informa il pubblico e riguarda una questione di interesse pubblico. Un no su una delle due chiude a «non dovuta» anche se l'altra resta senza risposta.

Per il testo c'è un'esenzione, e vale solo se ricorrono entrambe le condizioni: una revisione sostanziale con controllo editoriale e una responsabilità editoriale con un nome. La revisione da sola non basta: è la differenza fra «l'ho riletto» e «qualcuno ci mette la firma».

Che cosa risolve AI Disclosure per una redazione

AI Disclosure trasforma la valutazione sull'obbligo di etichetta in un dato del contenuto. La redazione descrive una volta sola come usa l'AI per ogni tipo di contenuto: per esempio, gli articoli scritti con l'assistenza dell'AI e rivisti da un giornalista, o le traduzioni automatiche rilette. Ogni nuovo contenuto eredita quella descrizione, e si cambia solo per le eccezioni.

Da lì il modulo fa tre cose. Mostra al lettore una scheda con l'icona ufficiale pubblicata dalla Commissione europea e una frase chiara. Scrive la stessa informazione nel codice della pagina, dove la leggono motori di ricerca e agenti. Tiene un report di tutto il sito, che dice per ogni contenuto se l'etichetta è dovuta, non è dovuta o manca ancora una risposta per deciderlo.

Il modulo segue le linee guida della Commissione sull'articolo 50 ma non decide al posto di chi pubblica: registra la valutazione e la rende visibile.

Come ho costruito AI Disclosure in Drupal

Il 3 agosto 2026 Marcus Johansson ha pubblicato nella Drupal AI Initiative il piano del modulo: sedici passi, dalle fondamenta alle integrazioni con gli altri moduli AI di Drupal. Gli ho scritto per prenderne lo sviluppo, e il 17 agosto ho aperto il progetto su drupal.org.

Fra il 18 e il 24 agosto ho chiuso le prime tre fasi del piano, dieci passi su sedici, fra cui la struttura dei livelli di AI, le descrizioni riusabili, il campo sui contenuti, la scheda per il lettore, i dati per le macchine, il report e la gestione delle traduzioni. Restano aperti la documentazione di progetto e le integrazioni con cinque moduli AI esistenti, che permetteranno a traduttori e generatori automatici di dichiarare da soli il proprio contributo.

Con chi sviluppo AI Disclosure

AI Disclosure ha tre maintainer. Marcus Johansson (@marcus_johansson (si apre in una nuova scheda)) ha scritto il piano nella Drupal AI Initiative ed è co-maintainer del progetto. Adam Nagy (@joevagyok (si apre in una nuova scheda)) si è unito in seguito come co-maintainer: secondo il suo profilo su drupal.org lavora come consulente Drupal per la Commissione europea sulla Europa Web Platform. Io scrivo il codice: al 22 settembre 2026, 221 dei 226 commit del repository sono miei.

Come uso AI Disclosure su questo sito

Ogni pagina di questo sito porta la scheda di AI Disclosure in fondo al testo. Gli originali italiani dichiarano che sono scritti con l'assistenza dell'AI e rivisti da me. Le versioni inglesi dichiarano che sono tradotte con l'AI e rilette.

Il 22 settembre 2026, mentre scrivevo questa scheda, ho scoperto che il sito non faceva quello che il modulo promette: la dichiarazione era una sola per le due lingue, e le pagine inglesi raccontavano come era nato l'italiano. L'ho corretta lo stesso giorno. È il caso per cui il modulo esiste: una dichiarazione vale per la lingua che il lettore ha davanti.

Il grado però non lo scelgo contenuto per contenuto. Il campo su questa pagina è vuoto: la dichiarazione arriva da un profilo predefinito, Editorial default, che il modulo applica a ogni contenuto che non ne porta uno proprio. Il profilo dice grado «Assistito dall'AI, guidato da una persona», revisione umana fatta, responsabilità editoriale a nome mio, mezzo testo, contenuto che informa il pubblico, questione di interesse pubblico no.

Da quelle risposte il modulo calcola required=not-required, e vale la pena dire perché: non per l'esenzione da revisione, che pure sarebbe soddisfatta, ma perché il paragrafo 2 chiede due condizioni insieme e questa pagina ne porta una sola. Una scheda di progetto informa il pubblico, ma non è una questione di interesse pubblico. Se un giorno scrivessi qui di politica europea, la stessa configurazione produrrebbe required=required senza che io tocchi niente, e l'etichetta comparirebbe da sola.

La scheda in fondo a questa pagina è quel calcolo reso visibile: l'icona ai_modified della Commissione europea, la frase del grado, e il dettaglio richiudibile con la descrizione del profilo. La riga con la responsabilità editoriale non compare perché il grado la tiene spenta fra le parti visibili, non perché manchi il nome.

Chi legge non deve fidarsi della scheda: la stessa informazione sta nel codice della pagina, e si legge da riga di comando. Il comando interroga la versione inglese di questa scheda.

curl -s https://giorgiopagano.org/en/projects/ai-disclosure | grep 'name="ai-disclosure"'

Risponde ai_translated; required=not-required; icon=ai_modified: il grado, il verdetto sull'obbligo, e quale icona della Commissione europea va mostrata. La versione inglese dichiara il grado della traduzione, questa pagina ai_assisted_hitl: il verdetto e l'icona restano gli stessi. Come si installa e come si configura sta nella documentazione del modulo (si apre in una nuova scheda).

Fonti su AI Disclosure e sull'articolo 50

Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

AI Content Migrate

Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

DOC to HTML

Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.

Native Observability

Native Observability è un modulo Drupal che registra dall'interno dell'applicazione cosa succede a ogni richiesta: tracce, span di esecuzione, eventi di cache, query lente e metriche aggregate per rotta. Non richiede un agente esterno né patch al core, e misura il proprio costo sul server dove gira invece di dichiararlo.

Native Observability è nato il 12 febbraio 2026 da una domanda pratica: capire cosa stesse facendo Drupal in produzione su un sito dove non potevo installare un agente esterno. L'ho scritto io, e su drupal.org ne sono l'unico maintainer, quindi ogni confronto con altri strumenti che trovi in questa pagina va letto sapendo questo. I dati sui moduli alternativi sono presi dalle rispettive pagine di progetto il 24 settembre 2026 e sono verificabili uno per uno; il giudizio su quando serve un modulo invece di un altro è mio, e lo trovi separato dai dati.

Sector
Strumenti per sviluppatori
Role
Autore e maintainer
Year
2026
Tech Stack
Drupal
PHP

Che cosa registra Native Observability

Il modulo registra cinque famiglie di dati, tutte legate alla singola richiesta HTTP.

Tracce

Ogni richiesta riceve un identificativo ULID, e le richieste figlie, comprese quelle AJAX, vengono correlate alla richiesta che le ha generate. L'identificativo di correlazione viene anche restituito in un header della risposta, quindi è recuperabile da fuori.

Span di esecuzione

I tempi dei servizi eseguiti durante la richiesta, categorizzati e visualizzabili per traccia. Le chiamate HTTP in uscita vengono osservate da un middleware sullo handler stack di Guzzle, quindi entra nella traccia ogni richiesta fatta attraverso l'http_client di Drupal, senza toccare il modulo che la esegue.

Metriche per rotta

Volume di richieste, durata media e due percentili, aggregati per rotta e non per URL.

Il percentile dice quanto è lenta la pagina per chi sta peggio. Il P95 è il tempo sotto il quale restano 95 richieste su 100: se vale 800 millisecondi, cinque richieste su cento hanno impiegato di più. Il P99 alza l'asticella a 99 su 100 e fotografa la coda peggiore. Servono perché la media nasconde proprio quelle: novantacinque pagine veloci e cinque lentissime danno una media rassicurante e un sito che qualcuno trova inutilizzabile.

Aggregare per rotta, e non per URL, è ciò che rende confrontabili due pagine dello stesso tipo con contenuti diversi.

Eventi di cache

Comportamento reale del livello di cache delle risposte, cacheabilità e invalidazioni per tag, ciascuno con un report filtrabile e un export JSON.

Come si leggono questi eventi su richieste vere, compreso il HIT della page cache che Drupal non gestisce, è spiegato nell'articolo perché una pagina Drupal non va in cache.

Query di database

Le query lente o comunque rilevanti, con la soglia decisa da slow_query_threshold_ms e un tetto per richiesta deciso da max_stored_queries_per_request, entrambi in native_observability_database_observer.settings.

Requisiti e compatibilità

Requisito Valore
Drupal 10 oppure 11, dichiarato su ogni modulo della famiglia
PHP 8.2 o superiore
Database qualunque database supportato da Drupal core
Release stabile corrente 2.0.1, 21 settembre 2026
Security advisory policy coperto

Il limite su PHP 8.2 non è prudenza: il modulo usa classi readonly, che PHP 8.1 non riesce a interpretare. Drupal 10 accetta ancora PHP 8.1, quindi su quella major il modulo è più restrittivo del core, e composer.json rifiuta l'installazione all'inizio invece di lasciarla fallire dopo.

Sul database c'è un solo caso particolare. La dashboard ordina i campioni con una window function, e MySQL 5.7 è l'unico server ammesso da Drupal che non ne ha. Riguarda solo Drupal 10, perché Drupal 11 richiede già MySQL 8.0. Su MySQL 5.7 l'ordinamento si sposta in PHP, le pagine continuano a funzionare, e lo status report lo dichiara.

Come si installa

L'installazione si fa con Composer e poi con uno dei tre comandi Drush che installano un tier intero.

composer require drupal/native_observability
drush en native_observability -y

Il secondo comando serve la prima volta e non è una formalità: Drush scopre i comandi di un modulo solo dopo che quel modulo è abilitato, quindi su un sito pulito i comandi drush no:preset:* non esistono ancora.

Tier Per chi Comando
raw scraper esterni, Prometheus, Mimir, CI. Nessuna interfaccia drush no:preset:raw
dashboard chi amministra il sito e legge i dati dentro Drupal drush no:preset:dashboard
integrations chi spinge la telemetria verso uno stack esterno via OTLP, o collega gli eventi a ECA drush no:preset:integrations

Ogni preset è idempotente: rieseguirlo su un sito già configurato stampa «Already enabled» per i moduli noti ed esce senza errore.

Perché il modulo può rifiutare di installarsi

Native Observability avvolge i servizi del core invece di applicare patch, e questo lascia spazio a un problema preciso: un altro modulo che decora o ri-tagga lo stesso servizio può prendere silenziosamente il sopravvento. La strumentazione smetterebbe di vedere una parte dei dati senza che compaia nessun errore, e i grafici continuerebbero a disegnarsi, semplicemente incompleti.

Il sanity check risponde a una domanda sola: la strumentazione è davvero agganciata? Elenca ogni punto di integrazione che la famiglia installa, conferma che sia l'implementazione attiva, e segnala qualunque altro modulo stia competendo per lo stesso punto. Si raggiunge da /admin/reports/native-observability/sanity-check o con drush no:sanity-check.

Quando trova una sovrascrittura incompatibile, il modulo base e tutti i sottomoduli rifiutano di installarsi. Uno strumento di misura che misura male è peggio di uno strumento assente, perché nessuno ha motivo di dubitare dei suoi numeri.

Quanto costa, e chi lo misura

Il costo del modulo lo misuri tu, sul tuo server, con un comando che il modulo stesso porta con sé. Non c'è una percentuale da credere sulla parola, perché una percentuale misurata altrove non descrive la tua installazione.

drush no:overhead:measure

La misura confronta due stati dello stesso sito, alterna i blocchi, lavora su differenze appaiate con un test di significatività deterministico, scarta il riscaldamento e dichiara cosa non è compreso nel numero. Il modulo porta due rotte di calibrazione apposite, chiuse fuori da una misura: una che non esegue nessuna query e nessun rendering, e una che esegue un carico dichiarato e costante. Servono a due cose: far sì che la stessa misura ponga la stessa domanda su qualunque installazione, e rendere confrontabili due esecuzioni sullo stesso sito.

Due macchine, due numeri diversi

La stessa misura, con lo stesso protocollo, su due macchine mie dà 4,684 e 6,953 millisecondi per richiesta. Non è un errore di una delle due: è il motivo per cui il modulo ha smesso di pubblicare una cifra unica.

Il primo numero viene dalla macchina di sviluppo su cui è nato il modulo, ed è quello riportato nella documentazione, con la sua tabella di provenienza. Ripetendo la misura otto volte in due giorni, da 9 a 15 coppie per volta, lì la forchetta è stata da 4,59 a 5,72 millisecondi.

Il secondo viene da un ambiente DDEV in container su un iMac, Drupal 11.4.5 e PHP 8.3, misurato il 25 settembre 2026 con Xdebug spento, perché Xdebug strumenta ogni chiamata di funzione PHP e qualunque misura fatta con lui acceso non vale niente. Questo è l'output, tagliato alle righe che contano:

Fixed cost per request (a): 6.953 ms/req

| Workload               | OFF       | ON     | Delta | Verdict |
| calibration-minimal    | 3.778 ms  | 10.757 | 6.953 | meas    |
| calibration-calibrated | 22.476 ms | 30.279 | 7.707 | meas    |

calibration-minimal:
  - budget: 0.87% of the declared TTFB budget (good)
  - N 15 | IQR [6.908, 7.020] | noise 0.040 | signs 7/7 | p=0.01562

Le due righe che rendono leggibile il numero sono le ultime. Il rumore di fondo della macchina è 0,040 millisecondi e l'effetto misurato è 6,953: il segnale sta centosettantatré volte sopra il rumore, con il test dei segni a 7 su 7 e p = 0,01562. Un delta che non superasse il rumore lo strumento lo marcherebbe unclear invece di meas, e non lo pubblicherei.

Contro un budget di risposta di 800 millisecondi sono lo 0,87%, ma quel denominatore resta mio: va sostituito col budget del tuo sito.

Il costo non cresce col carico

Le due rotte di calibrazione eseguono lavoro diverso, e il costo del modulo fra le due non cambia in modo misurabile: la differenza è 0,754 millisecondi su 20 unità dichiarate, con p = 0,125 contro una soglia di rumore di 0,293. Lo strumento non la arrotonda a zero e non la spaccia per un risultato positivo, la dichiara non risolta e spiega cosa vuol dire:

> This is a result, not a failure: it means the cost does not grow with the > declared load, as far as this run can resolve.

In pratica: il modulo costa una cifra fissa per richiesta, e non di più su una pagina che lavora di più. Se ti serve risolvere quella differenza, il comando accetta più coppie per alzare la potenza del test.

Il numero che non troverai qui

La tabella riporta anche +184,05% sulla rotta minimale, e quella percentuale non va letta. La legenda dello strumento lo dice da sé: la rotta di calibrazione minimale non esegue query né rendering, quindi il suo denominatore è artificialmente piccolo e la percentuale che ne esce non descrive nessuna pagina reale. Va letto il delta assoluto, e la quota sul budget della tua pagina.

Cosa ha osservato chi l'ha messo in produzione

Sotto l'episodio di Talking Drupal dedicato al modulo, un lettore ha raccontato di averlo installato su un sito di produzione che serve circa cento pagine al minuto e di averlo tenuto acceso un paio di giorni, senza percepire nessun calo di prestazioni. Nello stesso commento segnala un effetto che pesa di più: il database è cresciuto di tre o quattro volte.

È esattamente ciò che deve succedere. Un modulo che registra cosa succede a ogni richiesta scrive righe, e quelle righe occupano spazio su disco. La discussione pubblica su questo modulo ha parlato quasi solo di millisecondi: chi lo mette in esercizio si accorge prima del disco.

Come si tiene sotto controllo lo spazio

Impostazione Valore predefinito Cosa limita
retention.max_rows 50.000 tetto di righe su tracce, span, eventi di cache e query
trace_retention_hours 72 da quante ore le tracce vengono cancellate
spans_retention_hours 24 idem per gli span
metrics_retention_days 7 idem per le metriche aggregate
max_stored_queries_per_request 20 quante query si conservano per richiesta
max_stored_invalidations_per_request 50 quante invalidazioni di cache per richiesta

C'è anche un interruttore generale, capture.enabled: spegnendolo il modulo smette di registrare senza essere disinstallato, e i dati già raccolti restano disponibili per l'analisi. È il modo previsto per accendere l'osservazione su una finestra di tempo, spegnerla, e lavorare con calma su ciò che è stato raccolto.

Perché il numero prima non c'era

Fino alla versione 1.1.x il modulo pubblicava «Estimated overhead: 10%», una percentuale ottenuta sommando quattro costanti scritte nel codice: nessuna delle quattro era stata misurata, e la cifra non descriveva nessuna installazione reale, compresa quella di chi la leggeva. La versione 2.0.0 ha cancellato quel codice. Dopo l'aggiornamento la sezione dell'overhead nel report parte nascosta, e ricompare solo quando una misura è stata davvero eseguita.

Come si colloca rispetto agli altri strumenti

Image
La goccia di Drupal mostra il codice di una richiesta, accanto a un pannello con il suo tempo di risposta e le cinque cose che il modulo ha registrato.

Native Observability non è un profiler, è una scatola nera. La distinzione non è di marketing e cambia chi ha davanti lo strumento.

Un profiler lo accendi quando stai indagando: sei tu alla tastiera, riproduci il problema, e in cambio ottieni un dettaglio che qui non troverai. XHProf scompone il tempo per funzione, con l'albero di chi chiama chi. Native Observability cronometra i servizi e si ferma lì.

Una scatola nera invece era già accesa quando la cosa è successa. Registra meno, e tutto il suo valore sta nel non aver dovuto prevedere il guasto. È la differenza fra chiedersi «perché questa pagina è lenta mentre la guardo» e «perché quella richiesta di ieri, che non riesco a riprodurre, ci ha messo sei secondi».

Dati letti dalle pagine di progetto su drupal.org il 24 settembre 2026.

Strumento Ultima release Siti Security policy Cosa misura
webprofiler 11.2.3, 9 settembre 2026 1.164 coperto una richiesta per volta, da una toolbar sulla pagina che stai guardando
monitoring 8.x-1.22, 9 luglio 2026 2.595 coperto la salute del sito con sensori, non la latenza per rotta
opentelemetry 1.0.0-beta7, 1 aprile 2026 765 nessuna release stabile esiste tempo della richiesta e query, esportati a un collector esterno
xhprof 2.0.0-beta1, 12 marzo 2025 205 non coperto costo per funzione PHP, richiede un'estensione sul server
native_observability 2.0.1, 21 settembre 2026 12 coperto tracce, span, metriche per rotta, cache e query, con export

Due righe di quella tabella meritano attenzione e raramente vengono scritte. Il modulo opentelemetry, installato su 765 siti, non ha release stabili: la sua pagina dichiara «There are currently no supported stable releases». Il modulo monitoring, il più diffuso del gruppo con 2.595 siti, misura se il cron gira e se ci sono aggiornamenti, cioè la salute, che è una domanda diversa da quale rotta è lenta.

Il giudizio, separato dai dati, ed è una guida su dove andare più che un confronto. Se stai guardando la pagina mentre è lenta, apri WebProfiler: il riscontro è immediato e non devi lanciare niente. Se ti serve sapere quale funzione consuma il tempo, XHProf o Blackfire rispondono a quella domanda e Native Observability no. Se ti servono indici suggeriti sulle query lente, db_performance fa quello. Se hai più servizi e un'infrastruttura da correlare, uno stack esterno resta la scelta giusta.

Native Observability risponde a una domanda che gli altri non prendono in carico: cosa è successo a una richiesta precisa, già avvenuta, che non sai riprodurre. Ogni risposta porta un identificativo nell'header X-Native-Observability-Request-Id, e quella stringa chi ha avuto il problema può consegnartela. Da lì si arriva alla rotta, agli span e alle query lente di quella richiesta, non di una simile.

Il prezzo va detto nello stesso respiro. Si registra meno di un profiler. Ci sono soglie e tetti che buttano via dati, ed è il prezzo di restare sempre accesi. Il costo per richiesta esiste, ed è misurato invece che stimato. Non si suggeriscono indici. In cambio non serve un agente esterno, non serve un abbonamento, e i dati restano nel tuo database.

Stato del progetto

Voce Valore
Release stabile 2.0.1, 21 settembre 2026
Ramo corrente 2.0.x. Il ramo 1.1.x riceve solo correzioni
Siti dichiarati su drupal.org 12
Issue aperte 2, entrambe minori
Maintainer Giorgio Pagano, unico
Copertura security advisory policy di drupal.org

Dodici installazioni sono poche, e le scrivo invece di ometterle. Le due issue aperte al 24 settembre 2026 riguardano un riferimento sbagliato alla versione nella documentazione di installazione e una verifica automatica di compatibilità con Drupal 12 generata da un bot: nessun bug funzionale, nessuna richiesta di supporto rimasta senza risposta.

La documentazione completa, ventisette pagine fra guida d'uso e riferimento per sviluppatori, è pubblicata su project.pages.drupalcode.org/native_observability e viene generata dal repository del modulo, quindi segue il codice invece di essere una copia che invecchia altrove.

Modificato dall'AI

Questo contenuto è stato prodotto dall'AI e rivisto da una persona.

Come è stata usata l'AI?

Le bozze sono prodotte con l'assistenza dell'AI, poi dirette, rivedute e verificate da una persona. Numeri, date e versioni sono confrontati con le fonti pubbliche prima della pubblicazione, e la data di quel controllo è indicata nel testo.