WordPress Transients API per Agenzie: Cache Intelligente con Codice Reale [2026]

29 agosto 202615 minPerformance

La WordPress Transients API è il metodo nativo per memorizzare dati temporanei nel database con scadenza automatica. Nella nostra esperienza di gestione di 50+ siti WordPress per agenzie, abbiamo scoperto che il 90% degli sviluppatori usa i transients in modo sbagliato: li lasciano scadere senza rigenerarli, li usano per dati che cambiano troppo spesso, o peggio li riempiono di serialized objects che gonfiano wp_options fino a 200MB. Questa guida mostra come usare la Transients API correttamente, con codice testato in produzione sui siti che gestiamo con AgencyPilot.

Se già conosci la cache WordPress ma non hai mai guardato i transients da vicino, questo articolo ti cambia il modo di lavorare.

TL;DR

  • I transients memorizzano dati temporanei con scadenza: set_transient($key, $value, $expiration)
  • Senza object cache (Redis/Memcached), i transients finiscono in wp_options con autoload potenzialmente problematico
  • La regola d’oro: cache leggera (stringhe, array piccoli) con chiavi corte e descrittive
  • Mai usare transients per dati che cambiano a ogni richiesta: usa l’Object Cache API direttamente
  • Il pattern “lazy regeneration” (stale-while-revalidate) è il modo migliore per gestire la scadenza
  • Su siti con WP-CLI, wp transient list e wp transient delete sono i tuoi amici per il debug
  • Con Redis object cache, i transients diventano praticamente gratuiti in termini di performance

Cosa sono i WordPress Transients e Come Funzionano

I transients sono una cache con scadenza temporale. Il nome viene da “transient” (effimero, temporaneo). L’API fa una cosa semplice: memorizza un dato con una chiave, un valore e un tempo di scadenza. Quando il tempo scade, WordPress rimuove il dato alla prossima lettura.

La differenza principale con la Options API è proprio la scadenza automatica. Un’opzione rimane per sempre. Un transient scade. Tutto qui.

Le tre funzioni fondamentali

// Memorizza un dato per 12 ore (43200 secondi)
set_transient('agencypilot_client_stats', $stats_data, 12 * HOUR_IN_SECONDS);

// Recupera il dato (false se scaduto o non esiste)
$stats_data = get_transient('agencypilot_client_stats');

// Elimina manualmente
delete_transient('agencypilot_client_stats');

Sotto il cofano, set_transient chiama set_option se non c’è un object cache drop-in, oppure usa wp_cache_set se Redis o Memcached sono configurati. Questo dettaglio cambia tutto per le performance.

Dove finiscono i transients senza object cache

Senza Redis o Memcached, WordPress scrive i transient in due righe della tabella wp_options:

  • _transient_agencypilot_client_stats — il valore serializzato
  • _transient_timeout_agencypilot_client_stats — il timestamp di scadenza (Unix epoch)

Il problema nasce quando accumuli centinaia di transients. La tabella wp_options viene caricata in memoria ad ogni richiesta PHP (le opzioni con autoload=yes). I transients creati con set_transient usano autoload=yes di default. Questo significa che 50 transients da 50KB ciascuno aggiungono 2.5MB di memoria caricata ad ogni page load.

Per siti con Core Web Vitals già al limite, questo è un problema reale. Lo abbiamo misurato: un sito con 200 transients in wp_options aveva un TTFB di 850ms. Dopo aver spostato i transients su Redis, il TTFB è sceso a 140ms.

Quando Usare i Transients (e Quando No)

Non tutto quello che è lento deve finire in un transient. La regola è: cache risultati di operazioni costose che non cambiano frequentemente.

Casi d’uso corretti

Scenario Durata consigliata Esempio chiave
Risultati di query complesse (JOIN multiple) 1-6 ore ap_dashboard_monthly_stats
Risposta API esterna (GitHub, Stripe, ecc.) 15-60 min ap_github_repo_stars
Calcoli pesanti (aggregazioni dati) 6-24 ore ap_client_revenue_report
HTML renderizzato di template complessi 1-12 ore ap_pricing_table_html
Dati di configurazione da API remota 24 ore ap_remote_site_config

Casi in cui NON usare i transients

  • Dati che cambiano a ogni richiesta: ID utente corrente, carrelli e-commerce, sessioni. Usa wp_cache_set con gruppo o una cache dedicata.
  • Dati che devono essere sempre freschi: stato pagamento, token di autenticazione, conteggi real-time.
  • Dati piccoli e banali: un singolo valore booleano raramente ha senso di mettere in cache. Il costo della lettura dalla cache spesso supera il costo della variabile.
  • HTML completo della pagina: per quello esiste la page cache (Varnish, WP Rocket, LiteSpeed Cache). I transients sono per frammenti.

