• 16.08.2026 03:57:06
  • Admin Admin

Bu rehber, spring mvc uygulamalarında sanal thread kullanımı Tomcat Web Server, HikariCP sınırları, JFR pinning analizi ve Spring Security context aktarımıyla birlikte ölçülebilir biçimde ele alır.

Spring MVC'de Sanal Thread, JDBC Havuzu ve Context Propagation

Spring MVC ve spring rest api i莽in do臒ru kapasite varsay谋m谋

Bir spring mvc uygulamas谋nda sanal thread'e ge莽meden 枚nce darbo臒az谋n HTTP i艧 par莽ac谋臒谋 m谋, JDBC ba臒lant谋s谋 m谋, yoksa uzak servis gecikmesi mi oldu臒unu ay谋r谋n. Actuator ve Micrometer ile http.server.requests p95/p99 s眉relerini, hikaricp.connections.pending de臒erini ve JVM thread say谋s谋n谋 ayn谋 zaman aral谋臒谋nda kaydedin. 脰rne臒in 32 ba臒lant谋l谋k havuzda 300 e艧zamanl谋 istek test ediyorsan谋z, 268 iste臒in JDBC ba臒lant谋s谋 beklemesi sanal thread'in yanl谋艧 de臒il, veritaban谋 kapasitesinin s谋n谋rl谋 oldu臒unu g枚sterir.

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus

spring:
  datasource:
    hikari:
      maximum-pool-size: 32
      minimum-idle: 8
      connection-timeout: 1500
      validation-timeout: 500

HikariCP'de maximum-pool-size de臒erini CPU 莽ekirdek say谋s谋ndan t眉retmek yerine veritaban谋n谋n 枚l莽眉lm眉艧 e艧zamanl谋 sorgu kapasitesinden t眉retin. PostgreSQL taraf谋nda pg_stat_activity, MySQL taraf谋nda performance_schema.threads ile aktif sorgular谋 g枚zlemleyin; uygulamada hikaricp.connections.acquire zamanlay谋c谋s谋n谋n p95 de臒eri y眉kselirken veritaban谋 CPU'su doygunsa havuzu b眉y眉tmek yaln谋zca kuyruk bekleyen istek say谋s谋n谋 veritaban谋na ta艧谋r. Bu ayr谋m, spring rest api y眉k testinde yanl谋艧 kapasite art谋艧lar谋n谋 engeller.

Tomcat isteklerini sanal thread 眉zerinde 莽al谋艧t谋rmak

spring.threads.virtual.enabled ayar谋 uygulama i莽i asenkron y眉r眉t眉c眉leri etkileyebilse de, servlet isteklerinin ger莽ekten sanal thread 眉zerinde 莽al谋艧t谋臒谋n谋 varsaymay谋n. G枚m眉l眉 Tomcat i莽in protocol handler'a a莽谋k莽a bir executor ba臒lamak, istek kabul眉nden controller 莽al谋艧mas谋na kadar hangi y眉r眉t眉c眉n眉n kullan谋ld谋臒谋n谋 g枚r眉n眉r k谋lar. Bu yap谋land谋rma Java'n谋n sanal thread deste臒i olan bir 莽al谋艧ma zaman谋n谋 gerektirir.

@Configuration
class TomcatThreadsConfiguration {

    @Bean(destroyMethod = "close")
    ExecutorService requestExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }

    @Bean
    TomcatProtocolHandlerCustomizer<?> virtualThreadTomcatCustomizer(
            ExecutorService requestExecutor) {
        return protocolHandler -> protocolHandler.setExecutor(requestExecutor);
    }
}

Bu de臒i艧iklik ba臒lant谋 havuzunu, HTTP istemcisinin connection pool'unu veya veritaban谋n谋n maksimum ba臒lant谋 say谋s谋n谋 de臒i艧tirmez: sanal thread bloklanan istek s谋ras谋nda ta艧谋y谋c谋 thread'i serbest b谋rak谋r, fakat bekleyen i艧 yine ayn谋 fiziksel JDBC ba臒lant谋s谋n谋 talep eder. Bu nedenle controller i莽inde bloklayan JDBC 莽a臒r谋s谋 makul olabilirken, s谋n谋rs谋z paralel CompletableFuture 眉retmek uygun de臒ildir. Her alt i艧in ba臒lant谋 alabilmesi i莽in bir s谋n谋r koyun ya da istek ba艧谋na paralel sorgu fan-out'unu 枚l莽眉lm眉艧 havuz kapasitesinin alt谋nda tutun.

