Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni

Il protocollo QUIC è estremamente interessante da guardare, motivo per cui amiamo scriverne. Ma se le precedenti pubblicazioni su QUIC erano più di natura storica (storia locale, se preferite) e hardware, oggi siamo lieti di pubblicare una traduzione di tipo diverso: parleremo della reale applicazione del protocollo nel 2019. Inoltre non stiamo parlando di piccole infrastrutture situate in un cosiddetto garage, ma di Uber, che opera quasi in tutto il mondo. Come gli ingegneri dell'azienda sono giunti alla decisione di utilizzare QUIC in produzione, come hanno eseguito i test e cosa hanno visto dopo averlo implementato in produzione: sotto il taglio.

Le immagini sono cliccabili. Buona lettura!

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni

Uber ha una scala globale, vale a dire 600 città di presenza, in ciascuna delle quali l'applicazione si basa interamente su Internet wireless di oltre 4500 operatori cellulari. Gli utenti si aspettano che l'app non sia solo veloce, ma che sia in tempo reale: per raggiungere questo obiettivo, l'app Uber necessita di una bassa latenza e di una connessione molto affidabile. Ahimè, ma la pila HTTP / 2 non funziona bene nelle reti wireless dinamiche e soggette a perdite. Ci siamo resi conto che in questo caso le basse prestazioni sono direttamente correlate alle implementazioni TCP nei kernel del sistema operativo.

Per risolvere il problema, abbiamo applicato QUIC, un moderno protocollo di multiplexing di canale che ci offre un maggiore controllo sulle prestazioni del protocollo di trasporto. Attualmente il gruppo di lavoro IETF standardizza QUIC come HTTP / 3.

Dopo test approfonditi, abbiamo concluso che l'implementazione di QUIC nella nostra applicazione comporterebbe latenze di coda inferiori rispetto a TCP. Abbiamo osservato una riduzione nell'intervallo del 10-30% per il traffico HTTPS nelle applicazioni per conducenti e passeggeri. QUIC ci ha anche fornito il controllo end-to-end sui pacchetti utente.

In questo articolo condividiamo la nostra esperienza nell'ottimizzazione del TCP per le applicazioni Uber utilizzando uno stack che supporta QUIC.

La tecnologia più recente: TCP

Oggi TCP è il protocollo di trasporto più utilizzato per la distribuzione del traffico HTTPS su Internet. TCP fornisce un flusso affidabile di byte, affrontando così la congestione della rete e le perdite del livello di collegamento. L'uso diffuso di TCP per il traffico HTTPS è dovuto all'ubiquità del primo (quasi tutti i sistemi operativi contengono TCP), alla disponibilità sulla maggior parte delle infrastrutture (come bilanciatori del carico, proxy HTTPS e CDN) e alle funzionalità pronte all'uso disponibili su quasi la maggior parte delle piattaforme e reti.

La maggior parte degli utenti utilizza la nostra app in movimento e le latenze della coda TCP non erano neanche lontanamente vicine alle esigenze del nostro traffico HTTPS in tempo reale. In poche parole, gli utenti di tutto il mondo lo hanno sperimentato: la Figura 1 mostra i ritardi nelle principali città:

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 1: la latenza della coda varia nelle principali città di Uber.

Sebbene la latenza nelle reti indiane e brasiliane fosse superiore a quella degli Stati Uniti e del Regno Unito, la latenza della coda è significativamente superiore alla latenza media. E questo vale anche per gli Stati Uniti e il Regno Unito.

Prestazioni TCP via etere

TCP è stato creato per cablato reti, cioè con un’enfasi su collegamenti altamente prevedibili. Tuttavia, senza fili Le reti hanno le loro caratteristiche e difficoltà. Innanzitutto, le reti wireless sono soggette a perdite dovute a interferenze e attenuazione del segnale. Ad esempio, le reti Wi-Fi sono sensibili alle microonde, al Bluetooth e ad altre onde radio. Le reti cellulari soffrono di perdita di segnale (sentiero perduto) a causa della riflessione/assorbimento del segnale da parte di oggetti ed edifici, nonché da interferenza dal vicino torri cellulari. Ciò porta a risultati più significativi (4-10 volte) e più diversificati Tempo di andata e ritorno (RTT) e perdita di pacchetti rispetto a una connessione cablata.