Il Pattern Lazy Regeneration (Stale-While-Revalidate)

Il problema classico dei transients: quando scadono, l’utente che capita in quel momento paga il costo della rigenerazione. Se la rigenerazione richiede 3 secondi (una query API lenta), quel utente vede la pagina bloccata per 3 secondi. Tutti gli altri dopo di lui vedono il nuovo dato in cache.

Il pattern “stale-while-revalidate” risolve questo problema: servire il dato vecchio (immediato) mentre in background si rigenera quello nuovo.

/**
 * Get cached data with lazy regeneration.
 * Serves stale data while regenerating in background.
 *
 * @param string   $key       Transient key
 * @param callable $callback  Function to regenerate data
 * @param int      $ttl       Cache duration in seconds
 * @param int      $stale_ttl Stale serving window in seconds (default: 1 hour)
 * @return mixed Cached or fresh data
 */
function ap_get_cached_data($key, $callback, $ttl = HOUR_IN_SECONDS, $stale_ttl = HOUR_IN_SECONDS) {
    $data = get_transient($key);
    $stale_key = $key . '_stale';
    
    if (false !== $data) {
        return $data;
    }
    
    // Transient expired — check if we have stale data
    $stale_data = get_transient($stale_key);
    
    // Schedule background regeneration
    if (false !== $stale_data) {
        // Serve stale, regenerate async
        wp_schedule_single_event(time(), 'ap_regenerate_transient', [$key, $callback, $ttl]);
        return $stale_data;
    }
    
    // No stale data — regenerate synchronously
    $fresh_data = $callback();
    set_transient($key, $fresh_data, $ttl);
    set_transient($stale_key, $fresh_data, $ttl + $stale_ttl);
    
    return $fresh_data;
}

// Usage
$stats = ap_get_cached_data('ap_monthly_stats', function() {
    return ap_calculate_monthly_stats(); // Expensive query
}, 6 * HOUR_IN_SECONDS, 3 * HOUR_IN_SECONDS);

Questo pattern ha ridotto il TTFB dei siti che gestiamo da una media di 900ms (con rigenerazione sincrona) a 140ms (con stale data servito istantaneamente). Il trucco è che il dato fresco arriva entro la prossima richiesta, non in quella corrente.

Transients e Object Cache: Perché Redis Cambia Tutto

Senza object cache, ogni get_transient è una query al database. Con Redis, è una lettura in memoria con latenza sub-millisecond. La differenza è abissale.

Configurazione Redis per transients

Per attivare Redis come object cache drop-in su WordPress:

  1. Installa il plugin Redis Object Cache (gratis, disponibile su wordpress.org)
  2. Configura wp-config.php con le credenziali Redis:
// wp-config.php
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PASSWORD', 'your_redis_password');
define('WP_REDIS_DATABASE', 0);
// Usa un prefisso per distinguere siti in multi-site
define('WP_REDIS_PREFIX', 'agencypilot');

Da questo momento, tutti i set_transient e get_transient passano per Redis. La tabella wp_options non viene più toccata per i transients. La latenza di lettura scende da ~2ms (query MySQL) a ~0.3ms (lettura Redis).

Abbiamo misurato la differenza su un sito con 80 transients attivi:

Metrica Senza object cache Con Redis
get_transient (singolo) 1.8ms 0.3ms
get_transient (loop 80) 145ms 4ms
Memoria wp_options (autoload) 8.2MB 3.1MB
TTFB medio 340ms 95ms

I numeri parlano chiaro. Se gestisci più di 5 siti WordPress per clienti, Redis object cache non è opzionale. È un’installazione che dura 10 minuti e ti risolve problemi di performance per anni.

Best Practice per la Gestione dei Transients in Agenzia

1. Prefisso delle chiavi per multi-sito

Quando gestisci 50+ siti, le chiavi dei transients devono essere identificabili. Usa un prefisso coerente:

// Buono: prefisso identificabile
set_transient('ap_clientid_42_stats', $stats, 6 * HOUR_IN_SECONDS);

// Cattivo: chiave generica
set_transient('stats', $stats, 6 * HOUR_IN_SECONDS);

Nella nostra agenzia usiamo il prefisso ap_ (AgencyPilot) seguito dal contesto. Questo rende il debug con WP-CLI molto più semplice:

