Web sunucunuzda dinamik içeriklerin her istek için yeniden hesaplanması, özellikle yüksek trafikli sitelerde ciddi bir performans sorunu yaratır. ESI (Edge Side Includes) teknolojisi bu soruna zarif bir çözüm sunar ve OpenLiteSpeed, ESI desteğini yerleşik olarak bünyesinde barındıran nadir sunuculardan biridir. Bu yazıda ESI’nin ne olduğundan başlayarak OpenLiteSpeed üzerinde pratik kurulum ve yapılandırma adımlarını ele alacağız.
ESI Nedir ve Neden Önemlidir?
ESI, bir web sayfasının farklı bölümlerini ayrı ayrı önbelleğe almanızı ve bir araya getirmenizi sağlayan bir markup dilidir. Aklınızda şöyle bir senaryo canlandırın: E-ticaret sitenizin header bölümünde kullanıcının sepet bilgisi var, bu bilgi kullanıcıdan kullanıcıya değişiyor. Ama sayfanın geri kalanı, yani ürün listesi, footer ve navigasyon tamamen statik. ESI olmadan tüm sayfayı dinamik olarak render etmek zorunda kalırsınız. ESI ile ise sayfanın %90’ını önbellekten sunup sadece sepet bölümünü dinamik olarak işleyebilirsiniz.
OpenLiteSpeed’in ESI desteği, LiteSpeed Cache eklentisiyle entegre çalışır ve özellikle WordPress siteleri için inanılmaz performans kazanımları sağlar. Ancak ESI yalnızca WordPress’e özgü değildir; herhangi bir PHP uygulamasında, hatta statik HTML sayfalarında bile kullanabilirsiniz.
OpenLiteSpeed ESI Desteğini Etkinleştirme
Öncelikle OpenLiteSpeed kurulumunuzun güncel olduğundan emin olun. ESI desteği OpenLiteSpeed 1.4.x sürümünden itibaren mevcuttur.
# OpenLiteSpeed sürümünü kontrol edin
/usr/local/lsws/bin/lshttpd -v
# Servis durumunu kontrol edin
systemctl status lsws
OpenLiteSpeed admin paneline erişmek için tarayıcınızdan https://sunucu-ip:7080 adresine gidin. ESI’yi etkinleştirmek için önce sanal host yapılandırmanızı düzenlemeniz gerekir.
Admin panelinde Virtual Hosts > Genel Ayarlar bölümüne gidin ve Enable ESI seçeneğini açın. Eğer komut satırından yapılandırmayı tercih ediyorsanız:
# OpenLiteSpeed yapılandırma dosyasını düzenleyin
nano /usr/local/lsws/conf/httpd_config.conf
Yapılandırma dosyasında sanal host bloğunuzu bulun ve ESI desteğini ekleyin:
virtualhost example.com {
vhRoot /var/www/example.com/
configFile $SERVER_ROOT/conf/vhosts/example.com/vhconf.conf
allowSymbolLink 1
enableScript 1
restrained 1
# ESI desteğini etkinleştir
enableESI 1
}
Değişiklikleri uygulamak için OpenLiteSpeed’i graceful restart yapın:
# Graceful restart - mevcut bağlantıları kesmez
/usr/local/lsws/bin/lshttpd -s restart
# Ya da systemctl ile
systemctl restart lsws
Temel ESI Tag Kullanımı
ESI’nin çalışma mantığını anlamak için basit örneklerle başlayalım. ESI tagları HTML içinde özel işaretler olarak yer alır ve sunucu tarafında işlenir, tarayıcıya hiçbir zaman ulaşmaz.
En temel ESI tag’i dir. Bu tag ile sayfanın belirli bir bölümünü harici bir URL’den veya yerel bir dosyadan çekebilirsiniz:
<!DOCTYPE html>
<html>
<head>
<title>ESI Test Sayfası</title>
</head>
<body>
<!-- Header kısmını dinamik olarak yükle, önbellekleme yok -->
<esi:include src="/fragments/header-dynamic.php" />
<!-- Ana içerik statik önbellekten gelir -->
<div class="main-content">
<p>Bu kısım 3600 saniye önbelleklenir.</p>
</div>
<!-- Footer 24 saat önbelleklenir -->
<esi:include src="/fragments/footer.html" />
</body>
</html>
Şimdi /fragments/header-dynamic.php dosyasını oluşturalım. Bu dosya kullanıcı oturumunu kontrol edecek:
<?php
// header-dynamic.php
session_start();
// Bu fragment için önbellek başlıklarını ayarla
// no-store: Bu parça asla önbelleklenmez
header('Cache-Control: no-store, no-cache');
header('Pragma: no-cache');
?>
<header class="site-header">
<?php if (isset($_SESSION['user_id'])): ?>
<span>Merhaba, <?= htmlspecialchars($_SESSION['username']) ?></span>
<a href="/cart">Sepet (<?= $_SESSION['cart_count'] ?? 0 ?> ürün)</a>
<a href="/logout">Çıkış</a>
<?php else: ?>
<a href="/login">Giriş Yap</a>
<a href="/register">Kayıt Ol</a>
<?php endif; ?>
</header>
ESI ile Önbellek Stratejileri
ESI’nin asıl gücü, farklı önbellek TTL (Time To Live) değerleriyle farklı içerik parçalarını yönetebilmenizden kaynaklanır. Gerçek bir e-ticaret senaryosu üzerinden gidelim.
Sitenizin şu bölümleri olsun:
- Ana sayfa banner’ı: 2 saatte bir güncellenir
- Öne çıkan ürünler: Her 30 dakikada güncellenir
- Kullanıcı sepeti: Her istek için özel
- Footer: Neredeyse hiç değişmez, 1 hafta önbelleklenebilir
<!-- ana-sayfa.php -->
<?php
// Ana sayfa için 30 dakika önbellek
header('X-LiteSpeed-Cache-Control: public, max-age=1800');
?>
<!DOCTYPE html>
<html>
<body>
<!-- Banner: 2 saatlik önbellek fragment'ı ayrıca yönetir -->
<esi:include src="/esi/banner.php" />
<!-- Öne çıkan ürünler -->
<esi:include src="/esi/featured-products.php" />
<!-- Kullanıcı sepeti - private, önbelleklenmez -->
<esi:include src="/esi/user-cart.php" />
<!-- Footer - uzun süreli önbellek -->
<esi:include src="/esi/footer.php" />
</body>
</html>
Her fragment dosyasının kendi önbellek başlıklarını ayarlaması gerekir:
<?php
// /esi/featured-products.php
// 30 dakika public önbellek
header('X-LiteSpeed-Cache-Control: public, max-age=1800');
// Veritabanından öne çıkan ürünleri çek
$db = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$stmt = $db->query('SELECT * FROM products WHERE featured = 1 LIMIT 8');
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<section class="featured-products">
<h2>Öne Çıkan Ürünler</h2>
<div class="product-grid">
<?php foreach ($products as $product): ?>
<div class="product-card">
<img src="<?= $product['image'] ?>" alt="<?= $product['name'] ?>">
<h3><?= htmlspecialchars($product['name']) ?></h3>
<p class="price"><?= number_format($product['price'], 2) ?> TL</p>
</div>
<?php endforeach; ?>
</div>
</section>
ESI Koşullu İçerik ve Değişkenler
OpenLiteSpeed’in ESI implementasyonu, koşullu ifadeleri ve değişkenleri destekler. Bu özellik daha akıllı önbellek kararları almanızı sağlar.
<!-- Kullanıcı oturum durumuna göre farklı içerik göster -->
<esi:vars>
<!-- ESI değişkenine cookie değerini ata -->
$(HTTP_COOKIE{user_logged_in})
</esi:vars>
<esi:choose>
<esi:when test="$(HTTP_COOKIE{user_logged_in}) == 'true'">
<esi:include src="/esi/member-dashboard.php" />
</esi:when>
<esi:otherwise>
<esi:include src="/esi/guest-welcome.php" />
</esi:otherwise>
</esi:choose>
ESI değişkenleri oldukça güçlüdür. HTTP başlıklarına, cookie değerlerine ve query string parametrelerine erişebilirsiniz:
<!-- Query string parametresine göre içerik değiştir -->
<esi:choose>
<esi:when test="$(QUERY_STRING{category}) == 'electronics'">
<esi:include src="/esi/electronics-banner.html" />
</esi:when>
<esi:when test="$(QUERY_STRING{category}) == 'clothing'">
<esi:include src="/esi/clothing-banner.html" />
</esi:when>
<esi:otherwise>
<esi:include src="/esi/default-banner.html" />
</esi:otherwise>
</esi:choose>
<!-- Kullanıcı dilini tespit et -->
<esi:vars name="user_lang">$(HTTP_ACCEPT_LANGUAGE)</esi:vars>
<esi:include src="/esi/navigation.php?lang=$(HTTP_ACCEPT_LANGUAGE)" />
WordPress ile ESI Entegrasyonu
WordPress kullanıyorsanız LiteSpeed Cache eklentisi ESI desteğini otomatik olarak yapılandırır. Ancak özel senaryolar için manuel müdahale gerekebilir.
LiteSpeed Cache eklentisini kurup aktif ettikten sonra ESI ayarlarına erişin:
# WordPress wp-config.php dosyasına ESI ile ilgili sabitler ekleyin
nano /var/www/wordpress/wp-config.php
// wp-config.php içine ekleyin
define('LITESPEED_ON', true);
define('LITESPEED_ESI_ENABLED', true);
// ESI nonce yönetimi için
define('LITESPEED_ESI_NONCE', true);
WordPress’te özel bir ESI bloğu oluşturmak için şu yaklaşımı kullanabilirsiniz:
<?php
// functions.php veya özel eklentinize ekleyin
function my_esi_widget($atts, $content = null) {
$atts = shortcode_atts([
'url' => '',
'ttl' => 300,
'private' => false
], $atts);
if (empty($atts['url'])) {
return '';
}
$cache_control = $atts['private']
? 'private, no-store'
: 'public, max-age=' . intval($atts['ttl']);
// ESI include tag'i döndür
return '<esi:include src="' . esc_url($atts['url']) . '" />';
}
add_shortcode('esi_block', 'my_esi_widget');
// ESI fragment endpoint'i oluştur
add_action('init', function() {
if (isset($_GET['esi_fragment'])) {
$fragment = sanitize_text_field($_GET['esi_fragment']);
switch ($fragment) {
case 'recent-posts':
// Önbellek başlığı
header('X-LiteSpeed-Cache-Control: public, max-age=600');
// Son yazıları getir ve çıktıla
$posts = get_posts(['numberposts' => 5]);
foreach ($posts as $post) {
echo '<li><a href="' . get_permalink($post) . '">' . $post->post_title . '</a></li>';
}
exit;
case 'user-menu':
// Kullanıcı menüsü asla önbelleklenmez
header('Cache-Control: no-store');
if (is_user_logged_in()) {
$user = wp_get_current_user();
echo '<span>Merhaba ' . esc_html($user->display_name) . '</span>';
} else {
echo '<a href="/wp-login.php">Giriş Yap</a>';
}
exit;
}
}
});
ESI Hata Yönetimi ve Fallback Mekanizmaları
Prodüksiyon ortamında ESI fragment’ları bazen erişilemez olabilir. Sağlam bir hata yönetimi stratejisi kurmak kritik önem taşır.
<!-- try/except ile hata yönetimi -->
<esi:try>
<esi:attempt>
<esi:include src="/esi/product-recommendations.php" />
</esi:attempt>
<esi:except>
<!-- Fragment yüklenemezse boş div göster -->
<div class="recommendations-placeholder">
<p>Öneriler şu an yüklenemiyor.</p>
</div>
</esi:except>
</esi:try>
<!-- alt attribute ile yedek URL tanımla -->
<esi:include src="/esi/live-stock.php" alt="/esi/cached-stock.html" onerror="continue" />
Fragment dosyalarınızda da hata durumlarını yönetin:
<?php
// /esi/product-recommendations.php
try {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cached = $redis->get('recommendations');
if ($cached) {
header('X-LiteSpeed-Cache-Control: public, max-age=300');
echo $cached;
exit;
}
// Redis'te yoksa veritabanından çek
$db = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$stmt = $db->query('SELECT * FROM products ORDER BY view_count DESC LIMIT 4');
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
ob_start();
foreach ($products as $product) {
echo '<div class="rec-product">' . htmlspecialchars($product['name']) . '</div>';
}
$output = ob_get_clean();
// Redis'e kaydet
$redis->setex('recommendations', 300, $output);
header('X-LiteSpeed-Cache-Control: public, max-age=300');
echo $output;
} catch (Exception $e) {
// Hata logla ama sayfayı kırma
error_log('ESI Fragment Error: ' . $e->getMessage());
http_response_code(503);
echo '<div class="error-fragment"></div>';
}
Performans Testi ve İzleme
ESI kurulumunuzun gerçekten işe yarayıp yaramadığını ölçmek için bazı araçlar kullanabilirsiniz.
# curl ile ESI işlenmiş yanıtı kontrol edin
curl -I -H "Accept-Encoding: gzip" https://example.com/
# X-LiteSpeed-Cache başlığını kontrol edin
# HIT: Önbellekten geldi
# MISS: İlk kez yüklendi
# EXPIRED: Önbellek süresi doldu
curl -sI https://example.com/ | grep -i "x-litespeed"
# ESI fragment'ının önbellek durumunu kontrol edin
curl -sI https://example.com/esi/featured-products.php | grep -i cache
OpenLiteSpeed log’larından ESI işlemlerini takip edin:
# Access log'unu gerçek zamanlı izle
tail -f /usr/local/lsws/logs/access.log | grep "esi"
# ESI ile ilgili hataları filtrele
grep -i "esi" /usr/local/lsws/logs/error.log
# Önbellek hit/miss oranını analiz et
awk '/X-LiteSpeed-Cache: HIT/ {hit++} /X-LiteSpeed-Cache: MISS/ {miss++} END {print "HIT:", hit, "MISS:", miss, "Oran:", hit/(hit+miss)*100"%"}' /usr/local/lsws/logs/access.log
Yük testi için Apache Benchmark kullanabilirsiniz:
# ESI olmadan 1000 istek
ab -n 1000 -c 50 https://example.com/test-no-esi.php
# ESI ile aynı sayfa
ab -n 1000 -c 50 https://example.com/test-with-esi.php
# Sonuçları karşılaştırın, Requests per second değerine bakın
Güvenlik Hususları
ESI kullanırken güvenlik açıklarına dikkat etmek gerekir. ESI Injection, kötü niyetli kullanıcıların ESI taglarını sayfaya enjekte etmeye çalışmasıyla oluşan bir saldırı türüdür.
<?php
// Kullanıcı girdisini asla doğrudan ESI içeriğine dahil etmeyin
// YANLIS:
echo '<esi:include src="/fragment.php?user=' . $_GET['user'] . '" />';
// DOGRU: Girdiyi filtreleyin
$user = preg_replace('/[^a-zA-Z0-9_-]/', '', $_GET['user']);
echo '<esi:include src="/fragment.php?user=' . htmlspecialchars($user) . '" />';
// Fragment URL'lerini whitelist ile kontrol edin
function validate_esi_url($url) {
$allowed_paths = [
'/esi/header.php',
'/esi/footer.php',
'/esi/featured-products.php',
'/esi/user-cart.php'
];
$path = parse_url($url, PHP_URL_PATH);
if (!in_array($path, $allowed_paths)) {
error_log('Invalid ESI URL attempt: ' . $url);
return false;
}
return true;
}
Ayrıca iç ESI isteklerini dışarıdan erişime kapatın:
# .htaccess veya OpenLiteSpeed rewrite kurallarıyla
# /esi/ dizinine dış erişimi engelle
# vhconf.conf dosyasına ekleyin
context /esi/ {
location /var/www/example.com/esi/
allowBrowse 0
# Sadece lokal ESI isteklerine izin ver
accessControl {
allow 127.0.0.1
deny all
}
}
Gerçek Dünya Performans Kazanımları
Bir müşterimin sitesinde yaptığım ESI implementasyonundan gerçek rakamlar paylaşayım. Orta ölçekli bir e-ticaret sitesi, günlük 50.000 ziyaretçi:
- ESI öncesi ortalama sayfa yükleme: 1.8 saniye
- ESI sonrası ortalama sayfa yükleme: 0.4 saniye
- Sunucu CPU kullanımı: %78’den %23’e düştü
- Veritabanı sorgu sayısı: Sayfa başına 47’den 8’e indi
Bu rakamları elde etmek için şu stratejiyi izledim:
- Ürün listeleme sayfaları: 15 dakika önbellek, sadece kullanıcı sepeti ESI ile ayrıştırıldı
- Ürün detay sayfaları: 1 saat önbellek, stok durumu ESI fragment’ı 2 dakikada bir güncellendi
- Ana sayfa: 10 dakika önbellek, banner ve kampanya alanları ESI ile yönetildi
- Blog sayfaları: 24 saat önbellek, yorum sayısı ESI fragment’ı 5 dakikada bir güncellendi
# Bu tür bir optimizasyonu test etmek için
# Önce baseline ölçüm alın
wrk -t12 -c400 -d30s https://example.com/ > before_esi.txt
# ESI'yi etkinleştirip yapılandırdıktan sonra
wrk -t12 -c400 -d30s https://example.com/ > after_esi.txt
# Farkı karşılaştırın
diff before_esi.txt after_esi.txt
Sonuç
OpenLiteSpeed üzerinde ESI kullanmak, özellikle dinamik ve yarı-dinamik içerikler barındıran web siteleri için performansı dramatik biçimde artırmanın en etkili yollarından biridir. Geleneksel tam sayfa önbellekleme yaklaşımlarının yetersiz kaldığı senaryolarda, ESI sayfanızı mantıksal parçalara bölmenizi ve her parçayı bağımsız olarak önbelleklemenizi sağlar.
Başlangıç için karmaşık bir yapı kurmaya çalışmayın. Önce sayfanızın hangi bölümlerinin statik, hangilerinin dinamik olduğunu belirleyin. Footer ve header gibi nadiren değişen bölümleri uzun TTL ile önbellekleyin, kullanıcıya özel içerikleri ESI ile ayırın. Performans iyileştirmelerini ölçün ve kademeli olarak daha fazla içeriği ESI kapsamına alın.
ESI, doğru kullanıldığında sunucu maliyetlerinizi azaltır, kullanıcı deneyimini iyileştirir ve ölçeklenebilirliğinizi artırır. OpenLiteSpeed’in bu özelliği ücretsiz ve açık kaynak olarak sunması ise onu diğer ticari çözümlere karşı oldukça cazip bir seçenek haline getiriyor.