Net-Base Tímarit

06.06.2026

Háafkastalegur REST-þjónn í Delphi: Beiðnimörk, þráðahópur og hreint ofálagshegðun (kóðabrot)

Háframmistöðugur REST þjónn í Delphi er ekki aðeins hraður vegna „hraðs JSON“, heldur vegna stýrðrar samhliða vinnslu, harðra tímamarka og skýrrar hegðunar við ofálag. Þessi grein sýnir hagnýtt Concurrency-Gate með semafor, 429/503-svörum...

06.06.2026

Frá tímaritsþema til verkefnaframkvæmdar

Viðeigandi þjónustu- og tæknisíður fyrir greinina

Hvers vegna „High Performance“ hjá REST í Delphi oft bilar vegna samhliða keyrslu

Í reynd er High Performance REST Server Delphi sjaldnast takmarkaður af hreinni CPU-tíma á beiðni, heldur af óstjórnlegri samhliða keyrslu: of margar samtímis beiðnir, of margar samtímis gagnagrunnsfyrirspurnir eða blokkerandi I/O (skrá, net, gagnagrunnur). Niðurstaðan er þá ekki „lítil hægari“, heldur keðjuverkun: fleiri þræðir, lengri biðraðir, hrun í tengingapúla, auknar biðtímar, tímalok á viðskiptavinshlið og að lokum þjónn sem enn „lifir“ en skilar ekki lengur áreiðanlegum svörum.

Lyklalyf er ekki einn galdratrikkur, heldur meðvitað hegðun við yfirhleðslu: Þegar þjónninn nær mörkum sínum þarf hann að hafna snemma og áreiðanlega (venjulega HTTP 429 eða 503), í stað þess að láta beiðnir hleðast inn í endalausa biðröð. Þetta kóðasneið var skrifað til þess: léttvæg samhliðaaðgangsgátt (semaphore) með tímamörkum sem auðvelt er að innleiða í tilteknar REST-endapunkta – óháð því hvort þið notið Indy, WebBroker, Horse eða eigin HTTP-lags.

Arkitektúrahugmynd: Samhliðaaðgangsgátt fyrir „dýrari hlutann“

Grunnhugmyndin er einföld: Fyrir þann dýrari hluta (gagnagrunnsaðgangur, flóknar skýrslur, stór JSON-svör) er tekið þekki úr semaphore. Ef ekkert þekki er laust, er gefið tafarlaust stjórnlaust svar. Mikilvægt er að: þessi gátt þarf að vera áreiðanlega losuð (try/finally), og hún þarf að vera staðsett í kóðaflæði þar sem raunverulega kostnaðurinn er – ekki bara fremst í request-handler þegar enn koma parser/roti/auðkenning eftir á.

Þannig er álag ekki „falin“ heldur mætt með rásum: Þjónninn svarar færri beiðnum samtímis, en með stöðugri svörunartíma. Í sérsniðnum fyrirtækjaforritum er þetta oft verðmætara en einstök bestu-tímar í tilbúnum benchmarks.

Kóðasneið: Beiðnishamli með tímamörkum, 429/503 og telemetriuhooks

Eftirfarandi Delphi-kóði útfærir samhliðaaðgang sem klasa TRestRequestGate. Hann byggir á TSemaphore (úr System.SyncObjs; semaphore er teljari fyrir takmarkaðan samtímis aðgang). Gáttarkallið skilar annað hvort „Lease“-hlut (RAII-líkt: losun í Destructor) eða ákveður tafarlaust að skila yfirhleðslu-svörun. Að auki eru hooks fyrir logging/monitoring svo þið sjáið í rekstri afhverju beiðnum var hafnað.

Delphi
unit RESTRequestGate;

interface

uses
  System.SysUtils,
  System.Classes,
  System.SyncObjs,
  System.Diagnostics;

type
  // Lágmarks samhengi fyrir logging/rekjanleika; má t.d. stækka um User/Route.
  TRESTGateContext = record
    RequestId: string;
    Route: string;
    RemoteIp: string;
  end;

  TRESTOverloadDecision = (odAccepted, odRejectedBusy, odRejectedTimeout);

  // Hook fyrir rekstrar-telemmetríu (t.d. í skrá, Syslog, Prometheus-Exporter o.fl.)
  TRESTGateEvent = reference to procedure(const Ctx: TRESTGateContext;
                                         Decision: TRESTOverloadDecision;
                                         WaitedMs: Integer;
                                         InFlight: Integer);

  // Lease-objekt: losun tokens í Destructor.
  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: engin biðtími, tafarlaust 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;

  // Fyrst lækka teljarann, síðan sleppa semaphore-inu.
  TInterlocked.Decrement(FInFlightCounter^);
  FSemaphore.Release;
