İçeriğe geç

Java'da Session Pool (Oturum Havuzu): Kapsamlı Rehber

T
Tolgahan
· · 13 dk okuma · 92 görüntülenme

Java'da Session Pool (Oturum Havuzu): Kapsamlı Rehber

Bir otelin resepsiyonunu düşünün. Her gelen müşteriye sıfırdan bir oda inşa etmek yerine, hazır odaları tahsis ediyorsunuz. Müşteri ayrılınca oda temizleniyor ve bir sonraki misafir için hazır hale geliyor. İşte Session Pool tam olarak bu mantıkla çalışıyor — ama odalar yerine oturum nesneleri (session objects) var ve misafirler yerine kullanıcı istekleri.

Bu yazıda Java ekosisteminde session pooling konseptini, neden ihtiyaç duyduğumuzu, nasıl implemente edileceğini ve production ortamında dikkat etmeniz gereken kritik noktaları ele alacağız. Hazırsanız, dalıyoruz.


Session Nedir ve Neden Havuza İhtiyacımız Var?

Web uygulamalarında session (oturum), bir kullanıcının sunucuyla olan etkileşimini takip eden bir mekanizmadır. HTTP protokolü doğası gereği stateless (durumsuz) olduğundan, kullanıcının kim olduğunu, sepetinde ne olduğunu ya da giriş yapıp yapmadığını hatırlamaz. Session'lar tam da bu boşluğu doldurur.

Peki sorun ne? Sorun şu: her kullanıcı isteği geldiğinde yeni bir session nesnesi oluşturmak, bellekte yer ayırmak ve sonra çöp toplayıcıya (Garbage Collector) bırakmak pahalı bir operasyon. Binlerce eşzamanlı kullanıcı düşünün — her biri için tekrar tekrar nesne yaratıp yok ediyorsunuz. Bu, hem CPU hem de bellek üzerinde ciddi bir yük oluşturur.

Session Pool, tıpkı bir Connection Pool gibi, önceden oluşturulmuş session nesnelerini bir havuzda tutar ve ihtiyaç duyulduğunda bu havuzdan tahsis eder. Kullanım bittikten sonra nesne havuza geri döner — yok edilmez, temizlenir ve tekrar kullanılır.

Connection Pool ile Karşılaştırma

Muhtemelen HikariCP veya Apache DBCP gibi connection pool kütüphanelerini zaten biliyorsunuz. Mantık neredeyse aynı:

ÖzellikConnection PoolSession Pool
Havuzdaki nesneVeritabanı bağlantısıOturum nesnesi
Oluşturma maliyetiYüksek (TCP handshake, auth)Orta (bellek tahsisi, veri yapısı)
Yeniden kullanım✅ Evet✅ Evet
Thread-safetyZorunluZorunlu
Timeout mekanizmasıConnection timeoutSession timeout
Popüler araçlarHikariCP, C3P0Spring Session, Tomcat Manager

Connection pool'da bir bağlantı "ödünç alınır", sorgu çalıştırılır ve geri verilir. Session pool'da ise bir oturum nesnesi tahsis edilir, kullanıcı verileri tutulur ve belirli koşullar altında (timeout, logout) havuza geri döner ya da yok edilir.


HTTP Session Yönetimi: Servlet API Temelleri

Java'da session yönetiminin temel taşı Servlet API'deki HttpSession arayüzüdür. Tomcat, Jetty veya WildFly gibi uygulama sunucuları bu arayüzü implemente eder.

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;

public class SepetServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws IOException {

        // Mevcut session'ı al, yoksa yeni oluştur
        HttpSession session = request.getSession(true);

        // Kullanıcının sepetini session'dan çek
        @SuppressWarnings("unchecked")
        var sepet = (java.util.List<String>) session.getAttribute("sepet");

        if (sepet == null) {
            sepet = new java.util.ArrayList<>();
            session.setAttribute("sepet", sepet);
        }

