Redis ile Session Yönetimi: Hızlı ve Ölçeklenebilir Oturum Kontrolü

Oturum yönetimi, web uygulamalarının belki de en kritik bileşenlerinden biri. Kullanıcı giriş yapıyor, sepete ürün ekliyor, form dolduruyor ve tüm bu işlemler boyunca uygulamanın “kim olduğunu hatırlaması” gerekiyor. Geleneksel yöntemlerle bu verileri veritabanında tutmak hem yavaş hem de ölçekleme açısından kabus. İşte tam bu noktada Redis devreye giriyor ve session yönetimini tamamen başka bir boyuta taşıyor.

Neden Redis ile Session Yönetimi?

Klasik yaklaşımda session verileri ya dosya sisteminde ya da MySQL/PostgreSQL gibi ilişkisel veritabanlarında tutulur. Küçük uygulamalarda bu yeterli görünse de trafik arttıkça sorunlar da artmaya başlar.

Şöyle düşün: 10.000 eş zamanlı kullanıcın var ve her istek geldiğinde MySQL’e “bu kullanıcı hala oturum açık mı?” diye soruyorsun. Bu, veritabanı sunucusuna gereksiz bir yük bindiriyor. Üstelik yatay ölçekleme yaptığında, yani birden fazla uygulama sunucusu çalıştırdığında, sticky session gibi çözümlere mahkum kalıyorsun.

Redis’in bu konuda sunduğu avantajlar şunlar:

  • Bellek içi depolama: Veriye erişim mikrosaniye mertebesinde gerçekleşir
  • TTL desteği: Session süresi dolduğunda Redis otomatik olarak siler, ekstra iş gerektirmez
  • Atomik işlemler: Race condition endişesi olmadan güvenli yazma/okuma
  • Pub/Sub desteği: Oturum invalidasyonu için harika bir araç
  • Cluster desteği: Yatay ölçekleme neredeyse sancısız

Redis Kurulumu ve Temel Konfigürasyon

Önce ortamı hazırlayalım. Ubuntu/Debian tabanlı sistemlerde:

# Redis kurulumu
sudo apt update
sudo apt install redis-server -y

# Servis durumunu kontrol et
sudo systemctl status redis-server

# Redis'i aktif et ve başlat
sudo systemctl enable redis-server
sudo systemctl start redis-server

# Bağlantıyı test et
redis-cli ping
# Cevap: PONG

Session yönetimi için Redis konfigürasyonunu optimize etmemiz gerekiyor. /etc/redis/redis.conf dosyasında şu ayarları yapalım:

# /etc/redis/redis.conf içindeki kritik ayarlar
sudo nano /etc/redis/redis.conf

# Şu satırları bul ve düzenle:
# Maksimum bellek limiti (sunucu RAM'inin %70-75'i iyi bir başlangıç)
maxmemory 2gb

# Bellek dolduğunda eski session'ları sil (volatile-lru önerilir)
maxmemory-policy volatile-lru

# Session verisi için persistence gerekli mi? Tartışmalı ama AOF önerilir
appendonly yes
appendfsync everysec

# Bağlantı zaman aşımı (boşta kalan bağlantıları temizle)
timeout 300

# TCP keepalive
tcp-keepalive 60

# Bind adresi (sadece localhost veya uygulama sunucusu)
bind 127.0.0.1

# Şifre koru (üretim ortamında şart)
requirepass sifreniBurayaYaz_Guclu_Bir_Sifre

Konfigürasyonu uyguladıktan sonra Redis’i yeniden başlat:

sudo systemctl restart redis-server

# Şifre ile bağlan ve test et
redis-cli -a sifreniBurayaYaz_Guclu_Bir_Sifre ping

Session Veri Yapısı Tasarımı

Redis’te session verilerini nasıl saklayacağımızı düşünmemiz gerekiyor. İki yaygın yaklaşım var:

String tabanlı yaklaşım: Session verisini JSON olarak serialize edip tek bir key-value çifti olarak saklarsın. Basit ama her küçük güncelleme için tüm objeyi yeniden yazman gerekir.

