Quando il debito tecnico ti presenta il conto: Ory Keto, open source e Copilot 🧾🧠

Quando il debito tecnico ti presenta il conto: Ory Keto, open source e Copilot 🧾🧠

23 novembre 2025·Sandro Lain
Sandro Lain

Quando il debito tecnico ti presenta il conto

Quando il debito tecnico è silenzioso fa più paura di quando esplode. Di solito lo immaginiamo come una metafora vaga, qualcosa che “un giorno sistemeremo”. Poi arriva la realtà: timeout, errori a cascata, clienti infastiditi e un grafico di latenza che sembra l’ECG di qualcuno che sta correndo la maratona con lo zaino pieno di sassi.

Questa è la storia di un servizio cloud in crescita, di un pezzo critico dell’architettura rimasto fermo al 2020, di qualche scelta infelice nel database… e di come open source + martello + Copilot possano trasformare un disastro annunciato in un sistema tutto sommato dignitoso. 😄


Il debito tecnico non è (solo) una parola alla moda 💸

Quando si parla di debito tecnico spesso sembra una parola generica: tutti annuiscono, pochi la prendono davvero sul serio finché non tocca al proprio servizio finire sotto i riflettori.

Nel nostro caso il problema stava nella parte di autorizzazione all’accesso alle API, costruita attorno alla triade di Ory:

  • Kratos per l’autenticazione
  • Keto per l’autorizzazione
  • Oathkeeper come access proxy

Kratos e Oathkeeper erano stati aggiornati alle ultime versioni compatibili, relativamente in forma. Keto invece era rimasto indietro, alla gloriosa versione 0.5.7 del 12 ottobre 2020, abbandonata in un angolo come un vecchio microservizio di cui nessuno vuole più assumersi la responsabilità.

Perché? Perché dalla 0.6 in poi hanno cambiato completamente architettura e modello dati, passando da un sistema roles/policy based a uno Zanzibar-based. Non un semplice upgrade, ma un cambio di paradigma.

Teoricamente avremmo potuto:

  1. Seguire la documentazione di migrazione, più vicina a un “arrangiati” che a una guida vera e propria (peraltro con un ulteriore breaking change in 0.7, quindi doppia migrazione).
  2. Valutare una migrazione diretta verso qualcos’altro (ad esempio un sistema tipo SpiceDB), ma con un impatto enorme sull’integrazione esistente.

Peccato che, come spesso accade, la teoria si scontri con una realtà molto semplice: questo servizio era già profondamente integrato in vari microservizi, e nessuno era particolarmente eccitato all’idea di riscrivere mezzo perimetro di autorizzazione alla cieca.

Nel frattempo, i sintomi peggioravano.


I sintomi: timeout, lentezza e nessuna via di fuga elegante 🐢⏱️

Il problema principale era chiaro: il servizio non riusciva a scalare, né in termini di numero di policy in database, né rispetto al volume delle richieste.

I segnali erano sempre quelli, fin troppo familiari:

  • timeout ricorrenti nelle richieste HTTP da Oathkeeper verso Keto
  • di conseguenza, errori nelle API verso i client
  • impossibilità di scalare semplicemente “aggiungendo pod”, perché la radice non era a livello di CPU ma di accesso al dato

In altre parole: potevamo mettere tutte le repliche del mondo, ma quando ogni richiesta passa da una query fatta male, il collo di bottiglia si sposta solo di qualche millisecondo, non scompare.

E qui arriva il bivio.


Le opzioni sul tavolo: migrare, riscrivere o prendere il martello 🔨

Da un lato avevamo l’architettura “giusta” sulla carta, dall’altro una realtà di integrazioni profonde, tempi limitati e rischio architetturale non banale.

In pratica, le opzioni erano:

  1. Aggiornare Ory Keto all’ultima versione disponibile, affrontando due migrazioni di database e convertendo tutte le policy da un modello basato sui ruoli a uno basato sulle relazioni, senza sapere davvero che impatto potesse avere sulle autorizzazioni in produzione.
  2. Migrare a un altro servizio di autorizzazione, reintegrando tutte le API e sperando che l’integrazione con Oathkeeper non trasformasse il progetto in una riscrittura mascherata.
  3. Prendere a martellate Keto finché non avesse raggiunto un livello di performance accettabile, limitando al minimo l’impatto sull’architettura esistente.

Abbiamo scelto la 3. Non perché fosse “la più elegante”, ma perché era l’unica che rispettava questi vincoli:

  • non rompere i contratti API esistenti
  • non introdurre un nuovo componente concettuale nel disegno
  • ridurre il rischio, non aumentarlo

Insomma: prima lo salviamo, poi decidiamo se merita la pensione anticipata.