Per combattere le fluttuazioni e le perdite di larghezza di banda, le reti cellulari utilizzano in genere buffer di grandi dimensioni per i picchi di traffico. Ciò può portare a code eccessive, il che significa ritardi più lunghi. Molto spesso TCP considera questa coda come uno spreco a causa di un timeout prolungato, quindi TCP tende a inoltrare e quindi a riempire il buffer. Questo problema è noto come tampone (buffering di rete eccessivo, buffer bloat), e questo è molto problema serio Internet moderna.

Infine, le prestazioni della rete cellulare variano in base all'operatore, alla regione e all'ora. Nella Figura 2, abbiamo raccolto i ritardi medi del traffico HTTPS tra celle entro un intervallo di 2 chilometri. Dati raccolti per due importanti operatori cellulari a Delhi, in India. Come puoi vedere, le prestazioni variano da cella a cella. Inoltre, la produttività di un operatore differisce dalla produttività del secondo. Ciò è influenzato da fattori quali i modelli di accesso alla rete che tengono conto del tempo e della posizione, la mobilità degli utenti, nonché l’infrastruttura di rete che tiene conto della densità delle torri e il rapporto tra i tipi di rete (LTE, 3G, ecc.).

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 2. Ritardi utilizzando come esempio un raggio di 2 km. Delhi, India.

Inoltre, le prestazioni delle reti cellulari variano nel tempo. La Figura 3 mostra la latenza mediana per giorno della settimana. Abbiamo osservato differenze anche su scala più piccola, nell’arco di un solo giorno e di un’ora.

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 3. I ritardi di coda possono variare in modo significativo da un giorno all'altro, ma per lo stesso operatore.

Tutto quanto sopra fa sì che le prestazioni TCP siano inefficaci nelle reti wireless. Tuttavia, prima di cercare alternative al TCP, abbiamo voluto sviluppare una comprensione precisa sui seguenti punti:

  • TCP è il principale colpevole delle latenze di coda nelle nostre applicazioni?
  • Le reti moderne presentano ritardi di andata e ritorno (RTT) significativi e vari?
  • Qual è l'impatto di RTT e della perdita sulle prestazioni TCP?

Analisi delle prestazioni TCP

Per comprendere come abbiamo analizzato le prestazioni del TCP, diamo una rapida occhiata al modo in cui TCP trasferisce i dati da un mittente a un destinatario. Innanzitutto, il mittente stabilisce una connessione TCP, eseguendo una procedura a tre stretta di mano: Il mittente invia un pacchetto SYN, attende un pacchetto SYN-ACK dal destinatario, quindi invia un pacchetto ACK. Vengono impiegati un secondo e un terzo passaggio aggiuntivi per creare la connessione TCP. Il destinatario conferma la ricezione di ciascun pacchetto (ACK) per garantire una consegna affidabile.

Se un pacchetto o un ACK viene perso, il mittente ritrasmette dopo un timeout (RTO, timeout di ritrasmissione). L'RTO viene calcolato dinamicamente in base a vari fattori, come il ritardo RTT previsto tra mittente e destinatario.

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 4. Lo scambio di pacchetti su TCP/TLS include un meccanismo di ritrasmissione.

Per determinare le prestazioni del TCP nelle nostre applicazioni, abbiamo monitorato i pacchetti TCP utilizzando tcpdump per una settimana sul traffico di combattimento proveniente dai server edge indiani. Abbiamo quindi analizzato le connessioni TCP utilizzando tcptrace. Inoltre, abbiamo creato un'applicazione Android che invia traffico emulato a un server di prova, imitando il più possibile il traffico reale. Gli smartphone con questa applicazione sono stati distribuiti a diversi dipendenti, che hanno raccolto i registri nell'arco di diversi giorni.