        String urun = request.getParameter("urun");
        sepet.add(urun);

        // Session ID ve son erişim zamanını logla
        System.out.printf("Session ID: %s | Ürün eklendi: %s | Sepet boyutu: %d%n",
                session.getId(), urun, sepet.size());

        response.setContentType("text/plain; charset=UTF-8");
        response.getWriter().write("Ürün sepete eklendi: " + urun);
    }
}

Bu basit bir kullanım. Ama burada dikkat edilmesi gereken bir şey var: request.getSession(true) çağrıldığında, eğer session yoksa sunucu yeni bir tane oluşturur. Her oluşturma işlemi bellek ayırma, JSESSIONID cookie üretme ve dahili veri yapılarını başlatma anlamına gelir. Düşük trafikli bir uygulama için sorun değil. Ama saniyede binlerce istek alan bir e-ticaret sitesinde, bu maliyet hissedilir.

Session Lifecycle (Yaşam Döngüsü)

Bir session'ın hayatı şu aşamalardan geçer:

  1. Oluşturma (Creation): getSession(true) veya ilk istek ile

  2. Aktif Kullanım (Active): setAttribute() / getAttribute() ile veri okuma-yazma

  3. Boşta Bekleme (Idle): Kullanıcı aktif değil ama session hâlâ bellekte

  4. Timeout: maxInactiveInterval süresince erişilmezse otomatik sonlandırılır

  5. Invalidation (Geçersiz Kılma): session.invalidate() ile manuel sonlandırma

  6. Yok Etme (Destruction): Bellek serbest bırakılır, GC devreye girer

💡 İpucu: Tomcat'te varsayılan session timeout 30 dakikadır. Bunu web.xml dosyasında veya programatik olarak değiştirebilirsiniz:

>

``xml <session-config> <session-timeout>15</session-timeout> <!-- dakika cinsinden --> </session-config> ``

>

Ya da kod içinde: ``java session.setMaxInactiveInterval(900); // 900 saniye = 15 dakika ``


Object Pool Design Pattern ve Session Pooling

Session pooling aslında Gang of Four'un (GoF) klasik tasarım kalıplarından biri olan Object Pool Pattern'in bir uygulamasıdır. Fikir basit: pahalı nesneleri tekrar tekrar yaratmak yerine, bir havuzda hazır bulundur ve ihtiyaç olduğunda ödünç ver.

Şimdi basit ama işlevsel bir session pool implementasyonu yazalım:

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class SessionPool {

    // Havuzdaki boş (kullanılabilir) session nesneleri
    private final ConcurrentLinkedQueue<PooledSession> availablePool;

    // Aktif olarak kullanılan session'lar (sessionId → PooledSession)
    private final Map<String, PooledSession> activeSessions;

    private final int maxPoolSize;
    private final long sessionTimeoutMs;

    public SessionPool(int maxPoolSize, long sessionTimeoutMs) {
        this.maxPoolSize = maxPoolSize;
        this.sessionTimeoutMs = sessionTimeoutMs;
        this.availablePool = new ConcurrentLinkedQueue<>();
        this.activeSessions = new ConcurrentHashMap<>();

        // Havuzu önceden doldur (pre-warming)
        for (int i = 0; i < maxPoolSize / 2; i++) {
            availablePool.offer(createNewSession());
        }
        System.out.printf("Session Pool başlatıldı: %d session hazır%n",
                availablePool.size());
    }

    /**
     * Havuzdan bir session tahsis eder. Havuz boşsa yeni oluşturur.
     */
    public PooledSession borrowSession() {
        PooledSession session = availablePool.poll();

        if (session == null) {
            if (activeSessions.size() < maxPoolSize) {
                session = createNewSession();
            } else {
                throw new IllegalStateException(
                    "Session havuzu dolu! Maks kapasite: " + maxPoolSize);
            }
        }

        session.activate();
        activeSessions.put(session.getSessionId(), session);
        return session;
    }

    /**
     * Session'ı havuza geri iade eder.
     */
    public void returnSession(String sessionId) {
        PooledSession session = activeSessions.remove(sessionId);
        if (session != null) {
            session.reset(); // Verileri temizle
            availablePool.offer(session);
        }
    }

    /**
     * Süresi dolmuş session'ları tespit edip havuza geri gönderir.
     */
    public int evictExpiredSessions() {
        long now = System.currentTimeMillis();
        int evicted = 0;

        var iterator = activeSessions.entrySet().iterator();
        while (iterator.hasNext()) {
            var entry = iterator.next();
            PooledSession session = entry.getValue();

            if (now - session.getLastAccessTime() > sessionTimeoutMs) {
                iterator.remove();
                session.reset();
                availablePool.offer(session);
                evicted++;
            }
        }

        if (evicted > 0) {
            System.out.printf("%d adet süresi dolmuş session temizlendi%n", evicted);
        }
        return evicted;
    }

    private PooledSession createNewSession() {
        return new PooledSession(UUID.randomUUID().toString());
    }

    public int getActiveCount()    { return activeSessions.size(); }
    public int getAvailableCount() { return availablePool.size(); }
}