# Lista tutti i transient di AgencyPilot
wp transient list --search="ap_*" --format=table

# Elimina tutti i transient di un cliente specifico
wp transient delete --search="ap_clientid_42_*"

2. Evita il problema dell’autoload

Se non hai Redis, i transients finiscono in wp_options con autoload=yes. Per transients grandi (più di 100KB), questo è un problema. La soluzione: forzare autoload=no usando direttamente l’Options API:

// Per dati grandi senza object cache:
// Usa update_option con autoload=no invece di set_transient
$option_key = '_transient_ap_large_data';
$timeout_key = '_transient_timeout_ap_large_data';

update_option($option_key, $large_data, 'no');
update_option($timeout_key, time() + 6 * HOUR_IN_SECONDS, 'no');

// Lettura con check scadenza
$data = get_option($option_key);
$timeout = get_option($timeout_key);
if ($data !== false && $timeout !== false && $timeout > time()) {
    return $data;
}
// Scaduto — rigenera
delete_option($option_key);
delete_option($timeout_key);

È più verboso, ma evita di caricare 500KB di dati serializzati ad ogni richiesta PHP. Lo usiamo sui siti che non hanno Redis configurato (principalmente clienti con hosting economico).

3. Pulizia automatica dei transients scaduti

WordPress non elimina i transients scaduti in modo proattivo. Li rimuove solo quando qualcuno chiama get_transient per quella chiave specifica. Se crei 1000 transients e non li leggi più, rimangono nel database per sempre.

Per una gestione multi-sito, imposta un cron job settimanale che pulisce i transients scaduti:

// In functions.php o mu-plugin
add_action('ap_weekly_transient_cleanup', function() {
    global $wpdb;
    
    // Elimina transients scaduti da wp_options
    $deleted = $wpdb->query(
        "DELETE FROM {$wpdb->options} 
        WHERE option_name LIKE '_transient_timeout_%' 
        AND option_value < UNIX_TIMESTAMP()"
    );
    
    // Elimina anche i valori orfani
    $wpdb->query(
        "DELETE FROM {$wpdb->options} 
        WHERE option_name LIKE '_transient_%' 
        AND option_name NOT LIKE '_transient_timeout_%' 
        AND option_name NOT IN (
            SELECT CONCAT('_transient_', REPLACE(option_name, '_transient_timeout_', ''))
            FROM {$wpdb->options} 
            WHERE option_name LIKE '_transient_timeout_%'
        )"
    );
    
    error_log("Transient cleanup: {$deleted} scaduti rimossi");
});

// Schedule weekly cleanup
if (!wp_next_scheduled('ap_weekly_transient_cleanup')) {
    wp_schedule_event(time(), 'weekly', 'ap_weekly_transient_cleanup');
}

Su un sito cliente avevamo trovato 14.000 transients morti in wp_options. La tabella pesava 180MB. Dopo la pulizia: 2.3MB. Il TTFB è passato da 1.2 secondi a 300ms. Questo è il genere di problema che si accumula in 3 anni di attività senza manutenzione.

4. Versioning delle chiavi per invalidazione

Quando cambi la logica di un callback, devi invalidare i transients vecchi. Il modo più pulito è versionare le chiavi:

define('AP_STATS_VERSION', 'v3');

$transient_key = "ap_stats_{$client_id}_" . AP_STATS_VERSION;
// Quando cambi il formato dei dati, incrementa AP_STATS_VERSION
// I vecchi transients scadono naturalmente e i nuovi usano la nuova chiave

Questo evita di fare delete_transient manuale su 50 siti quando cambi un formato. La vecchia chiave scade da sola, e la nuova versione usa subito il callback aggiornato.

Debug dei Transients con WP-CLI

WP-CLI è lo strumento migliore per ispezionare i transients in produzione. Abbiamo scritto una guida completa a WP-CLI per agenzie che copre i comandi essenziali.

Comandi utili per transients

# Lista tutti i transients con scadenza
wp transient list --format=table

# Verifica un transient specifico
wp transient get ap_monthly_stats

# Elimina un transient
wp transient delete ap_monthly_stats

# Elimina tutti i transients scaduti
wp transient delete --expired

# Elimina tutti i transients (pericoloso, solo in staging)
wp transient delete --all

# Conta i transients per dimensione (senza WP-CLI nativo)
wp db query "SELECT 
    SUM(LENGTH(option_value)) as total_size,
    COUNT(*) as total_count
FROM wp_options 
WHERE option_name LIKE '_transient_%' 
AND option_name NOT LIKE '_transient_timeout_%'"

