• 24.09.2026 09:03:49
  • Admin Admin

Spring Boot servislerinde p95 gecikmesini endpoint, bağımlılık ve trace bazında ayırmayı öğrenin. Micrometer, OpenTelemetry ve Prometheus ile ölçülebilir bir spring rest api gözlemlenebilirliği kurun.

Spring Boot'ta Gecikme Bütçesi ve Micrometer Trace Korelasyonu

Spring Boot eğitimi kapsamında gecikme bütçesini ölçülebilir tanımlamak

Bir spring rest api için tek bir ortalama süre hedefi tanımlamak, kuyrukta bekleyen istekleri ve nadir fakat pahalı çağrıları gizler. Önce kullanıcı sözleşmesini p95 ve p99 üzerinden yazın: örneğin GET /orders/{id} için p95 180 ms, p99 450 ms. Bu bütçeyi uygulama, PostgreSQL ve dış HTTP çağrısı olarak parçalara ayırın. Spring Boot Actuator ile HTTP histogramlarını açmadan Prometheus tarafında p95 hesaplayamazsınız; yalnızca sayaç ve toplam süre ile kalırsınız.

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    distribution:
      percentiles-histogram:
        http.server.requests: true
      slo:
        http.server.requests: 50ms,100ms,180ms,450ms,1s
      minimum-expected-value:
        http.server.requests: 5ms
      maximum-expected-value:
        http.server.requests: 5s

Bu yapılandırmadan sonra Prometheus'ta son 15 dakikanın p95 değerini endpoint ve HTTP yöntemi bazında sorgulayın. `uri` etiketi sadece Spring MVC route template'i olan `/orders/{id}` değerini taşımalıdır. Controller içinde dinamik URI üretmek veya özel MeterRegistry etiketiyle `/orders/42` yazmak, her kimlik için yeni time series oluşturur ve Prometheus bellek tüketimini kontrolsüz büyütür.

histogram_quantile(0.95,
  sum by (le, uri, method, status) (
    rate(http_server_requests_seconds_bucket{uri!="UNKNOWN"}[15m])
  )
)

Ölçüme başlamadan önce k6 ile tekrar edilebilir bir taban yük alın. Aynı veri kümesi ve aynı pod sayısıyla 10 dakika boyunca sabit 80 RPS çalıştırın; önce p50, p95, p99, hata oranı ve PostgreSQL bağlantı havuzu aktif bağlantılarını kaydedin. `http_req_duration` tek başına yeterli değildir: k6 tarafındaki istemci süresini, `http_server_requests_seconds` ve bağımlılık span süreleriyle karşılaştırmak ağ geçidi veya servis içi gecikmeyi ayırır.

Spring MVC ve spring rest api trace bağlamını kaybetmeden izlemek

Güncel Spring Boot gözlemlenebilirlik altyapısında Micrometer Tracing, W3C `traceparent` başlığını alıp mevcut span'i istek iş parçacığına bağlar. Spring MVC uygulamasında özel bir Executor kullanıyorsanız bu bağlamı elle taşımadığınızda asenkron görev yeni trace altında görünür. Sonuç olarak bir trace ekranında controller span'i 15 ms görünürken gerçekte 600 ms süren veritabanı işi bağlantısız ayrı bir trace'e düşer.

@Configuration
class AsyncConfig {
  @Bean
  TaskExecutor applicationTaskExecutor(ObservationRegistry registry) {
    var executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(16);
    executor.setMaxPoolSize(32);
    executor.setQueueCapacity(200);
    executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
    executor.initialize();
    return executor;
  }
}

@Service
class InvoiceService {
  private final TaskExecutor applicationTaskExecutor;

  CompletableFuture<Invoice> loadAsync(UUID id) {
    return CompletableFuture.supplyAsync(() -> loadInvoice(id), applicationTaskExecutor);
  }
}

`ContextPropagatingTaskDecorator`, Micrometer context snapshot mekanizmasını kullanarak observation ve trace context'i hedef iş parçacığına kurar. `CompletableFuture.supplyAsync()` çağrısında Executor vermezseniz görev ForkJoinPool.commonPool'a gider; hem context propagasyonu atlanır hem de CPU ağırlıklı ortak havuz, bloklayan JDBC işi nedeniyle doyar. Bu ayrım özellikle java spring eğitimi materyallerinde sık atlanır: trace doğruluğu ile thread pool izolasyonu aynı kod satırında ele alınmalıdır.

Trace örnekleme oranını körlemesine yüzde 100 yapmak yüksek trafikte collector maliyetini artırır. Tempo, Jaeger veya Zipkin'e gönderilen span sayısını önce ölçün. Spring Boot tarafında olasılıksal örneklemeyi düşük tutup, Prometheus histogramlarıyla alarm üretin; hata veya p99 ihlali görülen endpoint için geçici olarak örnekleme oranını yükseltin. Bu yaklaşım, her başarılı isteğin tam trace'ini saklamak yerine metrikteki sapmadan trace'e geçiş sağlar.