end;

{ TRESTRequestGate }

constructor TRESTRequestGate.Create(AMaxInFlight: Integer);
begin
  inherited Create;
  if AMaxInFlight <= 0 then
    raise EArgumentException.Create('AMaxInFlight verður að vera > 0');

  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 þegar TimeoutMs > 0: beðið markvisst en takmarkað.
        Decision := odRejectedTimeout;
        Result := False;
        if Assigned(FOnEvent) then
          FOnEvent(Ctx, Decision, WaitedMs, InFlight);
      end;
  else
    begin
      // wrAbandoned/villutilvik: hafna varfærnislega
      Decision := odRejectedBusy;
      Result := False;
      if Assigned(FOnEvent) then
        FOnEvent(Ctx, Decision, WaitedMs, InFlight);
    end;
  end;
end;

end.

Markmið: stöðugleiki undir álagi frekar en „allt í einu“

Með MaxInFlight skilgreinir þú hversu margar beiðnir mega vera samtímis í „dýra hlutanum“. Þetta er meðvitað ekki „fjöldi CPU-kjarna“, heldur rekstrarvísi. Fyrir gagnagrunnstunga endapunkta er oft skynsamlegt að stilla MaxInFlight í hlutfalli við DB-Connection-Pool (til dæmis Pool = 20, MaxInFlight = 12 til 16), svo hver beiðni læsi ekki tengingu og aðrir þættir þurfi að bíða.

Skilyrði og gildrur

  • Try/Finally er skylda: Lease verður að losna á öruggan hátt. Ef þú færð Exceptions í endapunkti verður annars gáttin „lek“ og þjónninn helst varanlega á „busy“.
  • Veldu tímamörk með skynsemi: TimeoutMs=0 er hart takmörk (hafnað samstundis). Stutt tímamark (venjulega 50 til 150 ms) sléttir út toppa án þess að mynda raunverulegar biðraðir.
  • Gáttin ekki of snemma: Auðkenning (til dæmis Bearer/JWT) eða routing getur verið hagkvæmt; semaforinn ætti að grípa fyrir alvöru dýru hlutanum. Öfugt: ef auðkenning er dýr (t.d. gagnvart ytra Identity-kerfi), þarf einnig að takmarka hana.
  • 429 vs 503: HTTP 429 („Too Many Requests“) hentar vel þegar clients eiga að reyna aftur markvisst. 503 („Service Unavailable“) hentar þegar þjónustan tímabundið er almennt ófær um að taka við beiðnum á árangursríkan hátt. Í báðum tilvikum er mælt með Retry-After-haus.

Innleiðing í REST-Handler: Indy/WebBroker/Horse á hagnýtan hátt

Útdrátturinn er meðvitað framework-hlutlaus. Þú þarft aðeins eitt stað þar sem beiðnir „fara í gegn“. Algengt er að nota globalt Singleton eða gátt fyrir hverja leiðahóp (til dæmis „/reports“ minni, „/health“ án gáttar). Dæmi um innleiðingu sem sniðmát:

  • Fylltu samhengið (RequestId, Route, RemoteIp)
  • TryAcquire með stuttu timeouti
  • Við höfnun skrifa svar strax (429/503) og ljúka
  • Lease gildir innan scope þar til eftir hinn dýra hlutann

Í Horse (Middleware) liggur gáttin nálægt leiðahóp. Í WebBroker getur þú unnið í viðeigandi Action-Handler. Í Indy fer það eftir því hvort þú hafir þræði fyrir hverja beiðni; gáttin hefur þó áhrif svo lengi sem dýru hlutarnir eru skýrt afmarkaðir.

Þjónar með há afköst REST Delphi: Ofhleðslu-svör sem ekki „eitra“ clients

Svör við ofhleðslu eru meira en stöðukóðar. Ef clients senda strax aftur á 429/503 á árásargjarnan hátt getur myndast endurtilrauna-hrina. Í fjölbreyttu kerfisumhverfi (Mobile Apps, C# Services, Legacy-Clients) hjálpar samræmt atferli:

  • Retry-After: til dæmis 1 til 3 sekúndur, eftir endapunkti. Þetta er skýr taktstillir.
  • Stuttur body: Lítið JSON eins og {"error":"server_busy","requestId":"..."} dugar. Stór error-objekt kosta aftur CPU og bandbreidd.
  • Health-Endpoint án takmarkana: Eftirlit ætti að geta gefið upplýsingar jafnvel við álag (ef þarf með „degraded“-fána).

Ef þú keyrir reverse proxy eins og nginx hins vegar fyrir framan: Stilltu þar timeouts og buffering. Proxy getur létt álagi (TLS-Termination, Keep-Alive), en líka fært álagið (til dæmis bufferað stórar Request-Bodies). Í rekstri skiptir mestu að takmörkin séu samræmd: Proxy-Timeout > App-Timeout, annars sjá clients „Gateway Timeout“, þótt appið hefði hafnað beiðninni á hreinan hátt.

Þræðavinnsla, gagnagrunnspool og Keep-Alive: Hvar það fer úrskeiðis í rekstri

Gate leysir „of margir samtímis“-vandann, en hindrar ekki endilega að ein beiðni bindur óhóflega margar auðlindir. Þrír dæmigerðir brotpunktar úr Delphi-verkefnum koma einmitt upp við samskiptin milli þræðavinnslu, gagnagrunns og HTTP-tenginga:

  • Ein beiðni blokkerar marga þrönga auðlindapunkta: Fyrst gagnagrunnstengingu, síðan ytri HTTP-kall, síðan skráaraðgang. Ef allt þetta fer fram í sama beiðnþræði margfaldast biðtíminn. Gate takmarkar vissulega samhliða keyrslu, en gegnsætt flæði (throughput) fellur verulega. Hér borgar sig að afbaka tengslin (t.d. gera ytri köll ósamstillt, eða fyrirreikna með job-queue).
  • BDE-Ablösung mit nativer Anbindung-Pooling und Transaktionen: BDE-Ablosung mit nativer Anbindung getur poolað Connections, en „löng“ transaktion (t.d. vegna myndunar JSON eða viðskiptaathugana milli StartTransaction og Commit) heldur tengingunni óþarfa lengi. Við góða verklagi er transaktionin þröngst um raunveruleg statements og allt utan transaktionar serialiserast eða valid-er, ef það er faglega hægt.
  • HTTP Keep-Alive sem falinn minniætandi þáttur: Keep-Alive minnkar handshakes, en getur við marga óvirka viðskiptavini leitt til margra opinna sokka. Sérstaklega hjá Windows- und Linux-Services sjást þá ekki „CPU hoch“, heldur „Handles/FDs voll“ eða aukið RAM vegna buffera. Hér hjálpa skýr idle-timeout í server og á reverse-proxy auk takmarkana á per-client-IP, ef umhverfið leyfir það.

Niðurstaðan: MaxInFlight er ekki fast gildi. Hann ræðst af hægustu, þröngu auðlindinni ykkar (DB, ytri kerfi, storage) og hversu vel beiðni heldur þessum auðlindum „saman“.

Frammistöðulyftistöng við hlið Gate: Blandið ekki JSON, gagnagrunni og I/O saman

Gate stabiliserar, en kemur ekki í staðinn fyrir hreina endpoint-økonomíu. Þrjár þyngstu tafir í Delphi REST-serverum koma aftur og aftur upp:

  • JSON-uppbygging með óþarfa millistrengjum: Oft myndast álag vegna fjölda tímabundinna Unicode-strengja. Þar sem hægt er, byggið streaming-miðað (Writer/Stream) frekar en að búa til risastór millihlut, sérstaklega á lista-endpunktum.
  • Gagnagrunnsaðgangur „fyrir hvert item“: N+1-fyrirspurnir og per-row lookups eru klassík. Betra er: markviss joins, batch-fyrirspurnir, server-side aggregation. Fyrir mjög stór svör borgar sig pagination með stöðugri röðun (svo síður „renna“ ekki til).
  • Blokkandi I/O í beiðnþræði: Skráaraðgangur eða ytri HTTP-köll ættu annað hvort að vera stranglega takmörkuð eða flutt í ósamstillta pípu. Annars læsa þið dýrum þræði í „bíða“.

Fyrir þróaðar stafrænar fyrirtækjalausnir er þetta oft ágreiningpunkturinn: Endpoint var „snöggt“ bætt við og virkaði þar til raunverulegt álag og gagnafjöldi komu. Þá kemur í ljós hvort arkitektúrmörk hafa verið sett rétt (gagnaaðgangslag, caching, bulk-strategíur, skýrar timeouts).

Villuleit og rekstur: Hvað þið eigið að mæla

Hook-ið OnEvent er meðvitað einfalt. Í rekstri ættuð þið að lágmarki skrá eftirfarandi gildi:

  • InFlight (aktuelle Parallelität am Gate)
  • WaitedMs (wie viel „Queueing“ Sie zulassen)
  • Decision (accepted/busy/timeout)
  • Route/RemoteIp (grófa orsakaúrvinnsla, án þess að vanrækja persónuvernd)

Með þessu fáið þið merki um hvort takmörk séu of þröng (of margar 429) eða of laus (háir WaitedMs, vaxandi seinkun). Og þið sjáið hvort einstakar leiðir ráða ríkjum. Fyrir Windows- og Linux-Services er þetta í daglegu starfi úrslitaatriði: Án telemetrie verður frammistöðuvandamál fljótt að getraun milli nets, gagnagrunna, proxy og forritsins.

Óvenjulegt, en afar gagnlegt: „WaitedMs“ sem snemmviðvörun

Mörg teymi skoða aðeins Response-Time og CPU. WaitedMs er oft betri vísir, því hann sýnir að Requests bíða þegar fyrir sjálfa vinnslu. Ef WaitedMs hækkar meðan CPU helst hófsamlegt, er takmörkuð auðlind gjarnan ekki CPU heldur pool (DB-Verbindungen), læsing í Business-Logik eða ytri Downstream-Service. Þetta sparar tíma við orsakaúrvinnslu, því þið getið leitað markvissar í átt að „Pool/Lock/I/O“ frekar en „Compiler-Optimierung“.

Útfærslur: Per-Route-Gates, forgangsröðun og „Fast Lane“

Eina gáttin fyrir allt er einföld, en ekki alltaf best. Sennilegar útfærslur:

  • Gátt fyrir leiðahóp: „/reports“ strangt, „/api/orders“ hóflegt, „/health“ opið. Þannig hindrið þið að kostnaðarsamar skýrslubeiðnir ýti kjarnferlum til hliðar.
  • „Fast Lane“ fyrir stjórn/eftirlit: Sérstök gátt með takmarkaðri samsíða keyrslu, svo rekstraraðgerðir séu mögulegar þrátt fyrir álag.
  • Takmörk byggð á byte‑budget: Ef svarstærðir sveiflast mikið getur byte‑budget hjálpað (td. hámark X MB í samtímis myndun). Þetta er flóknara en einfaldari aðferðir, en raunhæft fyrir stórar niðurhalsaðgerðir.

Mikilvægt: Forgangssetning verður fljótt pólitísk („mein Endpoint ist wichtiger“). Tæknilega helst kerfið stöðugt ef forgangar eru tengdir ferlum (td. pantaskráning fyrir skýrslugerð), ekki hlutverkum eða deildum.

Niðurstaða: Er gáttin þess virði — og hvar brýtur nálgunin saman?

Concurrency-Gate er praktískur hluti af High Performance REST Server í Delphi, því hún gerir ofhleðslu stjórnanlega og heldur kerfum ykkar stöðugum við hámarksálag. Hún borgar sig sérstaklega ef þið hafið gagnagrunnstengda endapunkta, ef Reverse Proxy situr fyrir framan eða ef mörg client (Legacy, portalar, Services) mynda álag í bylgjum.

Mörkin eru skýr: Ef sjálf vinna hvers request er of dýr (óhagkvæmar fyrirspurnir, stór JSON-objekt, blokkerandi utanaðkomandi kerfi), þá hylur gáttin aðeins einkenni. Þá þarf að taka til gagnaaðgang, caching-strategíur, timeouts og hugsanlega asynkrona vinnslu (Queue/Job-System). Sem öryggisbelti í rekstri er gáttin þó oft munurinn á „stutt þjált“ og „algerlega óvinnandi“.

Ef þið viljið innleiða ofhleðsluhegðun í núverandi Delphi REST-API und REST-Server eða fínstilla mörk í samspili við gagnagrunns- og proxy-timeouts: Ræðið verkefni eða moderniserung með Net-Base.

Í faglegu samhengi skipta einnig Thread-Pool Delphi og Http 429 Too Many Requests máli þegar Integrationen, gagnaflæði og áframhaldandi þróun þurfa að spila vel saman.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

Næsta skref

Þegar úr málinu verður raunverulegt verkefni ber að skoða arkitektúr, núverandi kerfi og rekstur snemma saman.

Við styðjum ekki aðeins við einstakar spurningar, heldur einnig þegar úr kóðabútum, eldri kerfum eða gáttahugmyndum þarf að verða traust fyrirtækjaverkefni.

  • Núverandi staða, markmynd og tæknileg áhætta eru metin saman.
  • REST, gagnaaðgangur, gáttir og innleiðing eru ekki skildir eftir til síðar.
  • Það sést snemma hvaða leið er fjárhagslega og rekstrarlega sjálfbær.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax tiltækir. Fyrir Instagram undirbúum við hlekk og stuttan texta beint.

Tölvupóstur

Instagram opnast í nýjum flipa. Tengill og stuttur texti eru afritaðir í klippiborðið á undan.