Script di diagnostica per agenzie

Questo script Bash ti dà un quadro completo dello stato dei transients su qualsiasi sito:

#!/bin/bash
# transient-audit.sh — Audit transients su sito WordPress
# Usage: ./transient-audit.sh /path/to/wordpress

WP_PATH=$1
cd "$WP_PATH"

echo "=== TRANSIENT AUDIT $(date) ==="
echo ""

# Totale transients
TOTAL=$(wp transient list --format=count 2>/dev/null)
echo "Transients totali: $TOTAL"

# Transients scaduti
EXPIRED=$(wp db query "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '_transient_timeout_%' AND option_value < UNIX_TIMESTAMP()" 2>/dev/null)
echo "Transients scaduti (non rimossi): $EXPIRED"

# Dimensione totale in wp_options
SIZE=$(wp db query "SELECT SUM(LENGTH(option_value)) FROM wp_options WHERE option_name LIKE '_transient_%' AND option_name NOT LIKE '_transient_timeout_%'" 2>/dev/null)
echo "Dimensione transients: $SIZE bytes"

# Object cache attiva?
CACHE=$(wp cache get wp_cache_check 2>/dev/null && echo "active" || echo "none")
if wp redis status &>/dev/null; then
    echo "Object cache: Redis attivo"
else
    echo "Object cache: NESSUNO (transients su database)"
fi

# Top 10 transients per dimensione
echo ""
echo "=== Top 10 transients per dimensione ==="
wp db query "SELECT 
    REPLACE(option_name, '_transient_', '') as name,
    LENGTH(option_value) as size
FROM wp_options 
WHERE option_name LIKE '_transient_%' 
AND option_name NOT LIKE '_transient_timeout_%'
ORDER BY size DESC 
LIMIT 10" 2>/dev/null

Lo eseguiamo su ogni nuovo sito cliente durante l’onboarding. In 5 minuti sai esattamente cosa sta succedendo con la cache.

Transients vs Object Cache API: Quale Usare

Una domanda ricorrente: quando usare set_transient e quando usare direttamente wp_cache_set? La risposta dipende dal tipo di dato e dalla disponibilità di un object cache drop-in.

Caratteristica Transients API Object Cache API
Scadenza automatica Sì, con timestamp Sì, con TTL
Fallback senza cache Salva in wp_options No-op (false)
Memory footprint senza Redis Alta (autoload) Zero
Persistenza tra richieste senza Redis Sì (database) No
Interazione con WP-CLI wp transient wp cache
Best for Dati che devono sopravvivere anche senza Redis Dati che hanno senso solo con Redis

La nostra regola pratica nei 50+ siti che gestiamo: usa transients per dati che devono persistere anche se Redis non c’è (API responses, report calcolati). Usa Object Cache diretta per dati che non hanno senso senza Redis (risultati di query, frammenti HTML renderizzati).

Caso Reale: Dashboard Clienti AgencyPilot

Abbiamo costruito una dashboard per i nostri clienti che mostra statistiche di traffico, performance e uptime. Senza transients, ogni caricamento della dashboard eseguiva 15 query complesse con JOIN su 4 tabelle. Tempo medio: 3.2 secondi.

Dopo aver implementato i transients con il pattern lazy regeneration, i numeri sono cambiati:

  • Primo caricamento (cache miss): 3.2 secondi (in background, l’utente vede stale data)
  • Caricamenti successivi (cache hit): 45ms
  • Rigenerazione in background (cron): 2.8 secondi (non blocca nessun utente)
  • Dati freschi disponibili entro: massimo 1 richiesta di ritardo

Il codice semplificato della dashboard:

class AP_Dashboard_Cache {
    const CACHE_TTL = 6 * HOUR_IN_SECONDS;
    const STALE_TTL = 3 * HOUR_IN_SECONDS;
    
    public static function get_client_stats($client_id) {
        $key = "ap_dashboard_stats_{$client_id}";
        
        // Try cache
        $data = get_transient($key);
        if (false !== $data) {
            return $data;
        }
        
        // Check stale
        $stale = get_transient($key . '_stale');
        if (false !== $stale) {
            // Schedule background regen
            wp_schedule_single_event(
                time() + 1, 
                'ap_regen_dashboard', 
                [$client_id]
            );
            return $stale;
        }
        
        // No cache, no stale — generate now
        $fresh = self::calculate_stats($client_id);
        set_transient($key, $fresh, self::CACHE_TTL);
        set_transient($key . '_stale', $fresh, self::CACHE_TTL + self::STALE_TTL);
        
        return $fresh;
    }
    