management:
  tracing:
    sampling:
      probability: 0.10
    baggage:
      remote-fields: tenant-id
      correlation:
        fields: tenant-id
  otlp:
    tracing:
      endpoint: http://otel-collector:4318/v1/traces

`tenant-id` gibi baggage alanlarını Prometheus etiketi yapmayın. Baggage, log korelasyonu veya downstream kararları için trace bağlamında taşınabilir; metrik etiketi ise sınırlı kardinalite gerektirir. Buna karşılık `region=eu-west-1` veya `dependency=payment-provider` gibi değeri sınırlı alanlar, sorgulanabilir metrik etiketi olarak uygundur.

spring cloud ortamında bağımlılık gecikmesini span ve metrikle ayırmak

Microservices mimarisi içinde bir endpoint'in p95 değeri yükseldiğinde ilk soru 'hangi servis yavaş?' olmamalıdır. Aynı trace'te HTTP client span'inin 300 ms sürmesi, karşı servis CPU sorunu anlamına gelmeyebilir; DNS çözümleme, bağlantı havuzu beklemesi veya TLS handshake de span süresine dahildir. Spring Cloud Gateway, gateway ve servis katmanlarında aynı `traceparent` başlığını ilettiği için kök span'den başlayarak süreleri karşılaştırın.

Reactor Netty kullanan WebClient için bağlantı havuzu bekleme süresini görünür kılmak amacıyla havuz boyutu ve pending acquire sınırını açıkça ayarlayın. Varsayılanları bilmeden sadece timeout artırmak, kuyruktaki isteklerin daha uzun süre kaynak tutmasına neden olur. Aşağıdaki örnekte 50 bağlantı dolduğunda en fazla 100 istek 200 ms bekler, sonra kontrollü hata alır; sınırsız kuyruk oluşmaz.

@Bean
WebClient paymentClient(WebClient.Builder builder) {
  ConnectionProvider pool = ConnectionProvider.builder("payment-pool")
      .maxConnections(50)
      .pendingAcquireMaxCount(100)
      .pendingAcquireTimeout(Duration.ofMillis(200))
      .maxIdleTime(Duration.ofSeconds(20))
      .build();

  HttpClient httpClient = HttpClient.create(pool)
      .responseTimeout(Duration.ofMillis(700));

  return builder
      .baseUrl("https://payment.internal")
      .clientConnector(new ReactorClientHttpConnector(httpClient))
      .build();
}

Değişiklikten önce ve sonra aynı k6 senaryosunu çalıştırın: dış ödeme bağımlılığına 400 ms gecikme enjekte edin, 120 RPS altında `http.server.requests` p95, hata oranı ve Reactor Netty pending acquire sayısını karşılaştırın. Başarı kriteri sadece daha düşük p95 değildir: 200 ms havuz bekleme sınırından sonra 503 veya uygulamanın seçtiği kontrollü hata dönmeli, pod heap'inde büyüyen bekleyen Mono zincirleri oluşmamalıdır. Toxiproxy, bağımlılık gecikmesini bu deney için yeniden üretilebilir biçimde enjekte edebilir.

Bir spring cloud topolojisinde gateway'in route metriği ile backend'in controller metriğini aynı dashboard'da tutun. Gateway p95 40 ms iken backend p95 350 ms ise sorun gateway filtresinde değildir. Tersi durumda ise route predicate, token doğrulama veya gateway'deki body buffering gibi katmanları inceleyin. Bu ayrım, trace'e bakmadan yapılan rastgele replica artırma kararlarını engeller.

spring security maliyetini endpoint gecikmesinden ayrı ölçmek

Spring Security filtresinin maliyetini yalnızca toplam HTTP metriğinden çıkaramazsınız. Örneğin uzak JWKS anahtar yenilemesi veya pahalı bir `PermissionEvaluator`, controller çağrılmadan önce gecikme üretir. Kritik yetki kararlarını `Observation` ile sarmalayarak `security.authorization` adında ayrı bir timer üretin; `outcome` etiketi yalnızca `granted`, `denied` ve `error` gibi sınırlı değerler taşımalıdır.

@Component
class ObservedPermissionEvaluator implements PermissionEvaluator {
  private final ObservationRegistry registry;
  private final PermissionEvaluator delegate;

  @Override
  public boolean hasPermission(Authentication auth, Object target, Object permission) {
    return Observation.createNotStarted("security.authorization", registry)
        .lowCardinalityKeyValue("permission", String.valueOf(permission))
        .observe(() -> delegate.hasPermission(auth, target, permission));
  }

  @Override
  public boolean hasPermission(Authentication auth, Serializable id,
      String type, Object permission) {
    return delegate.hasPermission(auth, id, type, permission);
  }
}