JFR ile pinning ve JDBC beklemesini 枚nce-sonra kar艧谋la艧t谋rmak

Sanal thread'lerde kritik edge case, bloklayan I/O'nun synchronized monit枚r眉 i莽indeyken ger莽ekle艧mesidir. Bu durumda ta艧谋y谋c谋 thread pinlenebilir; y眉ksek gecikmeli bir uzak 莽a臒r谋, beklenenden fazla platform thread t眉ketir. Java Flight Recorder kayd谋nda jdk.VirtualThreadPinned olaylar谋n谋 inceleyin ve stack trace'te HTTP istemcisi, JDBC s眉r眉c眉s眉 veya dosya I/O'su ile birlikte g枚r眉nen monitor b枚lgelerini d眉zeltin.

# Y眉k testi boyunca 5 dakika kay谋t al
jcmd <PID> JFR.start name=vt-profile settings=profile duration=5m filename=vt-profile.jfr

# Pinning olaylar谋n谋 metin olarak incele
jfr print --events jdk.VirtualThreadPinned vt-profile.jfr

脰rne臒in a艧a臒谋daki ilk s眉r眉mde uzak 莽a臒r谋 lock alt谋nda yap谋l谋r; ikinci s眉r眉mde lock yaln谋zca payla艧谋lan durum ge莽i艧ini korur. ReentrantLock se莽imi tek ba艧谋na 莽枚z眉m de臒ildir; as谋l kural a臒, disk veya JDBC beklemesini kritik b枚l眉m眉n d谋艧谋na ta艧谋makt谋r.

// Hatal谋: remoteClient.fetch() beklerken monitor tutulur
synchronized (this) {
    cache.put(id, "FETCHING");
    return remoteClient.fetch(id);
}

// D眉zeltilmi艧: sadece durum g眉ncellemesi kilit alt谋nda
lock.lock();
try {
    cache.put(id, "FETCHING");
} finally {
    lock.unlock();
}
return remoteClient.fetch(id);

Kar艧谋la艧t谋rmay谋 ayn谋 veri k眉mesi, ayn谋 connection pool boyutu ve ayn谋 istek da臒谋l谋m谋yla yap谋n. k6 ile 枚nce platform-thread executor, sonra sanal-thread executor 莽al谋艧t谋r谋n; p95 HTTP s眉resi, hata oran谋, hikaricp_connections_pending tepe de臒eri ve JFR'deki pinning olay say谋s谋n谋 birlikte raporlay谋n. Sadece daha y眉ksek RPS g枚rmek yeterli de臒ildir: pending ba臒lant谋 say谋s谋 art谋yorsa sistem daha fazla iste臒i kabul edip veritaban谋 kuyru臒unda bekletiyor olabilir.

import http from 'k6/http';
import { check } from 'k6';

export const options = {
  scenarios: {
    api: { executor: 'constant-vus', vus: 250, duration: '3m' }
  },
  thresholds: {
    http_req_duration: ['p(95)<800'],
    http_req_failed: ['rate<0.01']
  }
};

export default function () {
  const res = http.get('http://localhost:8080/api/orders/42');
  check(res, { '200 d枚nd眉': r => r.status === 200 });
}

Spring Security, MDC ve @Async context aktar谋m谋

Bir istek sanal thread 眉zerinde 莽al谋艧谋rken Spring Security'nin varsay谋lan ThreadLocal tabanl谋 SecurityContext'i istek boyunca kullan谋labilir. Ancak @Async, executor'a yeni bir g枚rev g枚nderdi臒i anda g眉venlik ba臒lam谋 ve SLF4J MDC otomatik olarak ta艧谋nmaz. 脰zellikle audit kayd谋nda kullan谋c谋 kimli臒inin bo艧 gelmesi veya trace ID'nin kaybolmas谋 bu s谋n谋rda ortaya 莽谋kar.

