• 26.08.2026 21:29:51
  • Admin Admin

ASP.NET Core servislerinde container bellek limiti altında GC baskısını ölçmeyi, allocation kaynaklarını ayırmayı ve EF Core ile Blazor kaynaklı yaşam süresi hatalarını somut araçlarla inceleyin.

ASP.NET Core Eğitimi: Container Bellek Sınırında GC Tanılama Rehberi

ASP.NET Core Eğitimi ile GC baskısını tahmin etmek yerine ölçmek

Bir ASP.NET Core pod'u OOMKilled olduğunda ilk ayrım şudur: managed heap gerçekten limiti mi doldurdu, yoksa native bellek, thread stack'leri veya büyük response buffer'ları mı süreci büyüttü? .net core eğitimi kapsamında bu ayrımı yapmak için aynı yük altında en az 5 dakika iki ölçüm alın: `gc-heap-size`, `allocation-rate`, `gen-2-gc-count`, `time-in-gc` ve container'ın RSS değeri. `gc-heap-size` sabit kalırken RSS büyüyorsa yalnızca GC tuning yapmak yanlış teşhistir; örneğin TLS buffer'ları, ImageSharp gibi native kullanan paketler veya kontrolsüz thread sayısı araştırılmalıdır.

# Canli pod icinde once PID'yi bulun
dotnet-counters monitor --process-id 1 System.Runtime

# Heap dump alma maliyeti daha dusuk olan GC dump
# Dump'i uygulama dizinine yazin ve lokal makinede inceleyin
dotnet-gcdump collect --process-id 1 --output /tmp/orders.gcdump
dotnet-gcdump report /tmp/orders.gcdump

Önce-sonra karşılaştırmasında yalnızca ortalama gecikmeyi kullanmayın. k6 veya bombardier ile sabit istek hızı, aynı payload ve en az 60 saniyelik warm-up uygulayın; ardından p95 gecikme, istek başına allocation ve Gen 2 koleksiyon sayısını birlikte kaydedin. Örneğin bir değişiklik p95'i 80 ms'den 65 ms'ye indirirken `time-in-gc` oranını yüzde 4'ten yüzde 18'e çıkarıyorsa, kısa testte iyi görünen sonuç daha uzun trafikte tail latency sıçraması üretebilir. Bu ölçüm disiplini, bir .net core kursu içinde ezberlenen GC ayarlarından daha değerlidir.

Container limiti, GC heap bütçesi ve native bellek farkı

Kubernetes `resources.limits.memory` sürecin toplam adres alanını değil, cgroup tarafından izlenen toplam fiziksel bellek tüketimini sınırlar. GC heap'i bunun yalnızca bir parçasıdır. Bu nedenle 1 GiB limitli bir pod'da heap'e teorik olarak tamamını bırakmak güvenli değildir: JIT kodu, loader heap, socket buffer'ları, thread stack'leri ve native kütüphaneler için pay gerekir. `DOTNET_GCHeapHardLimitPercent` GC'nin kullanabileceği heap bütçesini sınırlar, fakat process RSS için mutlak koruma sağlamaz.

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: api
        image: registry.example/orders-api:stable
        resources:
          requests:
            memory: 768Mi
            cpu: 500m
          limits:
            memory: 1Gi
            cpu: '2'
        env:
        - name: DOTNET_GCHeapHardLimitPercent
          value: '75'
        - name: DOTNET_gcServer
          value: '1'

Bu örnekte yüzde 75, 1 GiB container limiti için GC heap'e yaklaşık 768 MiB üst sınır bırakır; kalan alan native tüketim dalgalanmaları içindir. Ancak değeri kopyalayarak kullanmayın. `dotnet-counters` ile değişiklik öncesi ve sonrası `gc-heap-size` tepe değerini, Kubernetes metriklerinden de `container_memory_working_set_bytes` tepe değerini karşılaştırın. Tek CPU limitli küçük pod'larda Server GC her zaman kazanmaz; heap başına GC worker'larının CPU rekabetini artırabilir. Aynı deployment manifest'i ile Server GC açık ve kapalı iki test koşusu yapmak gerekir.

Uygulamanın çalışma anında gördüğü bütçeyi health endpoint veya log üzerinden doğrulamak önemlidir. Bu kontrol, yanlış cgroup algılama, manifest'in yanlış namespace'e uygulanması ya da environment variable adındaki yazım hatasını deploy anında görünür kılar. csharp eğitimi ve csharp kursu materyallerinde sık atlanan ayrıntı, `TotalAvailableMemoryBytes` değerinin heap bütçesi hakkında bilgi verdiği, process'in toplam RSS değeri olmadığıdır.