Ve PooledSession sınıfımız:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PooledSession {

    private final String sessionId;
    private final Map<String, Object> attributes;
    private long creationTime;
    private long lastAccessTime;
    private boolean active;

    public PooledSession(String sessionId) {
        this.sessionId = sessionId;
        this.attributes = new ConcurrentHashMap<>();
        this.creationTime = System.currentTimeMillis();
        this.lastAccessTime = this.creationTime;
        this.active = false;
    }

    public void activate() {
        this.active = true;
        this.lastAccessTime = System.currentTimeMillis();
    }

    /**
     * Session'ı sıfırlar — veriler temizlenir ama nesne yok edilmez.
     * Otel analojisi: oda temizleniyor, yeni misafir için hazırlanıyor.
     */
    public void reset() {
        this.attributes.clear();
        this.active = false;
        this.lastAccessTime = System.currentTimeMillis();
    }

    public void setAttribute(String key, Object value) {
        this.lastAccessTime = System.currentTimeMillis();
        this.attributes.put(key, value);
    }

    @SuppressWarnings("unchecked")
    public <T> T getAttribute(String key) {
        this.lastAccessTime = System.currentTimeMillis();
        return (T) this.attributes.get(key);
    }

    public void removeAttribute(String key) {
        this.attributes.remove(key);
    }

    // Getter'lar
    public String  getSessionId()     { return sessionId; }
    public long    getCreationTime()  { return creationTime; }
    public long    getLastAccessTime(){ return lastAccessTime; }
    public boolean isActive()         { return active; }
}

Bu implementasyonda birkaç kritik tasarım kararı var:

  • `ConcurrentLinkedQueue` kullanıyoruz çünkü birden fazla thread aynı anda havuzdan session isteyebilir. Bu yapı lock-free ve thread-safe.

  • `ConcurrentHashMap` aktif session'ları takip etmek için ideal — yüksek eşzamanlılıkta (high concurrency) bile performanslı.

  • Pre-warming: Havuzu başlangıçta yarı kapasiteyle dolduruyoruz. Bu, ilk isteklerin session oluşturma maliyetini ödemesini engeller.

  • `reset()` metodu: Session nesnesini yok etmek yerine temizleyip yeniden kullanıma sunuyoruz. İşte havuzlamanın (pooling) tüm amacı bu.


Thread-Safe Session Management

Çok iş parçacıklı (multi-threaded) bir ortamda session yönetimi ciddi bir dikkat gerektirir. Aynı kullanıcı, aynı anda birden fazla AJAX isteği gönderebilir ve hepsi aynı session'a erişmeye çalışabilir.

⚠️ Dikkat: HttpSession thread-safe değildir! Servlet spesifikasyonu, container'ın session nesnesini senkronize edeceğini garanti etmez. Bu sorumluluğu siz almalısınız.