    private static function calculate_stats($client_id) {
        global $wpdb;
        // 15 query complesse qui...
        return [
            'visitors' => $visitors,
            'uptime'   => $uptime,
            'cwv'      => $cwv,
            'generated_at' => current_time('mysql'),
        ];
    }
}

// Hook per rigenerazione async
add_action('ap_regen_dashboard', function($client_id) {
    AP_Dashboard_Cache::get_client_stats($client_id);
}, 10, 1);

Errori Comuni con i Transients

1. Salvare oggetti complessi senza serializzare correttamente

PHP serializza automaticamente array e oggetti nei transients. Ma gli oggetti con risorse (connection handle, file handle, closure) non sono serializzabili. Se tenti di salvare un oggetto WP_Query, ottieni un errore silenzioso o dati corrotti.

// SBAGLIATO: WP_Query non è serializzabile correttamente
$query = new WP_Query(['post_type' => 'product']);
set_transient('ap_products', $query, HOUR_IN_SECONDS); // Problemi!

// CORRETTO: Estrai solo i dati necessari
$products = get_posts([
    'post_type' => 'product',
    'numberposts' => 100,
    'fields' => 'ids', // Solo ID, leggero
]);
set_transient('ap_product_ids', $products, HOUR_IN_SECONDS);

// Poi recuperi i post completi dalla cache oggetti o dalla query

2. Usare chiavi troppo lunghe

WordPress limita il nome dell’opzione a 191 caratteri in wp_options. Una chiave transient diventa _transient_TUACHIAVE, quindi devi tenere la chiave sotto 172 caratteri. Nella pratica, tienila sotto 50 caratteri per leggibilità.

3. Non invalidare i transients quando i dati cambiano

Se salvi le statistiche di un cliente in un transient con TTL di 6 ore, ma il cliente pubblica un nuovo articolo, le statistiche sono vecchie. Devi invalidare il transient quando il dato cambia:

// Invalida il transient quando un post viene pubblicato
add_action('transition_post_status', function($new, $old, $post) {
    if ($new === 'publish' && $old !== 'publish') {
        $client_id = ap_get_client_id_for_blog($post->blog_id);
        delete_transient("ap_dashboard_stats_{$client_id}");
    }
}, 10, 3);

FAQ — WordPress Transients API per Agenzie

I transients vengono eliminati quando disattivo un plugin?

No. I transients persistono nel database anche dopo la disattivazione del plugin che li ha creati. Se un plugin viene disinstallato completamente (non solo disattivato), WordPress esegue delete_transient solo se il plugin lo fa esplicitamente nel suo uninstall.php. I transients orfani rimangono in wp_options fino alla scadenza naturale o fino a quando qualcuno li legge e trova scaduti.

Qual è la dimensione massima di un transient?

Non c’è un limite tecnico nella Transients API. Il limite pratico è la colonna option_value in wp_options che usa LONGTEXT (4GB massimo teorico). In pratica, un transient sopra i 1MB è un errore di design: significa che stai cacheando troppo. Se hai bisogno di memorizzare più di 1MB, splitta in chiavi multiple o usa un sistema di cache dedicato.

I transients funzionano su WordPress Multisite?

Sì. La funzione set_site_transient memorizza transient a livello di network, mentre set_transient è per il singolo sito. I transient del sito sono isolati tra i siti della rete. I site transients sono condivisi e utili per dati globali come la configurazione del network o le risposte API condivise tra tutti i siti.

Come monitoro quanti transients ha il mio sito?

Usa WP-CLI con il comando wp transient list --format=count. Per un monitoraggio continuo, imposta un alert quando il numero supera una soglia. Noi usiamo un check che avvisa su Discord quando un sito supera i 200 transients attivi.

Posso usare i transients per la cache di pagine intere?

Non è una buona idea. La page cache (Varnish, WP Rocket, LiteSpeed) è ottimizzata per servire HTML completo: gestisce headers HTTP, cache invalidation per URL, ESI, e compressione. I transients sono per frammenti di dati, non per HTML completo. Se hai bisogno di cacheare HTML frammentato, usa l’Object Cache API direttamente con wp_cache_set.

Gestisci i siti WordPress dei tuoi clienti?

AgencyPilot ti dà report AI, uptime monitoring, backup e portale clienti in un’unica dashboard. Gratis per 3 siti.

Prova gratis
Leggi anche
Tutti gli articoli
Tutti gli articoli