using System.Runtime;

app.MapGet("/diagnostics/gc", () =>
{
    var info = GC.GetGCMemoryInfo();
    return Results.Ok(new
    {
        HeapBytes = GC.GetTotalMemory(forceFullCollection: false),
        AvailableHeapBytes = info.TotalAvailableMemoryBytes,
        HighMemoryLoadThresholdBytes = info.HighMemoryLoadThresholdBytes,
        MemoryLoadBytes = info.MemoryLoadBytes,
        LatencyMode = GCSettings.LatencyMode
    });
});

Entity Framework Eğitimi: sorgu şeklinin allocation maliyeti

Entity Framework Core tarafında GC baskısının yaygın kaynağı, yalnızca SQL'in yavaş olması değil, gereksiz entity grafiğinin materialize edilmesidir. Liste endpoint'i için `Include` ile `Order`, `Customer`, `Lines` ve `Product` grafiğini çekmek, her satır için change tracker girdileri ve navigation koleksiyonları oluşturabilir. Salt okunur bir liste için DTO projection kullanın; SQL'e yalnızca response'ta gereken kolonların gitmesini `ToQueryString()` ile doğrulayın.

var orders = await db.Orders
    .Where(x => x.CreatedAt >= from && x.CreatedAt < to)
    .OrderByDescending(x => x.CreatedAt)
    .Take(100)
    .Select(x => new OrderListItem(
        x.Id,
        x.Customer.Name,
        x.Total,
        x.Lines.Count))
    .ToListAsync(cancellationToken);

// SQL'i testte veya gelistirme ortaminda inceleyin:
var sql = db.Orders.Where(x => x.CreatedAt >= from).ToQueryString();

Saf DTO projection'da `AsNoTracking()` eklemek zararsız olsa da tracking yapılacak entity kalmadığı için allocation'ı anlamlı biçimde azaltmaz. Asıl kazanç projection'dır. Buna karşılık entity'leri gerçekten döndürmeniz gerekiyorsa `AsNoTracking()` change tracker girdilerini önler. Birden fazla collection `Include` edildiğinde cartesian explosion riskini `AsSplitQuery()` ile azaltabilirsiniz; fakat bu kez birden çok SQL sorgusu çalışır ve varsayılan izolasyon seviyesinde sorgular arasında veri değişirse tutarsız bir görünüm oluşabilir. Transaction gereksinimini endpoint semantiğine göre değerlendirin.

entity framework eğitimi içinde uygulanabilir bir doğrulama, endpoint başına allocation bütçesi koymaktır. Önce mevcut endpoint'e 100 sabit kayıtla yük verin, `allocation-rate` değerini kaydedin, sonra projection'a geçin ve aynı senaryoyu tekrar edin. `dotnet-gcdump report` çıktısında `InternalEntityEntry`, entity tipi ve `List<T>` örneklerinin retained size değerleri belirginse, sorgu şekli doğrudan heap basıncı yaratıyordur.

Blazor Eğitimi: circuit yaşam süresinde bellek tutmayı önlemek

Interactive server-side Blazor uygulamalarında scoped servislerin ömrü HTTP request'i ile değil, circuit ile ilişkilidir. Bir scoped servis `Channel`, timer, event subscription veya büyük DTO cache'i tutuyorsa, kullanıcı sekmesi açık kaldığı sürece bu nesneler de canlı kalabilir. Bu durum özellikle blazor eğitimi örneklerinde singleton event'e scoped nesne abone edilip unsubscribe edilmediğinde görülür: singleton delegate referansı circuit'in tamamını GC root olarak tutar.

public sealed class LivePriceSubscription : IAsyncDisposable
{
    private readonly CancellationTokenSource _stop = new();
    private readonly Task _pump;

    public LivePriceSubscription(PriceFeed feed)
    {
        _pump = Task.Run(() => feed.PumpAsync(_stop.Token));
    }

    public async ValueTask DisposeAsync()
    {
        _stop.Cancel();
        try { await _pump; }
        catch (OperationCanceledException) { }
        _stop.Dispose();
    }
}

builder.Services.AddScoped<LivePriceSubscription>();

Bu örnekte `IAsyncDisposable`, circuit kapanırken devam eden pump görevini iptal eder. Gerçek feed kodunda sınırsız `Channel.CreateUnbounded` kullanmak yerine `BoundedChannelOptions` ile kapasite belirleyin ve kullanıcıya her ara fiyatı göstermek gerekmiyorsa `DropOldest` seçin. Aksi halde yavaş bir circuit, producer hızında mesaj biriktirir ve heap büyümesi kullanıcı sayısından bağımsız olarak devam eder. c# eğitimi veya c# kursu alırken `Dispose` çağrısının tek başına yeterli olduğu varsayımı yaygındır; iptal edilmeyen async görevlerin tuttuğu closure'lar da nesneleri canlı bırakabilir.