@Configuration
@EnableAsync
class AsyncContextConfiguration {

    @Bean(destroyMethod = "close")
    ExecutorService virtualExecutorService() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }

    @Bean("applicationExecutor")
    AsyncTaskExecutor applicationExecutor(ExecutorService virtualExecutorService) {
        TaskExecutorAdapter delegate = new TaskExecutorAdapter(virtualExecutorService);
        delegate.setTaskDecorator(new MdcTaskDecorator());
        return new DelegatingSecurityContextAsyncTaskExecutor(delegate);
    }
}

final class MdcTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable task) {
        Map<String, String> captured = MDC.getCopyOfContextMap();
        return () -> {
            Map<String, String> previous = MDC.getCopyOfContextMap();
            try {
                if (captured == null) MDC.clear();
                else MDC.setContextMap(captured);
                task.run();
            } finally {
                if (previous == null) MDC.clear();
                else MDC.setContextMap(previous);
            }
        };
    }
}

Asenkron metoda @Async("applicationExecutor") ekleyin ve kullan谋c谋 kimli臒i ile correlation ID'nin loglarda korundu臒unu bir entegrasyon testiyle do臒rulay谋n. Burada s谋k yap谋lan hata, 莽a臒谋ran request transaction'谋n谋n da aktar谋ld谋臒谋n谋 sanmakt谋r: Spring transaction ba臒lam谋 thread'e ba臒l谋d谋r ve ta艧谋nmamal谋d谋r. Asenkron i艧in atomik yazma yapmas谋 gerekiyorsa ayr谋 bir bean 眉zerinden 莽a臒r谋lan @Transactional(propagation = Propagation.REQUIRES_NEW) metodu kullan谋n; ayn谋 bean i莽indeki self-invocation proxy'yi atlayaca臒谋 i莽in anotasyonu etkisiz b谋rakabilir.

Microservices mimarisi ve spring cloud s谋n谋rlar谋nda geri bas谋n莽

Bir microservices mimarisi i莽inde sanal thread ile daha fazla iste臒i e艧zamanl谋 kabul etmek, downstream servislerin de ayn谋 kapasiteye sahip oldu臒u anlam谋na gelmez. spring cloud Gateway katman谋nda retry say谋s谋 kontrol edilmezse 250 kullan谋c谋 iste臒i, bir retry ile 500 downstream 莽a臒r谋s谋na d枚n眉艧ebilir. Retry'yi yaln谋zca idempotent GET istekleriyle s谋n谋rlay谋n; POST i莽in idempotency key ve sunucu taraf谋nda kal谋c谋 deduplikasyon yoksa otomatik retry kullanmay谋n.