I risultati di entrambi gli esperimenti erano coerenti tra loro. Abbiamo riscontrato latenze RTT elevate; i valori della coda erano quasi 6 volte superiori al valore mediano; la media aritmetica dei ritardi è superiore a 1 secondo. Molte connessioni presentavano perdite, causando la ritrasmissione del 3,5% di tutti i pacchetti da parte del TCP. Nelle aree congestionate come aeroporti e stazioni ferroviarie, abbiamo registrato perdite del 7%. Questi risultati mettono in dubbio la saggezza convenzionale utilizzata nelle reti cellulari circuiti di ritrasmissione avanzati ridurre significativamente le perdite a livello di trasporto. Di seguito sono riportati i risultati dei test dell'applicazione "simulatore":

Metriche di rete
senso

RTT, millisecondi [50%,75%, 95%,99%]
[350, 425, 725, 2300]

Divergenza RTT, secondi
In media ~1,2 s

Perdita di pacchetti su connessioni instabili
Media ~3.5% (7% nelle aree sovraccariche)

Quasi la metà di queste connessioni ha avuto almeno una perdita di pacchetti, la maggior parte dei quali erano pacchetti SYN e SYN-ACK. La maggior parte delle implementazioni TCP utilizzano un valore RTO di 1 secondo per i pacchetti SYN, che aumenta esponenzialmente per le perdite successive. I tempi di caricamento dell'applicazione potrebbero aumentare a causa del fatto che TCP impiega più tempo per stabilire le connessioni.

Nel caso dei pacchetti di dati, valori RTO elevati riducono notevolmente l'utilizzo utile della rete in presenza di perdite transitorie nelle reti wireless. Abbiamo scoperto che il tempo medio di ritrasmissione è di circa 1 secondo con un ritardo di coda di quasi 30 secondi. Queste latenze elevate a livello TCP hanno causato timeout e nuove richieste HTTPS, aumentando ulteriormente la latenza e l'inefficienza della rete.

Mentre il 75° percentile dell’RTT misurato era di circa 425 ms, il 75° percentile del TCP era di quasi 3 secondi. Ciò suggerisce che la perdita ha fatto sì che TCP richiedesse 7-10 passaggi per trasmettere con successo i dati. Ciò potrebbe essere una conseguenza di un calcolo RTO inefficiente e dell'incapacità di TCP di rispondere rapidamente alle perdite ultimi pacchetti nella finestra e l'inefficienza dell'algoritmo di controllo della congestione, che non distingue tra perdite wireless e perdite dovute alla congestione della rete. Di seguito sono riportati i risultati dei test di perdita TCP:

Statistiche sulla perdita di pacchetti TCP
Valore

Percentuale di connessioni con almeno 1 pacchetto perso
45%

Percentuale di connessioni con perdite durante la configurazione della connessione
30%

Percentuale di connessioni con perdite durante lo scambio dati
76%

Distribuzione dei ritardi nella ritrasmissione, secondi [50%, 75%, 95%,99%] [1, 2.8, 15, 28]

Distribuzione del numero di ritrasmissioni per un pacchetto o segmento TCP
,

Applicazione di QUIC

Originariamente sviluppato da Google, QUIC è un protocollo di trasporto moderno multi-thread che funziona su UDP. Attualmente QUIC è presente processo di standardizzazione (abbiamo già scritto che esistono, per così dire, due versioni di QUIC, curioso può seguire il collegamento – ca. traduttore). Come mostrato nella Figura 5, QUIC è posizionato sotto HTTP/3 (in effetti, HTTP/2 sopra QUIC è HTTP/3, che ora viene ampiamente standardizzato). Sostituisce parzialmente i livelli HTTPS e TCP utilizzando UDP per formare i pacchetti. QUIC supporta solo il trasferimento sicuro dei dati poiché TLS è completamente integrato in QUIC.

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 5: QUIC viene eseguito con HTTP/3, in sostituzione di TLS, che in precedenza veniva eseguito con HTTP/2.