Il fork, il branch e Copilot come lente d’ingrandimento 🧬🤖

Bilancia concettuale tra debito tecnico e ottimizzazione

Debito tecnico vs ottimizzazione

Per prima cosa abbiamo creato un fork interno del repository di Keto, partendo dal tag 0.5.7 e aprendo un branch dedicato alle nostre modifiche.

Qui entra in scena Copilot, usato con un modello di fascia alta per fare una prima analisi mirata dell’implementazione dell’API di validazione dell’autorizzazione.

Non gli abbiamo chiesto miracoli né refactoring a caso: gli abbiamo fatto leggere il flusso, evidenziare i punti caldi, suggerire dove concentrarci. In pratica, lo abbiamo usato come uno junior iper-veloce bravo a:

  • navigare velocemente tra file e funzioni
  • riassumere parti di logica
  • mettere in evidenza pattern sospetti

La parte interessante? La codebase era datata e i problemi, con il senno di poi, erano quelli classici che ci si aspetta in un servizio nato in fretta e mai davvero ripensato per la scala.

Quello che abbiamo trovato, però, meritava un facepalm collettivo.


Cosa abbiamo trovato: query pigre, JSON sbagliati e zero indici 🐘📉

La problematica principale era nel modo in cui veniva effettuata la query al database (PostgreSQL) per validare una richiesta rispetto alle policy configurate.

In pratica:

  • ad ogni richiesta venivano letti tutti i record delle policy dal database
  • nessuna cache
  • nessun uso intelligente di indici

Tradotto: ogni chiamata di autorizzazione era un mini full scan. In un servizio di test può anche funzionare. In produzione, con policy reali e traffico reale, è un ottimo modo per far arrabbiare tutti.

Giusto per non farci mancare nulla, Copilot ci ha aiutato a confermare altre due chicche:

  • nessun indice sulle colonne rilevanti (del resto, perché usarli se tanto leggevano tutto 🙃)
  • i dati erano in una colonna JSON invece che JSONB, rendendo meno efficienti varie operazioni

Qui Copilot è stato utile non per l’idea in sé (le soluzioni erano abbastanza ovvie), ma per:

  • trovare rapidamente tutti i punti in cui la stessa logica veniva replicata
  • confrontare l’implementazione “teorica” con quella reale
  • generare rapidamente la bozza delle modifiche da rifinire a mano

Le principali leve di miglioramento erano chiare:

  • query ottimizzate che leggessero solo i dati necessari
  • aggiunta di indici mirati
  • introduzione di una cache per le richieste più frequenti

Da qui, è partita la parte divertente.


Test reali, non solo teoria: confronto tra versione originale e versione ottimizzata 🧪📊

Ogni modifica che tocca l’autorizzazione non è mai banale: se sbagli, non è un 500 qualsiasi, è gente che vede accessi negati (o peggio, concessi quando non dovrebbe).

Per questo abbiamo deciso di:

  • affiancare all’implementazione originale quella ottimizzata
  • usare un database reale in test
  • confrontare sia correttezza del risultato che performance

In pratica abbiamo fatto generare a Copilot la struttura di una suite di test di integrazione che:

  • avviava un database reale (ad esempio con container temporanei)
  • applicava schema e dati equivalenti a quelli di produzione (regole, policy, dataset realistico)
  • eseguiva le stesse chiamate di autorizzazione su implementazione originale e nuova implementazione
  • verificava sia l’equivalenza delle risposte che le differenze di tempi di esecuzione

Questo approccio ci ha permesso di:

  • essere relativamente tranquilli sulla correttezza
  • misurare in modo ripetibile l’effetto di ogni cambio
  • iterare velocemente sulle query finché non trovavamo il punto di equilibrio tra complessità ed efficacia

Il bello di avere open source + Copilot è proprio questo: puoi leggere tutto, cambiare tutto, e far sì che l’AI ti aiuti sia a capire che a validare, senza trattare il codice come una scatola nera.


Aggiungere la cache: Redis, rete e la sorpresa dell’in-memory ⚙️🧊

Dopo aver sistemato query e indici, il collo di bottiglia si era già ridotto drasticamente. Ma dal momento che il servizio era fortemente read-heavy, aveva senso provare a spingerci un po’ oltre.

Prima idea: usare Redis come cache condivisa, anche perché il servizio aveva già diverse repliche (nel tentativo, fino a quel momento poco felice, di scalare in orizzontale).

Sulla carta tutto bene. Nella pratica:

  • il miglioramento c’è stato, ma inferiore alle aspettative
  • la latenza di rete verso Redis annullava buona parte del beneficio
  • i guadagni si fermavano tra 1,5x e 2x nelle iterazioni al secondo