Hash tabanlı yaklaşım: Redis Hash yapısını kullanırsın. Her session alanı ayrı bir hash field olur. Sadece değişen alanı güncelleyebilirsin, bu çok daha verimli.

Ben hash tabanlı yaklaşımı tercih ediyorum. Şöyle görünür:

# Session oluşturma (Hash kullanarak)
redis-cli -a sifren HSET session:abc123xyz 
    user_id "42" 
    username "ahmet.yilmaz" 
    email "[email protected]" 
    role "editor" 
    ip_address "192.168.1.100" 
    user_agent "Mozilla/5.0..." 
    created_at "1704067200" 
    last_activity "1704067200"

# Session TTL ayarla (3600 saniye = 1 saat)
redis-cli -a sifren EXPIRE session:abc123xyz 3600

# Session verisini oku
redis-cli -a sifren HGETALL session:abc123xyz

# Tek bir alanı güncelle (tüm session'ı yeniden yazmadan)
redis-cli -a sifren HSET session:abc123xyz last_activity "1704070800"

# TTL'yi sıfırla (kullanıcı aktif olduğu sürece session devam etsin)
redis-cli -a sifren EXPIRE session:abc123xyz 3600

# Session'ın ne kadar ömrü kaldığını kontrol et
redis-cli -a sifren TTL session:abc123xyz

PHP ile Redis Session Entegrasyonu

PHP dünyasında Redis session handler kullanmak oldukça yaygın. İki seçenek var: PHP’nin built-in redis session handler’ı veya özel bir implementasyon.

Önce php-redis extension’ını kur:

# PHP Redis extension kurulumu
sudo apt install php-redis -y

# php.ini'yi düzenle
sudo nano /etc/php/8.1/fpm/php.ini

# Şu satırları ekle veya düzenle:
# session.save_handler = redis
# session.save_path = "tcp://127.0.0.1:6379?auth=sifren&database=1"
# session.gc_maxlifetime = 3600

# PHP-FPM'i yeniden başlat
sudo systemctl restart php8.1-fpm

Özel bir session handler yazmak istersen, bu sana daha fazla kontrol sağlar:

<?php
// redis_session_handler.php
class RedisSessionHandler implements SessionHandlerInterface
{
    private Redis $redis;
    private int $ttl;
    private string $prefix;

    public function __construct(string $host, int $port, string $password, int $ttl = 3600)
    {
        $this->redis = new Redis();
        $this->redis->connect($host, $port);
        $this->redis->auth($password);
        $this->redis->select(1); // Session için ayrı DB
        $this->ttl = $ttl;
        $this->prefix = 'session:';
    }

    public function open(string $path, string $name): bool
    {
        return $this->redis->isConnected();
    }

    public function read(string $id): string|false
    {
        $data = $this->redis->get($this->prefix . $id);
        return $data === false ? '' : $data;
    }

    public function write(string $id, string $data): bool
    {
        return $this->redis->setex($this->prefix . $id, $this->ttl, $data);
    }

    public function destroy(string $id): bool
    {
        $this->redis->del($this->prefix . $id);
        return true;
    }

    public function gc(int $max_lifetime): int|false
    {
        // Redis TTL ile otomatik temizliyor, gc gerekmiyor
        return 0;
    }

    public function close(): bool
    {
        return true;
    }
}

// Kullanım
$handler = new RedisSessionHandler('127.0.0.1', 6379, 'sifren', 3600);
session_set_save_handler($handler, true);
session_start();

Node.js ile Redis Session Yönetimi

Express.js kullananlar için connect-redis paketi hayat kurtarır:

# Gerekli paketleri kur
npm install express express-session connect-redis redis
// app.js
const express = require('express');
const session = require('express-session');
const { createClient } = require('redis');
const RedisStore = require('connect-redis').default;

const app = express();

// Redis client oluştur
const redisClient = createClient({
    socket: {
        host: '127.0.0.1',
        port: 6379
    },
    password: 'sifren',
    database: 1
});