Di seguito sono riportati i motivi che ci hanno convinto ad utilizzare QUIC per l'amplificazione TCP:

  • Creazione della connessione 0-RTT. QUIC consente il riutilizzo delle autorizzazioni di connessioni precedenti, riducendo il numero di handshake di sicurezza. In futuro TLS1.3 supporterà 0-RTT, ma sarà comunque richiesto un handshake TCP a tre vie.
  • superare il blocco HoL. HTTP/2 utilizza una connessione TCP per client per migliorare le prestazioni, ma ciò può portare al blocco HoL (head of line). QUIC semplifica il multiplexing e fornisce le richieste all'applicazione in modo indipendente.
  • controllo della congestione. QUIC risiede a livello di applicazione, facilitando l'aggiornamento dell'algoritmo di trasporto principale che controlla l'invio in base ai parametri di rete (numero di perdite o RTT). La maggior parte delle implementazioni TCP utilizzano l'algoritmo CUBO, che non è ottimale per il traffico sensibile alla latenza. Algoritmi sviluppati di recente come BBR, modellare in modo più accurato la rete e ottimizzare la latenza. QUIC consente di utilizzare BBR e aggiornare questo algoritmo man mano che viene utilizzato. miglioramento.
  • rifornimento delle perdite. QUIC chiama due TLP (sonda per la perdita della coda) prima che venga attivato l'RTO, anche quando le perdite sono molto evidenti. Questo è diverso dalle implementazioni TCP. TLP ritrasmette principalmente l'ultimo pacchetto (o quello nuovo, se presente) per attivare un rapido rifornimento. La gestione dei ritardi di coda è particolarmente utile per il modo in cui Uber gestisce la propria rete, in particolare per trasferimenti di dati brevi, sporadici e sensibili alla latenza.
  • ACK ottimizzato. Poiché ogni pacchetto ha un numero di sequenza univoco, non ci sono problemi distinzioni pacchetti quando vengono ritrasmessi. I pacchetti ACK contengono anche il tempo necessario per elaborare il pacchetto e generare un ACK sul lato client. Queste funzionalità garantiscono che QUIC calcoli l'RTT in modo più accurato. ACK in QUIC supporta fino a 256 bande NACK, aiutando il mittente a essere più resistente allo spostamento dei pacchetti e a utilizzare meno byte nel processo. ACK selettivo (SACCO) in TCP non risolve questo problema in tutti i casi.
  • migrazione della connessione. Le connessioni QUIC sono identificate da un ID a 64 bit, quindi se un client cambia indirizzo IP, il vecchio ID di connessione può continuare a essere utilizzato sul nuovo indirizzo IP senza interruzioni. Questa è una pratica molto comune per le applicazioni mobili in cui l'utente passa dalla connessione Wi-Fi a quella cellulare.

Alternative al QUIC

Abbiamo considerato approcci alternativi per risolvere il problema prima di scegliere QUIC.

La prima cosa che abbiamo provato è stata quella di implementare PoP (punti di presenza) TPC per terminare le connessioni TCP più vicino agli utenti. In sostanza, i PoP terminano una connessione TCP con un dispositivo mobile più vicino alla rete cellulare e inoltrano il traffico all'infrastruttura originale. Terminando il TCP più vicino, possiamo potenzialmente ridurre l'RTT e garantire che il TCP sia più reattivo a un ambiente wireless dinamico. Tuttavia, i nostri esperimenti hanno dimostrato che la maggior parte dell'RTT e delle perdite provengono dalle reti cellulari e l'uso dei PoP non fornisce un miglioramento significativo delle prestazioni.

Abbiamo anche esaminato l'ottimizzazione dei parametri TCP. Configurare uno stack TCP sui nostri server periferici eterogenei è stato difficile perché TCP ha implementazioni disparate nelle diverse versioni del sistema operativo. È stato difficile implementarlo e testare diverse configurazioni di rete. Non è stato possibile configurare TCP direttamente sui dispositivi mobili a causa della mancanza di autorizzazioni. Ancora più importante, funzionalità come le connessioni 0-RTT e la migliore previsione RTT sono fondamentali per l'architettura del protocollo e pertanto è impossibile ottenere vantaggi significativi ottimizzando solo il TCP.

