Transient API ile WordPress Veri Önbellekleme
Bir WordPress sitesinde “neden bu kadar yavaş?” sorusunu araştırırken veritabanı sorgularının her sayfa yüklemesinde tekrar tekrar çalıştığını fark etmek, sysadmin’in klasik “ah ha” anlarından biridir. WooCommerce mağazası olan bir müşteride tam olarak bunu yaşadım: ürün kategorilerini listeleyen basit bir widget, her istekte 12 ayrı sorgu çalıştırıyordu. Çözüm Transient API’ydi ve bu yazıda hem temel kavramları hem de gerçek üretim senaryolarında nasıl kullandığımı paylaşacağım.
Transient API Nedir ve Neden Önemlidir?
WordPress Transient API, geçici verileri anahtar-değer çiftleri olarak belirli bir süreyle saklamanıza olanak tanıyan yerleşik bir önbellekleme mekanizmasıdır. İsmi biraz yanıltıcı olabilir; “transient” kelimesi geçici anlamına gelir ama bu verilerin önemsiz olduğu anlamına gelmez. Tam aksine, yeniden hesaplanması maliyetli olan verileri bir kez hesaplayıp saklayarak sonraki isteklerde hızlıca sunmak için tasarlanmıştır.
Teknik olarak baktığımızda, varsayılan WordPress kurulumunda transient’lar wp_options tablosunda saklanır. Ancak Redis veya Memcached gibi harici bir object cache kurduğunuzda, WordPress otomatik olarak bu verileri bellek tabanlı depolama alanına taşır. Bu ayrım son derece kritik ve sıklıkla göz ardı edilir.
Üç temel fonksiyon vardır:
- set_transient($key, $value, $expiration): Veriyi belirtilen süreyle saklar
- get_transient($key): Saklanan veriyi getirir, süresi dolmuşsa
falsedöner - delete_transient($key): Transient’ı manuel olarak siler
Temel Kullanım Örneği
En basit kullanım senaryosundan başlayalım. Diyelim ki harici bir API’den döviz kurları çekiyorsunuz:
<?php
function get_exchange_rates() {
$cache_key = 'doviz_kurlari_v1';
// Önce cache'e bak
$cached_data = get_transient( $cache_key );
if ( false !== $cached_data ) {
return $cached_data;
}
// Cache'de yoksa API'den çek
$response = wp_remote_get( 'https://api.exchangerates.example.com/latest' );
if ( is_wp_error( $response ) ) {
return false;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
// 1 saat (3600 saniye) süreyle sakla
set_transient( $cache_key, $data, 3600 );
return $data;
}
Burada dikkat edilmesi gereken nokta: get_transient() fonksiyonu veri bulamazsa false döner. Bu nedenle karşılaştırma yaparken !== false kullanmak zorundasınız. Sıfır veya boş string gibi değerleri saklıyor olabilirsiniz ve if ($cached_data) kontrolü bunları yanlış değerlendirir.
WooCommerce Gerçek Dünya Senaryosu
Müşterideki asıl probleme dönelim. En çok satan 10 ürünü gösteren bir widget vardı ve her yüklemede WooCommerce’in pahalı meta sorgularını çalıştırıyordu. Şöyle bir yapı geliştirdik:
<?php
function get_bestseller_products( $limit = 10 ) {
$transient_key = 'wc_bestsellers_' . $limit . '_v2';
$products = get_transient( $transient_key );
if ( false !== $products ) {
return $products;
}
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => $limit,
'meta_key' => 'total_sales',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'meta_query' => array(
array(
'key' => '_stock_status',
'value' => 'instock',
'compare' => '=',
),
),
);
$product_query = new WP_Query( $query_args );
$products = array();
if ( $product_query->have_posts() ) {
while ( $product_query->have_posts() ) {
$product_query->the_post();
$product = wc_get_product( get_the_ID() );
$products[] = array(
'id' => $product->get_id(),
'name' => $product->get_name(),
'price' => $product->get_price(),
'sales' => $product->get_total_sales(),
'url' => get_permalink(),
);
}
wp_reset_postdata();
}
// 6 saat sakla
set_transient( $transient_key, $products, 6 * HOUR_IN_SECONDS );
return $products;
}
Bu kod sayesinde sorgu sayısını her istekte 12’den, 6 saatte bir kereye indirdik. Sayfa yüklenme süresi 2.3 saniyeden 0.4 saniyeye düştü.
Cache Invalidation: En Zorlu Kısım
“Bilgisayar biliminde iki zor şey vardır: cache invalidation, şeyleri isimlendirme ve off-by-one hataları.” Bu espri bir yana, transient yönetiminde en kritik mesele ne zaman ve nasıl geçersiz kılacağınızı bilmektir.
WooCommerce ürün güncellendiğinde bestseller cache’ini temizlemek için:
<?php
// Ürün güncellendiğinde cache'i temizle
add_action( 'woocommerce_update_product', 'invalidate_bestseller_cache' );
add_action( 'woocommerce_new_product', 'invalidate_bestseller_cache' );
function invalidate_bestseller_cache( $product_id ) {
// Farklı limit değerleri için oluşturulmuş tüm transient'ları sil
$limits = array( 5, 10, 15, 20 );
foreach ( $limits as $limit ) {
delete_transient( 'wc_bestsellers_' . $limit . '_v2' );
}
// Genel ürün listesi cache'lerini de temizle
delete_transient( 'product_category_counts' );
delete_transient( 'featured_products_sidebar' );
}
// Sipariş tamamlandığında satış sayıları değiştiği için cache'i yenile
add_action( 'woocommerce_order_status_completed', 'invalidate_sales_cache' );
function invalidate_sales_cache( $order_id ) {
$limits = array( 5, 10, 15, 20 );
foreach ( $limits as $limit ) {
delete_transient( 'wc_bestsellers_' . $limit . '_v2' );
}
}
Transient Anahtarı Yönetimi: Versiyon Stratejisi
Transient key’lerine versiyon numarası eklemek (örneğin _v2) basit ama çok etkili bir yaklaşım. Kod değişikliği yapıp eski cache’in temiz olduğundan emin olmak istediğinizde key’deki versiyonu artırmanız yeterli. Eski versiyon yavaş yavaş kendi kendine expire olur.
Daha sofistike bir yaklaşım için dinamik versiyon kullanabilirsiniz:
<?php
function get_cache_version( $group = 'default' ) {
$version_key = 'cache_version_' . $group;
$version = get_option( $version_key, 1 );
return $version;
}
function increment_cache_version( $group = 'default' ) {
$version_key = 'cache_version_' . $group;
$current = get_option( $version_key, 1 );
update_option( $version_key, $current + 1, false );
}
function get_versioned_transient( $key, $group = 'default' ) {
$version = get_cache_version( $group );
$versioned_key = $key . '_v' . $version;
return get_transient( $versioned_key );
}
function set_versioned_transient( $key, $value, $expiration, $group = 'default' ) {
$version = get_cache_version( $group );
$versioned_key = $key . '_v' . $version;
return set_transient( $versioned_key, $value, $expiration );
}
// Tüm ürün cache'ini tek seferde geçersiz kılmak için:
// increment_cache_version( 'products' );
Bu yaklaşımla ilgili grubun tüm cache’ini tek bir increment_cache_version() çağrısıyla geçersiz kılabilirsiniz.
Kullanıcıya Özel Transient’lar
Bazı durumlarda kullanıcıya özgü veri saklamanız gerekir. Bunu güvenli şekilde yapmak için:
<?php
function get_user_dashboard_stats( $user_id ) {
// Kullanıcı ID'sini key'e dahil et
$transient_key = 'user_dashboard_' . absint( $user_id ) . '_stats';
$stats = get_transient( $transient_key );
if ( false !== $stats ) {
return $stats;
}
// Kullanıcının sipariş geçmişi, puan durumu vb.
$customer_orders = wc_get_orders( array(
'customer_id' => $user_id,
'status' => array( 'completed' ),
'limit' => -1,
'return' => 'ids',
) );
$total_spent = 0;
foreach ( $customer_orders as $order_id ) {
$order = wc_get_order( $order_id );
$total_spent += (float) $order->get_total();
}
$stats = array(
'order_count' => count( $customer_orders ),
'total_spent' => $total_spent,
'last_update' => current_time( 'mysql' ),
);
// 30 dakika sakla
set_transient( $transient_key, $stats, 30 * MINUTE_IN_SECONDS );
return $stats;
}
// Kullanıcı sipariş verdiğinde cache'i temizle
add_action( 'woocommerce_order_status_completed', 'clear_user_stats_cache', 10, 2 );
function clear_user_stats_cache( $order_id ) {
$order = wc_get_order( $order_id );
$user_id = $order->get_customer_id();
if ( $user_id ) {
delete_transient( 'user_dashboard_' . $user_id . '_stats' );
}
}
Önemli uyarı: Kullanıcıya özel transient’lar söz konusu olduğunda anahtar uzunluğuna dikkat edin. WordPress transient anahtarları maksimum 172 karakter olabilir (veritabanı limiti nedeniyle pratik olarak daha az tutmanız önerilir).
Object Cache ile Entegrasyon
Yukarıda bahsettiğim kritik konuya gelelim. Redis veya Memcached kullandığınızda transient’lar otomatik olarak bellek üzerinde çalışır. Ama bazı nüanslar var:
Redis kurulumu olan bir sunucuda wp-config.php genellikle şöyle görünür:
// wp-config.php içinde Redis ayarları
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_DATABASE', 0 );
define( 'WP_REDIS_PREFIX', 'mysite_' );
define( 'WP_REDIS_MAXTTL', 86400 ); // Maksimum 24 saat
Redis varken transient’larınız wp_options tablosuna yazılmaz, doğrudan Redis’e gider. Bu durumda expiration olmayan transient’larla dikkatli olun: Redis’in maxmemory politikasına bağlı olarak veriler beklenmedik anlarda silinebilir. Kritik veriler için her zaman bir fallback mekanizması yazın.
Toplu Cache Temizleme ve Debug
Geliştirme sürecinde tüm transient’ları temizlemek için WP-CLI hayat kurtarır:
# Tüm transient'ları listele
wp transient list
# Belirli bir transient'ı sil
wp transient delete wc_bestsellers_10_v2
# Tüm expired transient'ları temizle
wp transient delete --expired
# Belirli prefix ile başlayanları bul ve sil
wp transient list --search="wc_bestsellers*" --format=ids |
xargs wp transient delete
# Tüm transient'ları sil (dikkatli kullanın!)
wp transient delete --all
Debug modunda transient’ların ne zaman hit, ne zaman miss yaptığını görmek için:
<?php
function debug_transient_get( $key ) {
$value = get_transient( $key );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$status = ( false !== $value ) ? 'HIT' : 'MISS';
error_log( sprintf(
'[Transient Debug] Key: %s | Status: %s | Time: %s',
$key,
$status,
current_time( 'H:i:s' )
) );
}
return $value;
}
Zamanlama Sabitleri
WordPress’in sunduğu zaman sabitlerini kullanmak kodun okunabilirliğini artırır:
- MINUTE_IN_SECONDS: 60 saniye
- HOUR_IN_SECONDS: 3600 saniye
- DAY_IN_SECONDS: 86400 saniye
- WEEK_IN_SECONDS: 604800 saniye
- MONTH_IN_SECONDS: 2592000 saniye (30 gün)
- YEAR_IN_SECONDS: 31536000 saniye
set_transient( $key, $data, 2 * HOUR_IN_SECONDS ) gibi kullanım, set_transient( $key, $data, 7200 ) yerine çok daha anlaşılır.
Performans Tuzakları ve İpuçları
Yıllar içinde gördüğüm yaygın hatalar:
Çok küçük expiration süresi: 30 saniyelik transient genellikle işe yaramaz. Eğer siteniz dakikada 100 istek alıyorsa, her 30 saniyede bir yeniden hesaplama yapmak hala ciddi yük demektir. Verinin ne sıklıkla değiştiğini düşünerek süre belirleyin.
Çok büyük veri saklamak: Transient’lara dev diziler veya serialize edilmiş büyük objeler yazmak, wp_options tablosunu şişirir. Redis kullanıyorsanız memory’i gereksiz tüketir. 1MB üzerindeki veri için farklı bir strateji düşünün.
Transient key çakışması: Birden fazla eklenti aynı site üzerinde çalışırken “bestsellers” gibi genel isimler çakışma yaratabilir. Her zaman eklentinize özgü prefix kullanın: myplugin_bestsellers_v1.
Race condition: Yüksek trafikli sitelerde cache miss anında birden fazla istek aynı anda pahalı sorguyu çalıştırabilir. “Cache stampede” ya da “thundering herd” denen bu durum için bir çözüm:
<?php
function get_data_with_lock( $key, $callback, $expiration = 3600 ) {
$data = get_transient( $key );
if ( false !== $data ) {
return $data;
}
// Kilit mekanizması
$lock_key = $key . '_generating';
if ( get_transient( $lock_key ) ) {
// Başka bir işlem veriyi oluşturuyor, kısa süre bekle
usleep( 100000 ); // 100ms
return get_transient( $key ); // Tekrar dene
}
// Kilidi al (30 saniye maksimum)
set_transient( $lock_key, 1, 30 );
// Veriyi oluştur
$data = call_user_func( $callback );
if ( false !== $data ) {
set_transient( $key, $data, $expiration );
}
// Kilidi serbest bırak
delete_transient( $lock_key );
return $data;
}
// Kullanımı:
$products = get_data_with_lock(
'wc_bestsellers_10_v2',
'calculate_bestsellers',
6 * HOUR_IN_SECONDS
);
Veritabanı Temizliği
Transient’lar, özellikle object cache olmayan sitelerde, zamanla wp_options tablosunu doldurabilir. WordPress teorik olarak expired transient’ları temizler ama bu her zaman güvenilir değildir. Periyodik temizlik için:
# Expired transient'ları veritabanından temizle
wp transient delete --expired
# Cron job olarak eklemek için (crontab -e):
# 0 3 * * * cd /var/www/html && wp transient delete --expired --allow-root
Ya da eklenti kodunuzun içinden:
<?php
// Eklenti aktivasyonunda scheduled event kur
register_activation_hook( __FILE__, 'schedule_transient_cleanup' );
function schedule_transient_cleanup() {
if ( ! wp_next_scheduled( 'myplugin_cleanup_transients' ) ) {
wp_schedule_event( time(), 'daily', 'myplugin_cleanup_transients' );
}
}
add_action( 'myplugin_cleanup_transients', 'do_transient_cleanup' );
function do_transient_cleanup() {
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_myplugin_%'
AND option_name NOT LIKE '_transient_timeout_%'"
);
// Timeout kayıtlarını da temizle
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_timeout_myplugin_%'"
);
}
Sonuç
Transient API, WordPress ekosisteminde en az kullanılan ama etkisi en yüksek araçlardan biridir. Doğru uygulandığında veritabanı yükünü dramatik biçimde düşürür, harici API çağrılarını minimize eder ve kullanıcı deneyimini iyileştirir. Yanlış kullanıldığında ise veri tutarsızlığına, stale content sorunlarına ve veritabanı şişmesine yol açar.
Özet olarak dikkat etmeniz gerekenler:
- Cache key’lerini versiyon numarasıyla yönetin
- İçerik değiştiğinde ilgili transient’ları aktif olarak geçersiz kılın
- Redis/Memcached kullanıyorsanız davranış farklarını bilin
- Yüksek trafik için race condition mekanizması ekleyin
- Çok büyük veri yapıları saklamaktan kaçının
- WP-CLI’yi geliştirme ve sorun giderme sürecinde etkin kullanın
Redis kurulu bir üretim ortamında, doğru yazılmış transient stratejisi ile veritabanı sorgularını yüzde 70-80 oranında azaltmak mümkün. Bu rakamlar teorik değil, pratikte ölçtüğüm gerçek sonuçlar. Eklenti geliştirirken performansı son aşamaya bırakmak yerine, bu mekanizmayı baştan mimariye dahil edin.