redisClient.connect().catch(console.error);

// Redis bağlantı hatalarını izle
redisClient.on('error', (err) => {
    console.error('Redis Client Error:', err);
});

redisClient.on('connect', () => {
    console.log('Redis bağlantısı başarılı');
});

// Session middleware ayarla
app.use(session({
    store: new RedisStore({
        client: redisClient,
        prefix: 'session:',
        ttl: 3600
    }),
    secret: 'cok-gizli-bir-secret-key-buraya',
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production', // HTTPS zorunlu
        httpOnly: true, // XSS koruması
        maxAge: 3600 * 1000, // 1 saat (ms cinsinden)
        sameSite: 'strict' // CSRF koruması
    }
}));

// Login endpoint örneği
app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    
    // Kullanıcı doğrulama (örnek)
    const user = await authenticateUser(username, password);
    
    if (user) {
        req.session.userId = user.id;
        req.session.username = user.username;
        req.session.role = user.role;
        req.session.loginTime = Date.now();
        
        res.json({ success: true, message: 'Giriş başarılı' });
    } else {
        res.status(401).json({ success: false, message: 'Hatalı kullanıcı adı veya şifre' });
    }
});

// Logout endpoint
app.post('/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) {
            return res.status(500).json({ error: 'Çıkış yapılırken hata oluştu' });
        }
        res.clearCookie('connect.sid');
        res.json({ success: true });
    });
});

app.listen(3000, () => console.log('Uygulama 3000 portunda çalışıyor'));

Güvenlik: Session Hijacking ve Fixation Önlemleri

Redis session yönetiminde güvenlik, asla göz ardı edemeyeceğin bir konu. Üretim ortamında karşılaşabileceğin başlıca tehditler ve çözümleri şöyle:

Session ID Güvenliği

Session ID’leri yeterince uzun ve rastgele olmalı. Redis tarafında ek güvenlik katmanları ekleyebilirsin:

# Kullanıcı bilgilerini session'a bağlayan script (bash ile test amaçlı)
# Gerçek uygulamada bu mantık uygulama kodunda olur

# IP ve User-Agent bazlı session doğrulama için meta veri sakla
redis-cli -a sifren HSET session:abc123xyz 
    ip_fingerprint "$(echo -n '192.168.1.100Mozilla/5.0' | sha256sum | cut -d' ' -f1)" 
    created_at "$(date +%s)" 
    rotation_count "0"

# Session token rotation - login sonrası yeni ID ver
# Eski session'ı sil, yeni oluştur
redis-cli -a sifren DEL session:eski_session_id
redis-cli -a sifren HSET session:yeni_session_id user_id "42"
redis-cli -a sifren EXPIRE session:yeni_session_id 3600

Aktif Session Takibi

Bir kullanıcının tüm aktif oturumlarını takip etmek isteyebilirsin. Örneğin “tüm cihazlardan çıkış yap” özelliği için:

# Kullanıcının aktif session'larını bir Set'te tut
redis-cli -a sifren SADD user_sessions:42 "session:abc123xyz"
redis-cli -a sifren SADD user_sessions:42 "session:def456uvw"

# Kullanıcının tüm session'larını listele
redis-cli -a sifren SMEMBERS user_sessions:42

# Tüm cihazlardan çıkış yap
for session_key in $(redis-cli -a sifren SMEMBERS user_sessions:42); do
    redis-cli -a sifren DEL "$session_key"
done
redis-cli -a sifren DEL user_sessions:42

Rate Limiting ile Session Koruması

Brute force saldırılarına karşı Redis’i rate limiting için de kullanabilirsin. Bu, session sisteminizle entegre çalışır:

# Login denemelerini say ve sınırla
# Her başarısız giriş için counter artır

# Başarısız giriş kaydı
redis-cli -a sifren INCR login_attempts:192.168.1.100

# 5 dakika TTL koy (5 dakika içinde max deneme hakkı)
redis-cli -a sifren EXPIRE login_attempts:192.168.1.100 300

# Mevcut deneme sayısını kontrol et
attempts=$(redis-cli -a sifren GET login_attempts:192.168.1.100)

if [ "$attempts" -gt 5 ]; then
    echo "IP adresi geçici olarak engellendi"
    # Blacklist'e ekle
    redis-cli -a sifren SETEX blocked_ip:192.168.1.100 3600 "1"
fi

# Başarılı girişte sayacı sıfırla
redis-cli -a sifren DEL login_attempts:192.168.1.100

Session İzleme ve Monitoring

Üretimde Redis session yönetimini izlemek kritik. Şu komutlar ve araçlar işini kolaylaştırır:

# Toplam aktif session sayısını öğren
redis-cli -a sifren DBSIZE

# Pattern ile session'ları listele
redis-cli -a sifren KEYS "session:*" | wc -l

# Redis bellek kullanımını incele
redis-cli -a sifren INFO memory

# Anlık istatistikler (her saniye güncellenir)
redis-cli -a sifren INFO stats | grep -E "total_commands_processed|keyspace_hits|keyspace_misses"

# Keyspace olaylarını izle (session oluşturma/silme)
redis-cli -a sifren CONFIG SET notify-keyspace-events KEA
redis-cli -a sifren PSUBSCRIBE '__keyevent@1__:expired'

# Büyük session'ları bul (yüksek bellek tüketen)
redis-cli -a sifren --bigkeys

# Belirli bir session'ın boyutunu öğren
redis-cli -a sifren MEMORY USAGE session:abc123xyz

Monitoring için basit bir Bash scripti yazalım. Bunu cron’a ekleyebilirsin:

#!/bin/bash
# redis_session_monitor.sh

REDIS_PASS="sifren"
REDIS_CLI="redis-cli -a $REDIS_PASS"
LOG_FILE="/var/log/redis_session_monitor.log"
ALERT_THRESHOLD=10000

timestamp=$(date '+%Y-%m-%d %H:%M:%S')

# Aktif session sayısı
session_count=$($REDIS_CLI -n 1 DBSIZE)

# Bellek kullanımı (MB cinsinden)
used_memory=$($REDIS_CLI INFO memory | grep "used_memory_human" | cut -d: -f2 | tr -d 'r')

# Hit ratio hesapla
hits=$($REDIS_CLI INFO stats | grep "keyspace_hits:" | cut -d: -f2 | tr -d 'r')
misses=$($REDIS_CLI INFO stats | grep "keyspace_misses:" | cut -d: -f2 | tr -d 'r')

if [ $((hits + misses)) -gt 0 ]; then
    hit_ratio=$(echo "scale=2; $hits * 100 / ($hits + $misses)" | bc)
else
    hit_ratio="N/A"
fi

echo "$timestamp | Sessions: $session_count | Memory: $used_memory | Hit Ratio: %$hit_ratio" >> $LOG_FILE

# Eşik aşıldıysa uyarı gönder
if [ "$session_count" -gt "$ALERT_THRESHOLD" ]; then
    echo "$timestamp - UYARI: Session sayısı eşiği aştı: $session_count" >> $LOG_FILE
    # Buraya mail veya Slack notification ekleyebilirsin
fi
# Script'i çalıştırılabilir yap ve cron'a ekle
chmod +x /usr/local/bin/redis_session_monitor.sh

# Her 5 dakikada bir çalıştır
crontab -e
# Şu satırı ekle:
# */5 * * * * /usr/local/bin/redis_session_monitor.sh

Yüksek Erişilebilirlik: Redis Sentinel ile Session Yönetimi

Üretim ortamında tek bir Redis node yeterli değil. Redis Sentinel ile failover senaryosunu ele alalım:

# Sentinel konfigürasyonu
sudo nano /etc/redis/sentinel.conf

# Şu içeriği ekle:
# sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass mymaster sifren
# sentinel down-after-milliseconds mymaster 5000
# sentinel failover-timeout mymaster 60000
# sentinel parallel-syncs mymaster 1

# Sentinel'i başlat
sudo redis-sentinel /etc/redis/sentinel.conf --daemonize yes

# Sentinel durumunu kontrol et
redis-cli -p 26379 sentinel master mymaster

Node.js uygulamanı Sentinel’e bağla:

// Sentinel ile Redis bağlantısı
const { createClient } = require('redis');

const redisClient = createClient({
    sentinels: [
        { host: '10.0.0.1', port: 26379 },
        { host: '10.0.0.2', port: 26379 },
        { host: '10.0.0.3', port: 26379 }
    ],
    name: 'mymaster',
    password: 'sifren',
    sentinelPassword: 'sentinel_sifren'
});

// Bağlantı olaylarını dinle
redisClient.on('error', (err) => console.error('Redis Error:', err));
redisClient.on('reconnecting', () => console.log('Redis yeniden bağlanıyor...'));
redisClient.on('ready', () => console.log('Redis hazır'));

Gerçek Dünya Senaryosu: E-Ticaret Platformu

Büyük bir e-ticaret projesinde session yönetimini nasıl yapılandırdığımı paylaşayım. Platformun yaklaşık 50.000 aktif kullanıcısı vardı ve MySQL’de session tutmak ciddi yük oluşturuyordu.

Redis’e geçiş sonrası yaptığımız optimizasyonlar:

  • Farklı session tipleri için farklı TTL: Misafir kullanıcılar için 30 dakika, kayıtlı kullanıcılar için 7 gün
  • Sepet verisi ayrımı: Session’da sadece user_id ve sepet_id tutuldu, sepet verisi ayrı bir Redis hash’te
  • Sıkıştırma: Büyük session verileri için gzip sıkıştırma kullanıldı
  • İki ayrı Redis instance: Biri session için (volatile-lru), biri cache için (allkeys-lru)
# Farklı TTL stratejisi için config
# Misafir oturum
redis-cli -a sifren SETEX session:guest:xyz 1800 '{"cart_id":"cart_456"}'

# Kayıtlı kullanıcı oturumu
redis-cli -a sifren HSET session:user:abc user_id "42" cart_id "cart_789"
redis-cli -a sifren EXPIRE session:user:abc 604800  # 7 gün

# "Beni hatırla" özelliği için uzun süreli token
redis-cli -a sifren HSET remember_token:longtoken123 user_id "42" created_at "$(date +%s)"
redis-cli -a sifren EXPIRE remember_token:longtoken123 2592000  # 30 gün

Bu geçiş sonrası veritabanı yükü %60 azaldı, sayfa yükleme süreleri ortalama 340ms’den 89ms’ye düştü.

Sonuç

Redis ile session yönetimi, doğru yapılandırıldığında hem performans hem de ölçeklenebilirlik açısından geleneksel yöntemlerle kıyaslanamaz avantajlar sunuyor. Bu yazıda ele aldığımız konuları hızlıca özetleyelim:

  • Temel konfigürasyon: volatile-lru politikası ve uygun maxmemory ayarı session yönetimi için şart
  • Veri yapısı seçimi: Hash yapısı, kısmi güncellemeler nedeniyle String’den çok daha verimli
  • Güvenlik: Session ID rotation, IP parmakizi, rate limiting ve güvenli cookie flag’leri ihmal edilemez
  • Monitoring: Hit ratio, bellek kullanımı ve aktif session sayısını düzenli takip et
  • Yüksek erişilebilirlik: Üretim ortamında Sentinel veya Cluster kullan, tek node’a güvenme

Redis’i session deposu olarak kullanmak başta fazla kompleks görünebilir ama bir kez doğru kurulduğunda bakımı son derece kolay. Özellikle mikroservis mimarisinde veya yük dengeleme arkasında birden fazla uygulama sunucusu çalıştırıyorsan, Redis neredeyse zorunlu hale geliyor. Artık “session hangi sunucuda?” diye düşünmene gerek yok.

Yorum yapın