Infine, abbiamo valutato diversi protocolli basati su UDP che risolvono i problemi dello streaming video: volevamo vedere se questi protocolli sarebbero stati utili nel nostro caso. Sfortunatamente, erano gravemente carenti in molte impostazioni di sicurezza e richiedevano anche una connessione TCP aggiuntiva per i metadati e le informazioni di controllo.

La nostra ricerca ha dimostrato che QUIC è forse l'unico protocollo in grado di risolvere il problema del traffico Internet, tenendo conto sia della sicurezza che delle prestazioni.

Integrazione di QUIC nella piattaforma

Per incorporare con successo QUIC e migliorare le prestazioni delle applicazioni in ambienti con scarsa connettività, abbiamo sostituito il vecchio stack (HTTP/2 su TLS/TCP) con il protocollo QUIC. Abbiamo utilizzato la libreria di rete Cronetto di Progetti di cromo, che contiene la versione originale del protocollo Google: gQUIC. Anche questa implementazione viene costantemente migliorata per seguire le ultime specifiche IETF.

Per prima cosa abbiamo integrato Cronet nelle nostre app Android per aggiungere il supporto per QUIC. L’integrazione è stata effettuata in modo tale da ridurre il più possibile i costi di migrazione. Invece di sostituire completamente il vecchio stack di rete che utilizzava la libreria OkHttp, abbiamo integrato Cronet SOTTO il framework API OkHttp. Eseguendo l'integrazione in questo modo, abbiamo evitato modifiche alle nostre chiamate di rete (utilizzate da Retrofit) a livello API.

Analogamente all'approccio per i dispositivi Android, abbiamo implementato Cronet nelle app Uber su iOS, intercettando il traffico HTTP dalla rete APIutilizzando Protocollo NSURL. Questa astrazione, fornita dalla iOS Foundation, gestisce i dati URL specifici del protocollo e garantisce che possiamo integrare Cronet nelle nostre applicazioni iOS senza costi di migrazione significativi.

Completamento del QUIC sui bilanciatori Google Cloud

Sul lato backend, il completamento QUIC è fornito dall'infrastruttura di bilanciamento del carico di Google Cloud, che utilizza alt-svc intestazioni nelle risposte per supportare QUIC. In generale, il bilanciatore aggiunge un'intestazione alt-svc a ciascuna richiesta HTTP e questo convalida già il supporto QUIC per il dominio. Quando un client Cronet riceve una risposta HTTP con questa intestazione, utilizza QUIC per le successive richieste HTTP a quel dominio. Una volta che il bilanciatore completa il QUIC, la nostra infrastruttura invia esplicitamente questa azione tramite HTTP2/TCP ai nostri data center.

Prestazioni: risultati

Le prestazioni di output sono il motivo principale della nostra ricerca di un protocollo migliore. Per cominciare abbiamo realizzato uno stand con emulazione di reteper scoprire come si comporterà QUIC sotto diversi profili di rete. Per testare le prestazioni di QUIC sulle reti del mondo reale, abbiamo eseguito esperimenti mentre guidavamo per Nuova Delhi utilizzando un traffico di rete emulato molto simile alle chiamate HTTP nell'app passeggeri.

esperimento 1

Attrezzatura per l'esperimento:

  • testare i dispositivi Android con gli stack OkHttp e Cronet per garantire di consentire il traffico HTTPS rispettivamente su TCP e QUIC;
  • un server di emulazione basato su Java che invia lo stesso tipo di intestazioni HTTPS nelle risposte e carica i dispositivi client per ricevere richieste da essi;
  • proxy cloud che si trovano fisicamente vicino all'India per terminare le connessioni TCP e QUIC. Mentre per la terminazione TCP abbiamo utilizzato un proxy inverso su Nginx, è stato difficile trovare un proxy inverso open source per QUIC. Abbiamo creato noi stessi un proxy inverso per QUIC utilizzando lo stack QUIC di base di Chromium e hanno pubblicato in Chromium come open source.

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioniIl protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 6. La suite di test su strada TCP vs QUIC era composta da dispositivi Android con OkHttp e Cronet, proxy cloud per terminare le connessioni e un server di emulazione.

esperimento 2

Quando Google ha reso disponibile QUIC con Bilanciamento del carico di Google Cloud, abbiamo utilizzato lo stesso inventario, ma con una modifica: invece di NGINX, abbiamo utilizzato i bilanciatori del carico di Google per terminare le connessioni TCP e QUIC dai dispositivi, nonché per instradare il traffico HTTPS al server di emulazione. I bilanciatori sono distribuiti in tutto il mondo, ma utilizzano il server PoP più vicino al dispositivo (grazie alla geolocalizzazione).

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 7. Nel secondo esperimento, volevamo confrontare la latenza di completamento di TCP e QUIC: utilizzando Google Cloud e utilizzando il nostro proxy cloud.

Di conseguenza, ci aspettavano diverse rivelazioni:

  • la terminazione tramite PoP ha migliorato le prestazioni TCP. Poiché i bilanciatori terminano le connessioni TCP più vicino agli utenti e sono altamente ottimizzati, ciò si traduce in RTT inferiori, che migliorano le prestazioni TCP. E sebbene QUIC sia stato meno colpito, ha comunque sovraperformato TCP in termini di riduzione della latenza della coda (del 10-30%).
  • le code sono colpite salti di rete. Sebbene il nostro proxy QUIC fosse più lontano dai dispositivi (circa 50 ms di latenza più alta) rispetto ai bilanciatori di carico di Google, ha fornito prestazioni simili: una riduzione del 15% della latenza rispetto a una riduzione del 20% nel 99° percentile per TCP. Ciò suggerisce che la transizione dell’ultimo miglio rappresenta un collo di bottiglia nella rete.

Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioniIl protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 8: I risultati di due esperimenti mostrano che QUIC supera significativamente le prestazioni di TCP.

Combattere il traffico

Ispirati dalla sperimentazione, abbiamo implementato il supporto QUIC nelle nostre applicazioni Android e iOS. Abbiamo condotto test A/B per determinare l'impatto di QUIC nelle città in cui opera Uber. In generale, abbiamo riscontrato una significativa riduzione dei ritardi di coda in entrambe le regioni, operatori di telecomunicazioni e tipologia di rete.

I grafici sottostanti mostrano i miglioramenti percentuali nelle code (95 e 99 percentili) per macroregione e per diverse tipologie di rete: LTE, 3G, 2G.
Il protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioniIl protocollo QUIC in azione: come Uber lo ha implementato per ottimizzare le prestazioni
Figura 9. Nei test di battaglia, QUIC ha sovraperformato TCP in termini di latenza.

Solo in avanti

Forse questo è solo l'inizio: il rilascio di QUIC in produzione ha offerto straordinarie opportunità per migliorare le prestazioni delle applicazioni sia su reti stabili che instabili, ovvero:

Maggiore copertura

Analizzando le prestazioni del protocollo sul traffico reale, abbiamo visto che circa l'80% delle sessioni utilizzava con successo QUIC tutti richieste, mentre il 15% delle sessioni utilizzava una combinazione di QUIC e TCP. Supponiamo che la combinazione sia dovuta al timeout della libreria Cronet su TCP, poiché non è in grado di distinguere tra guasti UDP reali e condizioni di rete scadenti. Stiamo attualmente cercando una soluzione a questo problema mentre lavoriamo alla successiva implementazione di QUIC.

Ottimizzazione QUIC

Il traffico proveniente dalle app mobili è sensibile alla latenza, ma non alla larghezza di banda. Inoltre, le nostre applicazioni vengono utilizzate principalmente su reti cellulari. Sulla base degli esperimenti, le latenze di coda sono ancora elevate anche se si utilizza un proxy per terminare TCP e QUIC vicino agli utenti. Stiamo attivamente cercando modi per migliorare la gestione della congestione e migliorare l’efficienza degli algoritmi di recupero delle perdite QUIC.

Con questi e molti altri miglioramenti, intendiamo migliorare l'esperienza dell'utente indipendentemente dalla rete e dalla regione, rendendo il trasporto dei pacchetti comodo e senza interruzioni più accessibile in tutto il mondo.

Fonte: habr.com

Aggiungi un commento