Düşünün: bir kullanıcı aynı anda iki sekme açıp iki farklı ürünü sepete ekliyor. İkisi de aynı session'a yazıyor. Race condition (yarış durumu) kaçınılmaz.

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ThreadSafeSepetManager {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * Sepete thread-safe şekilde ürün ekler.
     * ReadWriteLock kullanıyoruz: okuma işlemleri paralel çalışabilir,
     * yazma işlemleri ise exclusive erişim gerektirir.
     */
    public void urunEkle(PooledSession session, String urun) {
        lock.writeLock().lock();
        try {
            @SuppressWarnings("unchecked")
            List<String> sepet = session.getAttribute("sepet");

            if (sepet == null) {
                // Synchronized list kullan — ekstra güvenlik katmanı
                sepet = Collections.synchronizedList(new ArrayList<>());
                session.setAttribute("sepet", sepet);
            }

            sepet.add(urun);
            session.setAttribute("sepetSonGuncelleme", System.currentTimeMillis());

        } finally {
            lock.writeLock().unlock(); // Her zaman finally'de unlock!
        }
    }

    /**
     * Sepet içeriğini okur — birden fazla thread aynı anda okuyabilir.
     */
    public List<String> sepetGetir(PooledSession session) {
        lock.readLock().lock();
        try {
            List<String> sepet = session.getAttribute("sepet");
            return sepet != null
                    ? Collections.unmodifiableList(new ArrayList<>(sepet))
                    : Collections.emptyList();
        } finally {
            lock.readLock().unlock();
        }
    }
}

Burada `ReadWriteLock` kullanmamızın sebebi performans. Çoğu web uygulamasında okuma işlemleri yazma işlemlerinden çok daha fazladır. ReadWriteLock, birden fazla thread'in aynı anda okumasına izin verirken, yazma anında exclusive erişim sağlar. Normal synchronized bloğu kullanırsanız, okumalar bile birbirini bekler — bu da gereksiz bir darboğaz (bottleneck) yaratır.


HikariCP Analojisi: Session Pool'u Daha İyi Anlamak

HikariCP, Java dünyasının en hızlı connection pool kütüphanesidir. Session pooling ile karşılaştırmak, konsepti somutlaştırır.

HikariCP'de bir bağlantının hayatı şöyledir:

Havuz → Uygulama (borrow) → SQL çalıştır → Havuza geri ver (return)

Session pool'da ise:

Havuz → Kullanıcı isteği (borrow) → Veri oku/yaz → Boşta kal → Timeout/Logout (return)

İkisi arasındaki temel fark ödünç alma süresi. Bir veritabanı bağlantısı milisaniyeler içinde ödünç alınıp geri verilir. Ama bir session dakikalarca, hatta saatlerce aktif kalabilir. Bu yüzden session pool'lar genellikle daha büyük kapasiteyle yapılandırılır ve timeout mekanizması kritik önem taşır.

HikariCP'nin yapılandırma parametrelerine benzer şekilde, bir session pool'da da şu parametreleri düşünmelisiniz:

  • `maximumPoolSize`: Aynı anda aktif olabilecek maksimum session sayısı

  • `minimumIdle`: Havuzda her zaman hazır bekleyen minimum session sayısı

  • `idleTimeout`: Kullanılmayan session'ın havuzda ne kadar kalacağı

  • `maxLifetime`: Bir session nesnesinin toplam ömrü (ne kadar süredir yeniden kullanılıyor olursa olsun)


Spring Session ile Redis-Backed Session Management

Gerçek dünya uygulamalarında session yönetimi genellikle Spring Session ile yapılır. Özellikle birden fazla sunucu (cluster) çalıştırıyorsanız, session'ları tek bir sunucunun belleğinde tutmak yetmez — Redis veya Hazelcast gibi dağıtık bir depoya (distributed store) ihtiyacınız olur.

Spring Session + Redis entegrasyonu, session pooling'in en yaygın production-grade uygulamasıdır. Yapılandırma şaşırtıcı derecede basit:

// build.gradle veya pom.xml'e eklenecek bağımlılıklar:
// spring-boot-starter-data-redis
// spring-session-data-redis

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 30 dakika
public class SessionConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // Lettuce, Jedis'e göre daha performanslı — non-blocking I/O destekler
        LettuceConnectionFactory factory = new LettuceConnectionFactory();
        factory.setHostName("redis-cluster.internal");
        factory.setPort(6379);
        return factory;
    }
}

Bu tek @EnableRedisHttpSession anotasyonu, şunları otomatik olarak yapar:

  1. Tomcat'in varsayılan HttpSession implementasyonunu Spring Session ile değiştirir

  2. Session verilerini Redis'e yazar (serialize ederek)

  3. Her istekte session'ı Redis'ten okur (deserialize ederek)

  4. Session timeout'unu Redis'in TTL (Time To Live) mekanizmasıyla yönetir

  5. Cluster ortamında herhangi bir sunucu herhangi bir session'a erişebilir — sticky session'a gerek kalmaz

💡 İpucu: Redis-backed session kullanırken, session'a koyduğunuz tüm nesnelerin Serializable arayüzünü implement ettiğinden emin olun. Aksi halde SerializationException alırsınız ve bu hata genellikle production'da, en beklenmedik anda ortaya çıkar.


Gerçek Dünya Senaryoları

Senaryo 1: E-Ticaret Sepet Yönetimi

Bir e-ticaret sitesinde kullanıcının alışveriş sepeti session'da tutulur. Black Friday gibi yoğun dönemlerde saniyede binlerce session oluşur. Session pool olmadan:

  • 100.000 eşzamanlı kullanıcı × ~2KB session verisi = ~200MB sadece session için bellek kullanımı

  • Her session oluşturma/yok etme döngüsü GC baskısı yaratır

  • GC pause'ları kullanıcı deneyimini bozar (latency spike)

Session pool ile:

  • Önceden tahsis edilmiş nesneler GC baskısını azaltır

  • reset() ile nesne yeniden kullanılır — allocation maliyeti bir kere ödenir

  • Havuz boyutu sınırlı olduğundan bellek kullanımı öngörülebilir

Senaryo 2: Çoklu Cihaz Login State

Bir kullanıcı telefondan, tabletten ve bilgisayardan aynı anda giriş yapıyor. Her cihaz farklı bir session'a sahip ama hepsi aynı kullanıcıya ait. Bu durumda:

  • Session'lar cihaz bazında ayrı tutulur ama kullanıcı bazında ilişkilendirilir

  • "Tüm cihazlardan çıkış yap" özelliği, belirli bir kullanıcıya ait tüm session'ları invalidate etmeyi gerektirir

  • Redis-backed session'larda bu, bir pattern scan ile kolayca yapılabilir:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;

@Service
public class SessionInvalidationService {

    private final RedisTemplate<String, Object> redisTemplate;

    public SessionInvalidationService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * Belirli bir kullanıcının TÜM session'larını sonlandırır.
     * Kullanım: şifre değişikliği, güvenlik ihlali, "heryerden çıkış yap"
     */
    public long invalidateAllSessions(String userId) {
        // Spring Session, Redis'te "spring:session:*" pattern'inde saklar
        String pattern = "spring:session:sessions:*";
        Set<String> keys = redisTemplate.keys(pattern);

        long invalidated = 0;
        if (keys != null) {
            for (String key : keys) {
                Object owner = redisTemplate
                        .opsForHash()
                        .get(key, "sessionAttr:userId");

                if (userId.equals(owner)) {
                    redisTemplate.delete(key);
                    invalidated++;
                }
            }
        }

        System.out.printf("Kullanıcı %s için %d session sonlandırıldı%n",
                userId, invalidated);
        return invalidated;
    }
}