Leak şüphesinde önce staging ortamında `dotnet-gcdump collect` alın, kullanıcı circuit'lerini kapattıktan sonra ikinci dump'ı toplayın. İki dump arasında `LivePriceSubscription`, component tipi veya DTO koleksiyonlarının instance sayısı düşmüyorsa, `dotnet-gcdump report` içindeki root path'i inceleyin. Bu yöntem, sadece process memory grafiğine bakmaktan daha kesin biçimde hangi delegate veya singleton'ın referansı tuttuğunu gösterir.

Microsoft teknolojileri eğitimi için tekrarlanabilir yük testi kapısı

GC değişikliğini production'a almadan önce CI içinde tekrarlanabilir bir performans kapısı kurun. Aynı container image, aynı CPU ve bellek limitleri, sabit test verisi ve aynı k6 senaryosu kullanılmalıdır. Karşılaştırma için release commit'ini baseline olarak saklayın; aday commit p95, hata oranı ve allocation-rate eşiğini aşarsa pipeline başarısız olmalıdır. Bu yaklaşım, microsoft teknolojileri eğitimi bağlamında gözle yapılan lokal test ile container altındaki davranış arasındaki farkı kapatır.

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

export const options = {
  vus: 40,
  duration: '3m',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<250']
  }
};

export default function () {
  const response = http.get(`${__ENV.BASE_URL}/api/orders?take=100`);
  check(response, { 'HTTP 200': r => r.status === 200 });
}

k6 çalışırken ayrı bir process'te `dotnet-counters monitor --process-id 1 System.Runtime` çıktısını dosyaya yazın. Baseline'a göre p95 aynı kalmış olsa bile allocation-rate yüzde 30 yükseldiyse değişikliği inceleyin; bu genellikle daha sonraki Gen 2 frekansının habercisidir. Tersine, daha düşük allocation uğruna database'e çok sayıda split query göndermek connection pool beklemesini artırabilir. Bu nedenle karar metriği en az p95, SQL command sayısı, allocation-rate ve `time-in-gc` dörtlüsünü içermelidir.

İlgili Eğitim

.NET Core Eğitimi

Sık Sorulan Sorular

.net core eğitimi alırken container bellek limiti için GCHeapHardLimitPercent nasıl seçilir?

Önce gerçek yükte `container_memory_working_set_bytes` ile `gc-heap-size` tepe değerlerini ölçün. Limit ile heap tepe değeri arasındaki fark native bellek ve stack tüketimi için yeterli değilse, yüzde bazlı heap bütçesi belirleyin. 1 GiB limitte yüzde 75 sadece başlangıç hipotezidir; aynı yük altında OOM, p95 ve `time-in-gc` karşılaştırmasıyla doğrulanmalıdır.

entity framework eğitimi için AsNoTracking mi DTO projection mı daha az bellek kullanır?

Salt DTO dönen bir sorguda temel kazanç projection'dır; entity materialization ve navigation grafiği oluşmaz. `AsNoTracking()` entity döndüren read-only sorgularda change tracker maliyetini kaldırır, ancak saf DTO projection'da ek etkisi genellikle sınırlıdır. `ToQueryString()` ile seçilen kolonları, `dotnet-counters` ile allocation-rate'i önce-sonra ölçün.

blazor eğitimi sırasında scoped servis neden memory leak gibi görünür?

Server-side Blazor'da scoped servis circuit boyunca yaşar. Singleton event aboneliği, iptal edilmeyen Task veya sınırsız Channel bu servisi circuit kapansa bile canlı tutabilir. `IAsyncDisposable` ile cancellation uygulayın, event unsubscribe edin ve kapanmış circuit'lerden sonra iki adet `dotnet-gcdump` alarak instance sayısının düştüğünü doğrulayın.

csharp kursu ve asp.net core eğitimi projelerinde GC performansı nasıl regresyon testi yapılır?

Release baseline ve aday commit için aynı k6 senaryosunu aynı container limitleriyle çalıştırın. Her koşuda p95, hata oranı, `allocation-rate`, `gen-2-gc-count` ve `time-in-gc` kaydedin. Yalnız HTTP ortalamasını kıyaslamak yerine bu metriklerden biri eşik dışına çıkarsa pipeline'ı başarısız yapın.

AI / LLM Discovery

Bu makale Opendart Akademi Microsoft / C# 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