Burada `target` nesnesinin ID'sini tag olarak eklemeyin. `document:7b3...` gibi bir etiket Prometheus cardinality patlaması yaratır. Bunun yerine izin türünü `read`, `write`, `approve` gibi sonlu bir kümeden seçin. Spring Security yapılandırmasında bu evaluator'u method security expression handler'a bağlayın ve ardından `security.authorization` p95 değerini `http_server_requests` p95 ile karşılaştırın.

Örneğin p95 HTTP süresi 240 ms, authorization p95 190 ms ise controller SQL sorgusunu optimize etmek yanlış başlangıç noktasıdır. Önce evaluator içindeki N+1 sorgusunu doğrulayın: Hibernate `org.hibernate.SQL` logunu sadece test ortamında açın veya datasource-proxy ile sorgu sayısını kaydedin. Tek izin kontrolünde 12 sorgu görüyorsanız rol ve kaynak izinlerini tek projection sorgusunda çekmek, ölçülebilir ve doğrudan bir düzeltmedir.

spring boot kursu projelerinde dashboard, alarm ve regresyon testi

Bir spring framework eğitimi veya spring boot eğitimi projesinde dashboard'u sadece CPU ve bellek panelleriyle bitirmeyin. Her endpoint için istek hızı, p95, p99, 5xx oranı ve aktif trace linki bulunmalıdır. Grafana'da p95 paneline Tempo datasource link'i ekleyerek ilgili `uri` ve zaman aralığıyla trace araması açın. Böylece alarm anında log içinde rastgele correlation ID aramak yerine metrikten yavaş trace'lere geçilir.

Alarmı ortalama gecikmeye değil SLO hata bütçesi tüketimine bağlayın. Örneğin 30 günlük yüzde 99.9 erişilebilirlik hedefinde hata bütçesi yüzde 0.1'dir. Prometheus recording rule ile kısa penceredeki hata oranını uzun pencereyle çarpın; ardından Alertmanager'ın `for: 10m` süresiyle geçici deploy dalgalanmalarını filtreleyin.

groups:
- name: api-slo
  rules:
  - record: job:http_5xx_ratio:rate5m
    expr: |
      sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
      /
      sum(rate(http_server_requests_seconds_count[5m]))
  - alert: ApiErrorBudgetBurn
    expr: job:http_5xx_ratio:rate5m > 0.0144
    for: 10m
    labels:
      severity: page
    annotations:
      summary: "API 5xx error budget burn is above 14.4x"

Performans regresyonunu CI içinde yakalamak için k6 testini ayrı bir performans ortamında çalıştırın; paylaşımlı CI runner üzerinde çıkan milisaniye sonuçları güvenilir değildir. Baz sürüm ile aday sürümü aynı container image kaynak sınırları, aynı PostgreSQL snapshot'ı ve aynı k6 senaryosunda ölçün. Kabul eşiğini örneğin 'p95 en fazla yüzde 10 artabilir ve 5xx oranı yüzde 0.1'i geçemez' olarak kodlayın. Bu yöntem, microservices mimarisi büyüdükçe gözlemlenebilirliği dokümantasyon değil teslim kriteri haline getirir.

Sık Sorulan Sorular

Spring Boot eğitiminde p95 gecikme metriği Prometheus ile nasıl hesaplanır?

`management.metrics.distribution.percentiles-histogram.http.server.requests=true` ayarını açın, sonra `histogram_quantile(0.95, sum by (le, uri) (rate(http_server_requests_seconds_bucket[15m])))` sorgusunu kullanın. Percentile histogram kapalıysa bucket serileri oluşmaz ve bu sorgu p95 üretemez.

Spring MVC asenkron kodunda traceparent neden kaybolur?

İş `CompletableFuture.supplyAsync` ile Executor vermeden ForkJoinPool.commonPool'a gönderildiğinde istek thread'indeki observation context otomatik taşınmaz. Micrometer `ContextPropagatingTaskDecorator` eklenmiş bir `ThreadPoolTaskExecutor` kullanın ve bu executor'u tüm asenkron çağrılara açıkça verin.

spring cloud servislerinde WebClient connection pool beklemesi nasıl ölçülür?

Önce Reactor Netty pool için `maxConnections`, `pendingAcquireMaxCount` ve `pendingAcquireTimeout` değerlerini tanımlayın. Toxiproxy ile downstream gecikmesi enjekte edin, aynı k6 yükünde HTTP p95 ve hata oranını önce-sonra karşılaştırın. Trace'te client span süresinin artmasıyla pool sınırına bağlı timeout hatalarını birlikte inceleyin.

spring security PermissionEvaluator metriğinde hangi etiketler güvenlidir?

`permission=read|write|approve` ve `outcome=granted|denied|error` gibi sonlu değer kümeleri güvenlidir. Kullanıcı ID'si, kaynak UUID'si, JWT subject'i veya istek URL'sindeki gerçek kimlik değerleri Prometheus etiketi olmamalıdır; bunlar yüksek cardinality nedeniyle time series sayısını büyütür.

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