Dergi konusundan proje pratiğine
İçeriğe Uygun Hizmet ve Teknik Sayfalar
Neden „High Performance“ hedefi REST içinde Delphi’de sıkça paralellik nedeniyle başarısız olur
Bir High Performance REST Sunucusu Delphi pratikte nadiren yalnızca her istek için düşen saf CPU süresiyle sınırlanır; sınır genellikle kontrolsüz paralelliktir: çok fazla eşzamanlı istek, çok fazla eşzamanlı veritabanı sorgusu veya engelleyici I/O (dosya, ağ, veritabanı). Sonuç „biraz daha yavaş“ gibi değil, bir zincirleme reaksiyon gibidir: daha fazla thread, daha uzun kuyruklar, Connection-Pool çöküşü, artan gecikmeler, istemci tarafında zaman aşımları ve sonunda hâlâ „çalışan“ ama istikrarlı yanıt üretemeyen bir sunucu.
Karşı önlem tek bir hile değil, bilinçli bir aşırı yük davranışı‚dır: Sunucu sınırlarına geldiğinde erken ve deterministik olarak reddetmelidir (tipik olarak HTTP 429 veya 503), istekleri sonsuz bir kuyruğa sokmak yerine. Tam da bunun için bu kaynak parça tasarlanmıştır: mevcut REST uç noktalarına entegre edilebilen, hafif bir Concurrency-Gate (Semaphore) ve zaman aşımı mekanizması — Indy, WebBroker, Horse veya kendi HTTP katmanınızı kullanıyor olmanızdan bağımsız olarak.
Mimari fikir: Concurrency-Gate’i „ağır işlem“ öncesine koymak
Temel fikir basit: Ağır işlem gerektiren kısımdan (veritabanı erişimi, karmaşık raporlar, büyük JSON yanıtları) önce Semaphore’dan bir token rezerve edilir. Boş token yoksa derhal kontrollü bir yanıt verilir. Önemli olan: Bu gate güvenilir şekilde serbest bırakılmalıdır (try/finally) ve gerçekten maliyetli olan kod yoluna yerleştirilmelidir — sadece Request-Handler’ın en başına değil, zira ardından hâlâ parser/router/kimlik doğrulama çalışabilir.
Böylece yük „yok edilmez“, kanala yönlendirilir: Sunucu aynı anda daha az isteği yanıtlar, fakat daha istikrarlı gecikmelerle. Bireysel kurumsal uygulamalarda bu, sentetik benchmark’larda rastgele elde edilen en iyi sürelere kıyasla çoğu zaman daha değerlidir.
Kaynak parçası: Zaman aşımı, 429/503 ve telemetri kancaları ile Request-Limiter
Aşağıdaki Delphi kodu TRestRequestGate sınıfı olarak bir Concurrency-Gate uygular. Bu TSemaphore ( System.SyncObjs‚den; bir semaphore sınırlı eşzamanlı erişimler için bir sayıcıdır) üzerine kuruludur. Gate çağrısı ya bir „Lease“ nesnesi döner (RAII-benzeri: Destructor’da serbest bırakma) ya da derhal bir aşırı yük yanıtı vermeyi seçer. Ek olarak işletmede isteklerin neden reddildiğini görebilmeniz için Logging/Monitoring kancaları vardır.
unit RESTRequestGate;
interface
uses
System.SysUtils,
System.Classes,
System.SyncObjs,
System.Diagnostics;
type
// Logging/Tracing için minimum bağlam; örn. kullanıcı/rota ile genişletilebilir.
TRESTGateContext = record
RequestId: string;
Route: string;
RemoteIp: string;
end;
TRESTOverloadDecision = (odAccepted, odRejectedBusy, odRejectedTimeout);
// İşletme telemetrisi için hook (örn. dosya, Syslog, Prometheus-Exporter, vb.)
TRESTGateEvent = reference to procedure(const Ctx: TRESTGateContext;
Decision: TRESTOverloadDecision;
WaitedMs: Integer;
InFlight: Integer);
// Lease nesnesi: token’in serbest bırakılması Destructor’da.
TRESTGateLease = class
private
FSemaphore: TSemaphore;
FInFlightCounter: PInteger;
FReleased: Boolean;
public
constructor Create(ASem: TSemaphore; ACounter: PInteger);
destructor Destroy; override;
procedure Release;
end;
TRESTRequestGate = class
private
FSem: TSemaphore;
FMaxInFlight: Integer;
FInFlight: Integer;
FOnEvent: TRESTGateEvent;
public
constructor Create(AMaxInFlight: Integer);
destructor Destroy; override;
// TimeoutMs = 0: bekleme yok, hemen 429/503
function TryAcquire(const Ctx: TRESTGateContext; TimeoutMs: Cardinal;
out Lease: TRESTGateLease;
out WaitedMs: Integer;
out Decision: TRESTOverloadDecision): Boolean;
property OnEvent: TRESTGateEvent read FOnEvent write FOnEvent;
property MaxInFlight: Integer read FMaxInFlight;
function InFlight: Integer;
end;
implementation
uses
System.Math;
{ TRESTGateLease }
constructor TRESTGateLease.Create(ASem: TSemaphore; ACounter: PInteger);
begin
inherited Create;
FSemaphore := ASem;
FInFlightCounter := ACounter;
FReleased := False;
end;
destructor TRESTGateLease.Destroy;
begin
Release;
inherited;
end;
procedure TRESTGateLease.Release;
begin
if FReleased then
Exit;
FReleased := True;
// Önce sayacı azalt, sonra semaforu serbest bırak.
TInterlocked.Decrement(FInFlightCounter^);
FSemaphore.Release;
end;
{ TRESTRequestGate }
constructor TRESTRequestGate.Create(AMaxInFlight: Integer);
begin
inherited Create;
if AMaxInFlight <= 0 then
raise EArgumentException.Create(‚AMaxInFlight > 0 olmalıdır‘);
FMaxInFlight := AMaxInFlight;
FInFlight := 0;
// InitialCount = MaxCount = AMaxInFlight
FSem := TSemaphore.Create(nil, AMaxInFlight, AMaxInFlight, “);
end;
destructor TRESTRequestGate.Destroy;
begin
FSem.Free;
inherited;
end;
function TRESTRequestGate.InFlight: Integer;
begin
Result := TInterlocked.CompareExchange(FInFlight, 0, 0);
end;
function TRESTRequestGate.TryAcquire(const Ctx: TRESTGateContext; TimeoutMs: Cardinal;
out Lease: TRESTGateLease; out WaitedMs: Integer; out Decision: TRESTOverloadDecision): Boolean;
var
Sw: TStopwatch;
WaitRes: TWaitResult;
CurrentInFlight: Integer;
begin
Lease := nil;
WaitedMs := 0;
Decision := odRejectedBusy;
Sw := TStopwatch.StartNew;
if TimeoutMs = 0 then
WaitRes := FSem.WaitFor(0)
else
WaitRes := FSem.WaitFor(TimeoutMs);
WaitedMs := Integer(Min(Sw.ElapsedMilliseconds, High(Integer)));
case WaitRes of
wrSignaled:
begin
CurrentInFlight := TInterlocked.Increment(FInFlight);
Lease := TRESTGateLease.Create(FSem, @FInFlight);
Decision := odAccepted;
Result := True;
if Assigned(FOnEvent) then
FOnEvent(Ctx, Decision, WaitedMs, CurrentInFlight);
end;
wrTimeout:
begin
// wrTimeout: TimeoutMs > 0 durumunda hedefli bekleme, ancak sınırlandırma.
Decision := odRejectedTimeout;
Result := False;
if Assigned(FOnEvent) then
FOnEvent(Ctx, Decision, WaitedMs, InFlight);
end;
else
begin
// wrAbandoned/hata durumları: temkinli şekilde reddet
Decision := odRejectedBusy;
Result := False;
if Assigned(FOnEvent) then
FOnEvent(Ctx, Decision, WaitedMs, InFlight);
end;
end;
end;
end.
Amaç: Her şeyi aynı anda yapmak yerine yük altında kararlılık
MaxInFlight ile kaç isteğin aynı anda „maliyetli bölüm“e girebileceğini belirlersiniz. Bu bilerek „CPU çekirdek sayısı“ değil, bir işletme parametresidir. Veritabanına yoğun yük bindiren uç noktalarda genellikle MaxInFlight‚ı DB bağlantı havuzuna göre ayarlamak mantıklıdır (örneğin Pool = 20, MaxInFlight = 12 ila 16), böylece her istek bir bağlantıyı bloke etmez ve ardından diğer thread’ler arkasından gelmez.
Sınır koşulları ve tuzaklar
- Try/Finally ist Pflicht: Lease mutlaka serbest bırakılmalıdır. Endpoint’te exception’lar varsa aksi halde gate „sızar“ ve sunucu kalıcı olarak „busy“ durumda kalır.
- Timeout sinnvoll wählen:
TimeoutMs=0sert bir limittir (hemen reddetme). Kısa bir zaman aşımı (tipik 50 ila 150 ms) zirveleri yumuşatır, gerçek kuyruklar oluşturmadan. - Gate nicht zu früh: Kimlik doğrulama (örneğin Bearer/JWT) veya yönlendirme daha uygun olabilir; semafor gerçekten maliyetli bölümden önce devreye girmelidir. Tersine: Eğer kimlik doğrulama pahalı hale geliyorsa (ör. harici bir Identity sistemine karşı), bunun da sınırlandırılması gerekir.
- 429 vs 503: HTTP 429 („Too Many Requests“) istemcilerin hedefli yeniden denemesi isteniyorsa uygundur. 503 („Service Unavailable“) hizmet geçici olarak genel olarak gelen istekleri anlamlı şekilde kabul edemiyorsa uygundur. Her iki durumda da bir
Retry-Afterbaşlığı tavsiye edilir.
Integration in REST-Handler: Indy/WebBroker/Horse için pragmatik yaklaşım
Snippet kasıtlı olarak framework’e bağımsız tutulmuştur. İsteklerin „geçtiği“ tek bir yere ihtiyacınız vardır. Tipik olarak global bir singleton veya her rota grubu için bir gate (örneğin „/reports“ için daha küçük, „/health“ için gate olmadan) kullanılır. Örnek bir entegrasyon şablonu:
- Bağlamı doldurun (RequestId, Route, RemoteIp)
TryAcquirekısa bir timeout ile- Reddedilirse hemen yanıt yazın (429/503) ve sonlandırın
- Lease, maliyetli bölüm tamamlanana kadar scope içinde kalır
Horse’da (Middleware) gate rota grubuna yakın konumlanır. WebBroker’da ilgili Action-Handler içinde çalışabilirsiniz. Indy’de ise her isteğe bir thread olup olmamasına bağlıdır; gate yine de etki eder, yeter ki maliyetli bölümler düzgün şekilde sınırlandırılsın.
High Performance REST Server Delphi: Overload-Antworten, die Clients nicht „vergiften“
Aşırı yük yanıtları sadece durum kodlarından ibaret değildir. İstemciler 429/503 aldığında agresif şekilde hemen tekrar gönderirse bir retry fırtınası oluşur. Heterojen sistem ortamlarında (Mobil uygulamalar, C# Servisler, Legacy-istemciler) tutarlı bir davranış yardımcı olur:
- Retry-After: örneğin uç noktaya göre 1 ila 3 saniye. Bu net bir zamanlayıcı sağlar.
- Kısa Body: Küçük bir JSON, örn.
{"error":"server_busy","requestId":"..."}, yeterlidir. Büyük hata nesneleri tekrar CPU ve bant genişliği tüketir. - Health-Endpoint ungedrosselt: İzleme, yük altında bile bilgi sağlamalıdır (gerekirse „degraded“ bayrağı ile).
Önünde nginx gibi bir reverse proxy çalıştırıyorsanız: oradaki timeout ve buffering ayarlarını uyumlu hale getirin. Bir proxy yükü hafifletebilir (TLS-termination, Keep-Alive), ancak yükü de öteleyebilir (örneğin büyük istek gövdelerini tamponlayarak). Operasyonda önemli olan, limitlerin tutarlı olmasıdır: Proxy-Timeout > App-Timeout, aksi halde istemciler „Gateway Timeout“ görür; oysa uygulama isteği düzgün şekilde reddetmiş olabilirdi.
Threading, DB-Pools und Keep-Alive: Pratikte tıkandığı noktalar
Das Gate „“zu viele gleichzeitig““ sorununu çözer, ancak tek bir isteğin aşırı miktarda kaynak bağlamasını otomatik olarak engellemez. Delphi projelerinden üç tipik tıkanma noktası tam olarak threading, veritabanı ve HTTP bağlantıları arasındaki kesişimlerde ortaya çıkar:
- Bir istek birden fazla sınırlı kaynağı bloke eder: Önce bir DB bağlantısı, sonra harici bir HTTP çağrısı, sonra bir dosya erişimi. Bunların hepsi aynı request-thread içinde gerçekleşirse, bloke süreleri çarpılır. Gate paralelliği sınırlasa da, throughput ciddi şekilde düşer. Bu durumda bağımlılıkları gevşetmek mantıklıdır (ör. harici çağrıları asenkron yapmak, ön hesaplamayı Job-Queue ile yapmak).
- BDE-yerel bağlantılı ikame-Havuzlama ve Transaktionen: BDE-Ablosung mit nativer Anbindung bağlantıları havuzlayabilir, ancak „uzun“ bir transaksi (ör. JSON oluşturma veya iş kontrolleri StartTransaction ile Commit arasında yer alıyorsa) bağlantıyı gereksiz yere tutar. İyi bir uygulama, transaksi mümkün olduğunca gerçek ifadelerin etrafına daraltmak ve iş mantığı izin veriyorsa işlem dışında seri hale getirme veya doğrulama yapmaktır.
- HTTP Keep-Alive gizli bir bellek tüketicisi olarak: Keep-Alive el sıkışmalarını azaltır, ancak çok sayıda boştaki istemci olduğunda çok sayıda açık sokete yol açabilir. Özellikle Windows- ve Linux-Services ortamlarında CPU artışı yerine „Handles/FDs dolu“ veya tamponlardan kaynaklanan RAM kullanımı görülür. Bu durumda sunucu ve reverse proxy tarafında net idle-timeout’lar ve ortam izin veriyorsa istemci-IP başına limit yardımcı olur.
Sonuç: MaxInFlight sabit bir değer değildir. O, en yavaşınız ve en kıt kaynağınıza bağlıdır (DB, harici sistemler, Storage) ve bir isteğin bu kaynakları ne kadar „birlikte tuttuğuna“ bağlıdır.
Gate’in yanı sıra performans kaldıraçları: JSON, DB ve I/O’yu karıştırmayın
Gate denge sağlar, ancak temiz bir endpoint kaynak ekonomisinin yerini almaz. Delphi REST sunucularında tekrar eden üç darboğaz sıkça görülür:
- Gereksiz ara string’lerle JSON oluşturma: Çoğu zaman yük, çok sayıda geçici Unicode string’ten kaynaklanır. Mümkünse, özellikle liste endpoint’lerinde, devasa ara nesneler yerine akışa yönelik oluşturma (Writer/Stream) kullanın.
- Öğe başına veritabanı erişimi: N+1 sorguları ve satır bazlı lookuplar klasik hatadır. Daha iyi olan; hedeflenmiş join’ler, batch-sorgular, sunucu tarafı agregasyondur. Çok büyük sonuç setlerinde, sayfaların „atlamaması“ için kararlı sıralama ile birlikte paginasyon uygulanması faydalıdır.
- İstek iş parçacığında engelleyen I/O: Dosya erişimleri veya harici HTTP çağrıları ya sıkı şekilde sınırlandırılmalı ya da asenkron bir pipeline’a taşınmalıdır. Aksi halde pahalı thread’leri „bekleme“ için bloke edersiniz.
Gelişmiş dijital kurumsal çözümler için bu genellikle kritik noktadır: Bir endpoint „hızlıca“ eklendiğinde çalışır; gerçek yük ve veri hacmi geldiğinde ise mimari sınırlarının (veri erişim katmanı, caching, bulk stratejileri, net zaman aşımı) düzgün çizilip çizilmediği ortaya çıkar.
Debugging und Betrieb: Neleri ölçmelisiniz
Der Hook OnEvent kasıtlı olarak basit tutulmuştur. Pratikte en azından aşağıdaki değerleri kaydetmelisiniz:
- InFlight (Gate’deki mevcut Paralellik)
- WaitedMs (ne kadar „Queueing“e izin verdiğiniz)
- Decision (accepted/busy/timeout)
- Route/RemoteIp (kabaca neden analizi, veri korumasını göz ardı etmeden)
Bununla limitlerin çok sıkı mı (çok fazla 429) yoksa çok gevşek mi (yüksek WaitedMs, artan gecikmeler) olduğuna dair bir sinyal alırsınız. Ayrıca bazı rotaların baskın olup olmadığını görürsünüz. Für Windows- und Linux-Services için bu günlük işletmede belirleyicidir: Telemetri olmadan bir performans sorunu hızla ağ, veritabanı, proxy ve uygulama arasındaki bir tahmin oyununa dönüşür.
Alışılmadık ama son derece faydalı: „WaitedMs“ erken uyarı göstergesi
Birçok ekip sadece yanıt sürelerine ve CPU’ya bakar. WaitedMs genellikle daha iyi bir göstergedir, çünkü isteklerin gerçek işe başlamadan önce beklediğini gösterir. WaitedMs artarken CPU ılımlı kalıyorsa, kıt kaynak çoğunlukla CPU değil; bir havuz (DB-Verbindungen), iş mantığında bir kilit veya harici bir downstream servisidir. Bu, neden analizinde zaman kazandırır çünkü aramayı „Pool/Lock/I/O“ yönünde, „Compiler-Optimierung“ yönünde değil, daha hedefli yapmanızı sağlar.
Varyantlar: Rota başına Gate’ler, Öncelikler ve „Fast Lane“
Her şey için tek bir Gate basittir, ama her zaman ideal değildir. Mantıklı varyantlar:
- Rota grubu başına Gate: „/reports“ sıkı, „/api/orders“ ılımlı, „/health“ açık. Böylece pahalı rapor isteklerinin çekirdek süreçleri baskılamasını engellersiniz.
- Admin/Monitoring için Fast Lane: Düşük paralellikli ayrı bir Gate, böylece işletme işlemleri yük altında bile mümkün olur.
- Bütçe tabanlı limitler: Yanıt boyutları çok değişkense, ek bir byte bütçesi yardımcı olabilir (ör. oluşturma sırasında aynı anda maksimum X MB). Bu daha karmaşıktır, ancak büyük indirmelerde gerçekçidir.
Önemli: Önceliklendirme hızla politik olur („benim endpoint’im daha önemli“). Teknik olarak stabil kalması için önceliklerin rollere veya departmanlara değil, süreçlere bağlanması gerekir (ör. raporlamadan önce sipariş alımı gibi).
Sonuç: Gate buna değer mi – ve yöntem nerede başarısız olur?
Bir Concurrency-Gate, Delphi içinde yüksek performanslı bir REST sunucu için pragmatik bir yapı taşıdır, çünkü overload’u kontrol edilebilir kılar ve sistemlerinizi peak yükte stabil tutar. Özellikle veritabanına bağlı endpoint’leriniz varsa, önünde bir Reverse Proxy varsa veya birden çok istemci (Legacy, portallar, servisler) dalgalar halinde yük oluşturuyorsa fayda sağlar.
Sınırlar nettir: Eğer her istek için yapılan gerçek iş çok maliyetliyse (verimsiz sorgular, büyük JSON nesneleri, bloke edici dış sistemler), Gate sadece semptomları örter. O zaman veri erişimi, caching stratejileri, time-out’lar ve gerekirse asenkron işleme (Queue/Job-System) ele alınmalıdır. İşletmede bir emniyet kemeri olarak Gate çoğunlukla „kısa süre kullanılabilir“ ile „tamamen kullanılamaz“ arasındaki farktır.
Eğer mevcut bir Delphi REST-API und REST-Server‚a overload davranışı entegre etmek veya limitleri veritabanı ve proxy time-out’larıyla düzgünce dengelemek istiyorsanız: Proje veya modernizasyon girişimini Net-Base ile görüşün.
Uzmanlık alanında, entegrasyonlar, veri akışları ve sürdürülebilir gelişim düzgün çalışması gerektiğinde Thread-Pool Delphi ve Http 429 Too Many Requests de önemli rol oynar.
Sonraki adım
Konu gerçek bir projeye dönüştüğünde, mimari, mevcut yapı ve işletme erken aşamada birlikte ele alınmalıdır.
Bireysel sorularda destek vermekle kalmıyoruz; kaynak kodu parçacıklarından, legacy konularından veya portal fikirlerinden sağlam bir kurumsal projeye dönüşene kadar da destek veriyoruz.
- Mevcut durum, hedef durum ve teknik riskler birlikte değerlendirilir.
- REST, veri erişimi, portallar ve Rollout sonraki işler olarak ertelenmez.
- Hangi yolun ekonomik ve işletme açısından uygulanabilir olduğunu erken görürsünüz.