⚠️ Dikkat: Production ortamında keys() komutu yerine scan() kullanın! keys() tüm key'leri tek seferde döndürür ve Redis'i bloklar. SCAN ise cursor-based çalışır ve Redis'i bloklamaz.


Performance Tuning İpuçları

Session pool'unuzun performansını optimize etmek için şu noktalara dikkat edin:

1. Session Boyutunu Minimize Edin

Session'a koyduğunuz her byte, bellek tüketir ve serializasyon/deserializasyon maliyeti yaratır. Session'a sadece gerçekten gerekli verileri koyun.

Yanlış yaklaşım:

// Tüm kullanıcı nesnesini session'a koymak — gereksiz veri yükü!
session.setAttribute("kullanici", kullaniciRepository.findById(id));

Doğru yaklaşım:

// Sadece gereken bilgileri koy
session.setAttribute("userId", kullanici.getId());
session.setAttribute("roller", kullanici.getRolleri());
// Detaylar gerektiğinde veritabanından çek

2. Timeout Değerlerini Doğru Ayarlayın

  • Çok kısa timeout → Kullanıcılar sürekli logout olur, kötü deneyim

  • Çok uzun timeout → Gereksiz bellek kullanımı, güvenlik riski

  • Önerilen: Uygulama türüne göre 15-30 dakika. Bankacılık uygulamalarında 5-10 dakika

3. Session Replication Stratejisi

Cluster ortamında session'ları nasıl paylaşacağınıza karar verin:

  • Sticky Session: Load balancer aynı kullanıcıyı hep aynı sunucuya yönlendirir. Basit ama sunucu düşerse session kaybolur.

  • Session Replication: Tüm sunucular tüm session'ları tutar. Tutarlı ama yüksek bellek ve ağ maliyeti.

  • Centralized Store (Redis/Hazelcast): En iyi denge — session'lar merkezi depoda, herhangi bir sunucu erişebilir.

4. Lazy Session Creation

Her isteğe session oluşturmayın. Statik kaynaklar (CSS, JS, resim) için session gereksizdir:

// Session'ı sadece gerçekten gerektiğinde oluştur
HttpSession session = request.getSession(false); // false = yoksa oluşturma!
if (session == null && kullaniciGirisYapmisMi(request)) {
    session = request.getSession(true); // Sadece login durumunda oluştur
}

5. Monitoring ve Metrikler

Session pool'unuzu izlemeden optimize edemezsiniz. Şu metrikleri takip edin:

  • Aktif session sayısı

  • Havuzdaki boş session sayısı

  • Session oluşturma/yok etme hızı

  • Ortalama session süresi

  • Session boyutu (serialize edilmiş haliyle)


Yaygın Hatalar ve Tuzaklar

❌ Hata 1: Session'a Büyük Nesneler Koymak

// YANLIŞ — tüm sorgu sonucunu session'a koymak
List<Urun> tumUrunler = urunService.hepsiniGetir(); // 10.000 ürün!
session.setAttribute("urunler", tumUrunler);

Bu, her session için megabyte'larca bellek tüketir. Üstelik Redis-backed session'larda her istek bu veriyi serialize/deserialize eder — latency patlar.

Çözüm: Sadece ID'leri veya sayfalanmış (paginated) küçük veri setlerini tutun. Büyük veri seti gerekiyorsa cache (önbellek) kullanın.

❌ Hata 2: Session Fixation Saldırısına Açık Olmak

Session fixation, saldırganın bilinen bir session ID'yi kurbana dayatmasıdır. Login sonrası session ID'yi yenilemezsaniz bu saldırıya açık olursunuz.

// Login başarılı olduktan SONRA mutlaka yeni session oluşturun
HttpSession eskiSession = request.getSession(false);
if (eskiSession != null) {
    eskiSession.invalidate(); // Eski session'ı yok et
}
HttpSession yeniSession = request.getSession(true); // Yeni ID ile oluştur
yeniSession.setAttribute("userId", authenticatedUser.getId());