A quel punto abbiamo provato una strada più semplice: cache in-memory, con una libreria ottimizzata, limitando opportunamente il TTL (5 minuti) e la dimensione della cache per evitare derive.

Il risultato? Decisamente più divertente.


I numeri: da tartaruga a qualcosa di ragionevole 📈🚀

Combinando gli interventi abbiamo ottenuto, in sintesi:

  • tra 10x e 30x di aumento di performance grazie alle query ottimizzate e agli indici sul database
  • su quella base, un ulteriore 10x–20x sulle richieste coperte da cache in-memory
  • con Redis, come detto, il guadagno si fermava intorno al 2x

Risultato finale lato servizio:

  • timeout praticamente azzerati
  • nessun cambiamento architetturale visibile dall’esterno
  • nessuna modifica ai contratti API dei microservizi

Il tutto in circa 12 ore lavorative effettive tra analisi, prove, test, rifiniture e qualche discussione su cosa fosse “accettabile” portare in produzione.

Sì, in tutto questo Copilot ha dato una mano importante: non ha “magicamente risolto il problema”, ma ha accelerato ogni fase ripetitiva, dalla navigazione nel codice alla generazione di test e boilerplate. Meno tempo a litigare con i dettagli, più tempo a ragionare sui trade-off.


Sicurezza, dipendenze e un po’ di manutenzione ordinaria 🧹🛡️

Già che avevamo il paziente sul tavolo operatorio, sarebbe stato un peccato richiuderlo senza un checkup completo.

Abbiamo quindi:

  • aggiornato alle ultime versioni compatibili le dipendenze
  • compilato con una versione più recente di Go, allineata agli standard attuali
  • sistemato alcune issue di sicurezza note, emerse dalle advisory e dagli strumenti di analisi

Questa parte è meno spettacolare, ma spesso è quella che fa la differenza sulla vita utile residua di un servizio legacy. E sì, anche qui Copilot è utile per:

  • spiegare rapidamente cambi di API nelle librerie
  • suggerire adattamenti minimi
  • evitare di perdersi nei dettagli delle breaking change

Non è un sostituto della testa, ma è un ottimo acceleratore per chi la testa la vuole usare su problemi più grandi.


Cosa ci insegna questa storia: open source, AI e responsabilità 🧩🤝

Alla fine di tutto, cosa resta oltre ai grafici più belli e ai clienti più tranquilli?

Qualche lezione abbastanza chiara:

  1. Il debito tecnico non è astratto: prima o poi diventa timeout, pagine lente, clienti arrabbiati e sviluppatori frustrati.
  2. L’open source è un superpotere, se lo tratti come tale: puoi fare fork, correggere, ottimizzare, contribuire indietro. Non sei ostaggio di una black box.
  3. Copilot (e in generale gli LLM) sono moltiplicatori, non sostituti: ti aiutano a capire più in fretta, a sperimentare di più, a scrivere test meglio strutturati. Ma le decisioni, i compromessi e la responsabilità restano umani.
  4. Non sempre la soluzione giusta è “riscrivere tutto”: a volte il compromesso migliore è far respirare il sistema esistente, guadagnare spazio di manovra e solo dopo decidere se e come passare a un nuovo paradigma.

E soprattutto: migliorare un servizio “vecchio” non è una sconfitta rispetto a riscriverlo da zero. È spesso la scelta più adulta.


Il vantaggio (vero) dell’open source 🔓🌍

Questa storia sarebbe stata molto diversa se Keto fosse stato un prodotto chiuso e opaco. In quel caso, le nostre opzioni sarebbero state più o meno:

  • aprire ticket di supporto
  • aspettare una nuova release
  • sperare che il roadmap del vendor fosse allineato ai nostri bisogni

Con l’open source, invece, abbiamo potuto:

  • ispezionare il codice e capire cosa stesse succedendo davvero
  • intervenire in autonomia sui punti critici
  • modellare il comportamento del servizio sulle nostre esigenze reali

Lato etico e comunitario, c’è anche un altro punto: quando investi tempo per migliorare un progetto open source, stai restituendo valore non solo al tuo contesto, ma a chiunque condivida lo stesso problema. Anche se il tuo fork resta interno, le conoscenze acquisite possono tradursi in bug report migliori, discussioni tecniche più precise, o perfino in future pull request.

In un mondo dove parliamo spesso di developer experience, platform engineering e AI everywhere, ogni tanto fa bene ricordarsi una cosa semplice:

il vero vantaggio dell’open source è la libertà di non essere spettatori passivi dei problemi, ma partecipanti attivi alle soluzioni.

E se lungo la strada puoi sfruttare strumenti come Copilot per arrivare prima al punto, con il caffè ancora caldo, tanto meglio. ☕😉

Ultimo aggiornamento il