spring:
  cloud:
    gateway:
      routes:
        - id: catalog
          uri: http://catalog:8080
          predicates:
            - Path=/api/catalog/**
          filters:
            - name: CircuitBreaker
              args:
                name: catalogCircuit
                fallbackUri: forward:/fallback/catalog
            - name: Retry
              args:
                retries: 1
                methods: GET
                statuses: BAD_GATEWAY,GATEWAY_TIMEOUT

Gateway'deki timeout, controller'daki HTTP client timeout'u ve veritaban谋 connection-timeout de臒erleri istek b眉t莽esine g枚re s谋ralanmal谋d谋r. 脰rne臒in u莽tan uca SLO 900 ms ise gateway timeout'unu 800 ms, uygulaman谋n downstream client timeout'unu 650 ms, Hikari connection timeout'unu 150 ms gibi daha k眉莽眉k e艧ikler halinde ayarlay谋n. Aksi halde gateway iste臒i iptal ettikten sonra uygulama sanal thread'i downstream sonucu beklemeyi s眉rd眉rebilir; bu durum loglarda ba艧ar谋l谋 ama art谋k istemcisi olmayan i艧ler 眉retir.

spring framework e臒itimi i莽in do臒rulanabilir ekip al谋艧t谋rmas谋

Bir spring framework e臒itimi, spring boot e臒itimi veya spring boot kursu kapsam谋nda bu konuyu yaln谋zca Executors.newVirtualThreadPerTaskExecutor() 莽a臒r谋s谋yla bitirmeyin. java spring e臒itimi laboratuvar谋nda kat谋l谋mc谋dan ayn谋 endpoint i莽in iki 莽al谋艧t谋rma istemek daha de臒erlidir: ilkinde platform-thread executor, ikincisinde Tomcat virtual-thread executor; teslim 莽谋kt谋s谋 k6 枚zeti, JFR dosyas谋, Prometheus'tan Hikari pending grafi臒i ve MDC ta艧谋mas谋n谋 do臒rulayan test olmal谋d谋r.

@SpringBootTest
class AuditContextIT {
    @Test
    void async_audit_keeps_authenticated_user_and_trace_id() {
        // MockMvc ile authenticated istek g枚nderin.
        // Async kayd谋n userId ve traceId alanlar谋n谋 Awaitility ile do臒rulay谋n.
        await().atMost(Duration.ofSeconds(2))
               .untilAsserted(() -> assertThat(auditRepository.findLatest().userId())
                   .isEqualTo("alice"));
    }
}

Sık Sorulan Sorular

spring mvc uygulamas谋nda sanal thread kullan谋rken HikariCP havuzu ka莽 ba臒lant谋 olmal谋?

Say谋y谋 sanal thread say谋s谋ndan 眉retmeyin. 脰nce sabit y眉kte hikaricp.connections.acquire p95 de臒erini, veritaban谋 CPU'sunu ve aktif sorgu say谋s谋n谋 枚l莽眉n. Havuz b眉y眉d眉臒眉nde acquire s眉resi d眉艧眉yor ve veritaban谋 CPU'su doygunlu臒a ula艧m谋yorsa art谋艧 anlaml谋 olabilir; CPU doygunken pending de臒erinin y眉kselmesi, havuzun de臒il sorgu maliyetinin veya veritaban谋 kapasitesinin s谋n谋r oldu臒unu g枚sterir.

spring security context @Async ve sanal thread aras谋nda neden kaybolur?

Spring Security varsay谋lan olarak kullan谋c谋 bilgisini SecurityContextHolder i莽indeki ThreadLocal'da tutar. @Async i艧i ba艧ka bir thread'de 莽al谋艧t谋rd谋臒谋 i莽in bu ThreadLocal bo艧 ba艧lar. Executor'谋 DelegatingSecurityContextAsyncTaskExecutor ile sarmalay谋n; log correlation alanlar谋 i莽in ayr谋ca TaskDecorator ile MDC kopyalay谋n. Transaction context'ini ayn谋 y枚ntemle ta艧谋may谋n, asenkron i艧 i莽in ayr谋 transaction a莽谋n.

spring cloud Gateway retry ayar谋 microservices mimarisi i莽inde nas谋l s谋n谋rland谋r谋lmal谋?

Retry'yi GET gibi idempotent metotlarla ve d眉艧眉k bir deneme say谋s谋yla s谋n谋rlay谋n; 枚rnekte retries: 1 kullan谋lm谋艧t谋r. Gateway, uygulama HTTP client'谋 ve veritaban谋 timeout'lar谋n谋 u莽tan uca deadline'谋n alt谋na kademeli yerle艧tirin. Ayr谋ca CircuitBreaker fallback'inin ger莽ek hata oran谋n谋 maskelemedi臒ini do臒rulamak i莽in Gateway'in retry ve circuit breaker metriklerini Prometheus'ta ayr谋 panellerde izleyin.

spring rest api i莽in virtual thread pinning nas谋l tespit edilir?

Y眉k testi s谋ras谋nda jcmd PID JFR.start settings=profile ile JFR kayd谋 al谋n ve jfr print --events jdk.VirtualThreadPinned komutuyla olaylar谋 listeleyin. Stack trace'te synchronized alt谋nda JDBC, HTTP veya dosya I/O g枚r眉rseniz, I/O 莽a臒r谋s谋n谋 kritik b枚l眉m d谋艧谋na 莽谋kar谋n. De臒i艧iklikten sonra ayn谋 k6 senaryosunda pinning olay say谋s谋n谋 ve p95 gecikmeyi birlikte kar艧谋la艧t谋r谋n.

AI / LLM Discovery

Bu makale Opendart Akademi Spring Framework eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt