synopse / mORMot2

OpenSource RESTful ORM/SOA/MVC Framework for Delphi and FreePascal
https://synopse.info
Other
531 stars 132 forks source link

THttpAsyncServer performs poorly (down to 1 req/sec) on Linux for non-keepalive requests. #81

Closed turkerali closed 2 years ago

turkerali commented 2 years ago

When tested with ab (apache benchmark), THttpAsyncServer serves poorly (down to 1 req/sec) for non-keepalive benchmarks on Linux. Keepalive benchmarks are not affected. Note that socket based THttpServer does not show this behaviour.

Benchmarks results for THttpAsyncServer:

Non-Keepalive: ab -t 60 -n 10000 -c 30 http://127.0.0.1:8888/app/engine/mormot/util_json_eni?AET_JSON_REQUEST=FETCH_ENI_CABLE_TYPES This is ApacheBench, Version 2.3 <$Revision: 1879490 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient) apr_pollset_poll: The timeout specified has expired (70007) Total of 1 requests completed

Keepalive: ab -t 60 -n 10000 -c 30 -k http://127.0.0.1:8888/app/engine/mormot/util_json_eni?AET_JSON_REQUEST=FETCH_ENI_CABLE_TYPES Concurrency Level: 30 Time taken for tests: 1.525 seconds Complete requests: 10000 Failed requests: 0 Keep-Alive requests: 10000 Total transferred: 2500000 bytes HTML transferred: 490000 bytes Requests per second: 6556.17 [#/sec] (mean) Time per request: 4.576 [ms] (mean) Time per request: 0.153 [ms] (mean, across all concurrent requests) Transfer rate: 1600.63 [Kbytes/sec] received

Code to reproduce:

program demo; {$I mormot.defines.inc} uses {$I mormot.uses.inc} SysUtils, mormot.core.base, mormot.core.Text, mormot.core.json, mormot.core.os, mormot.app.console, mormot.rest.core, mormot.rest.server, mormot.rest.http.server, mormot.rest.memserver, mormot.orm.base, mormot.orm.core, mormot.orm.sql, mormot.orm.sqlite3, mormot.net.sock, mormot.net.server, mormot.rest.mvc, mormot.rest.sqlite3, mormot.crypt.core, mormot.DB.sql, mormot.DB.sql.zeos, mormot.DB.raw.sqlite3.static;

type TMyRestServer = class(TRestServerFullMemory) private published procedure util_json_eni(Ctxt: TRestServerUriContext); end;

TSQLEniCableTypes = class(TOrm) private FENI_CABLE_TYPE: RawUTF8; published property ENI_CABLE_TYPE: RawUTF8 index 32 read FENI_CABLE_TYPE write FENI_CABLE_TYPE; end;

var // DB RELATED ZeosDSN: string; LibraryLocation: string; fConnection: TSQLDBZEOSConnectionProperties; // ORM RELATED aHTTPServer: TRestHttpServer; aServer: TMyRestServer; aetDBSchema: TOrmModel; aetRestServerDB: TRestServerDB;

procedure TMyRestServer.util_json_eni(Ctxt: TRestServerUriContext); var tmp_query: ISQLDBStatement; sql: RawUTF8 = ''; AET_JSON_REQUEST: RawUTF8; begin if (Ctxt.Method = mGET) and Ctxt.InputExists['AET_JSON_REQUEST'] then begin AET_JSON_REQUEST := Ctxt.InputUTF8['AET_JSON_REQUEST']; if AET_JSON_REQUEST = 'FETCH_ENI_CABLE_TYPES' then sql := 'SELECT ENI_CABLE_TYPE_ID, ENI_CABLE_TYPE FROM eni_cable_types'; tmp_query := fConnection.PrepareInlined(sql, True); if tmp_query = nil then Ctxt.Returns('{"success":false,"msg":"INVALID SQL"}') // e.g. invalid aSQL else begin tmp_query.ExecutePrepared; Ctxt.Returns(tmp_query.FetchAllAsJSON(True)); tmp_query.ReleaseRows; end; end else Ctxt.Returns('{"success":false,"msg":"INVALID REQUEST"}'); end;

begin { // UNCOMMENT FOR LOGGING: with TSynLog.Family do begin Level := LOG_VERBOSE; EchoToConsole := LOG_VERBOSE; // log all events to the console end; } // MySQL CLIENT LIBRARY: LibraryLocation := '/usr/lib/x86_64-linux-gnu/libmariadb.so.3'; ZeosDSN := 'zdbc:mysql://127.0.0.1:3306/demo_db?username=root;password=demo_pass;LibLocation='

synopse commented 2 years ago

In fact, ab makes all its connections, then start sending the request headers once they are all connected - no real HTTP client does that: they connect, then send the request. This is not well handled by the async server, unless you set the paoAcceptWait option. But for regular HTTP/1.0 requests (as from a mORMot client), it works fine. I am investigating further. It should be fixed anyway, because it sounds like a race condition just after connection.

synopse commented 2 years ago

From my tests, this issue seems fixed now. Please reopen if needed.

ProcessRead now waits up to 50ms for actual data to come from the client. Epoll was not triggered it again as we expected.