Quale servizio esterno sta rallentando questa rotta Drupal?

Approfondisci il progetto Native Observability
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, quanto costa il modulo in prestazioni nell'articolo sul suo sovraccarico.

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

Native Observability si aggancia al punto da cui passano tutte le chiamate. 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. 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.

Un caso reale, raccontato a The Drop Times (si apre in una nuova scheda): un cron aggregava i dati di un API gateway, e dopo circa dieci richieste gli endpoint a monte rallentavano senza restituire errori. Le app mobili ricevevano così dati già vecchi.

Che cosa il modulo non fa

Native Observability 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 backend OpenTelemetry vale la pena 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

Per cominciare a misurare le chiamate esterne bastano 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). Sottomoduli e requisiti. Fonte primaria.
  2. Sorgente del modulo (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. 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.
12 min 2.259 parole

Perché questa pagina Drupal non va in cache?

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...

8 min 1.442 parole

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

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ù:...

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.