Spring Security bunu otomatik yapar (sessionFixation().migrateSession()), ama framework kullanmıyorsanız manuel yapmalısınız.

❌ Hata 3: Session'ı Synchronized Block Dışında Değiştirmek

// YANLIŞ — race condition!
List<String> sepet = (List<String>) session.getAttribute("sepet");
sepet.add(yeniUrun); // Başka bir thread aynı anda aynı listeyi değiştirebilir!
session.setAttribute("sepet", sepet);

Çözüm: Ya ReadWriteLock kullanın (yukarıdaki örnekteki gibi), ya da session'daki nesneleri immutable (değişmez) tutun — her değişiklikte yeni bir kopya oluşturup session'a set edin.

❌ Hata 4: Session Timeout'u Test Etmemek

Geliştiricilerin büyük çoğunluğu session timeout'u sadece production'da keşfeder. Uygulamanız timeout sonrası düzgün çalışıyor mu? Session yokken erişilen sayfalar anlamlı bir hata mesajı gösteriyor mu? Kullanıcı login sayfasına yönlendiriliyor mu?

Bu senaryoları test ortamında simüle edin. Timeout süresini geçici olarak 1 dakikaya düşürüp uygulamanın davranışını kontrol edin.

❌ Hata 5: Logout'ta Session'ı Temizlememek

// YANLIŞ — sadece attribute silmek yetmez
session.removeAttribute("userId");
session.removeAttribute("sepet");
// Session hâlâ hayatta ve session ID geçerli!

// DOĞRU — session'ı tamamen invalidate et
session.invalidate();

removeAttribute() verileri siler ama session nesnesi yaşamaya devam eder. invalidate() ise session'ı tamamen sonlandırır ve cookie'yi geçersiz kılar.


Best Practices Özeti

  1. Session'a minimum veri koyun — ID'ler ve roller yeterli, büyük nesneleri cache'e taşıyın

  2. Thread-safety'yi garanti edinReadWriteLock veya immutable nesneler kullanın

  3. Login sonrası session ID'yi yenileyin — Session fixation saldırılarını önleyin

  4. Lazy session creation uygulayın — Her istek için session oluşturmayın

  5. Timeout değerlerini bilinçli seçin — Uygulama türüne göre 5-30 dakika arası

  6. Cluster ortamında Redis/Hazelcast kullanın — Sticky session'a güvenmeyin

  7. Session metriklerini izleyin — Aktif sayısı, boyut, oluşturma hızı

  8. Session'daki nesnelerin `Serializable` olduğundan emin olun — Redis/Hazelcast kullanıyorsanız zorunlu

  9. Logout'ta `invalidate()` çağırınremoveAttribute() yetmez

  10. Session timeout senaryolarını test edin — Production'da sürprizlerle karşılaşmayın


Kapanış

Session pooling, Java web uygulamalarında performansın ve ölçeklenebilirliğin (scalability) temel taşlarından biridir. Konsept olarak basit — nesneleri yeniden kullan, gereksiz yere yaratıp yok etme — ama doğru uygulamak dikkat gerektirir.

Hatırlayın: otel analojisine geri dönersek, iyi bir otel sadece odaları temiz tutmaz, aynı zamanda doluluk oranını izler, bakım zamanlarını planlar ve misafir deneyimini optimize eder. Session pool'unuz da aynı özeni hak ediyor.

İster Servlet API ile sıfırdan yazın, ister Spring Session + Redis ile enterprise-grade bir çözüm kurun — bu yazıdaki prensipleri takip ettiğiniz sürece sağlam, güvenli ve performanslı bir session yönetimine sahip olacaksınız.

Kodunuz temiz, session'larınız güvende olsun. 🚀

Paylaş:
Son güncelleme: Jun 04, 2026

Yorumlar

Giriş yapın ve yorum bırakın.

Henüz yorum yok

Düşüncelerinizi paylaşan ilk siz olun!

Bu yazıyı beğendiniz mi?

Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.

İlgili Yazılar