My DataSnap webinar is running right now (I'm listening to myself while blogging), it will be available as a reply and you can still attend the second and third rounds this evening and in the night (European time). To sign up, enter your data at http://forms.embarcadero.com/forms/AMUSCA1211DelphiMulti-tierMCantu11-28.

There is an introductory video at http://www.embarcadero.com/rad-in-action/development-and-deployment-of-delphi-multi-tier-applications and the same site will soon host the webinar white paper, a rather detailed document of well over 40 pages, which doesn't repeat my past white papers but adds a lot of new content.

This blog post, however, was also prompted by a DataSnap performance post last week on robertocschneiders.wordpress.com/2012/11/22/datasnap-analysis-based-on-speed-stability-tests/. While I partially disagree with the testing scenario, it does highlight a few important elements. My disagreement comes from the fact that having a rather sophisticated architecture for processing complex data types, managing sessions, mapping methods, and a lot more will never compete on speed with "simpler" architectures. I'm not saying that simpler architectures are not more effective (and DataSnap can certainly learn from leaner approaches), but you have to compare solutions offering similar features.

There are other issues that show up, as I'll highlight in a second, but for most of them I know a lot of fault on our side: we should document the alternatives better, have wizards generating more optimized versions, make it easy to pick different configurations. After this long preamble, let me cover a few technical points.

Thread Pooling

 

Regarding threading, creating one for each incoming request is Indy’s IdHTTPServer default configuration, but you can tune it adding code to the server main form, which creates and manages the Web server component. The OnCreate event handler of this form initializes the HTTP server, with the code:

procedure TForm1.FormCreate(Sender: TObject);
begin
  FServer := TIdHTTPWebBrokerBridge.Create(Self);
end;

Now we can change the configuration to use a thread pool, pre-allocating a number of threads for the incoming concurrent requests:

procedure TForm1.FormCreate(Sender: TObject);
var
  SchedulerOfThreadPool: TIdSchedulerOfThreadPool;
begin
  FServer := TIdHTTPWebBrokerBridge.Create(Self);

  SchedulerOfThreadPool := TIdSchedulerOfThreadPool.Create(FServer);
  SchedulerOfThreadPool.PoolSize := 50;
  FServer.Scheduler := SchedulerOfThreadPool;

  FServer.MaxConnections := 150;
end;

As you can see in the last line above you can also put a limit to the concurrent connection (MaxConnections): After reaching this limit the server will stop responding and simply return an error. This value should probably be quite high, but it is relevant, as it will prevent the server from shutting down in case of an attack or simply an excessive workload. It is often better to return a clear error message (“too many connections”) than simply fail to respond under the load.

Sessions Management

DataSnap creates sessions automatically, one for each request by a new client. The fact a client is new is determined by looking to extra headers, cookies, or query parameters. If nothing is found, a new session in created. So if you write a custom application for testing and make a plain HTTP call (maybe in a loop) you and up creating a server side session for each request. These will stay in memory for 20 minutes after the last request, by default. 

What I have added to my testing code is a first call to create and return the session information, which is then copied to HTTP extra headers. Here are the lines I added before the calling loop:

        IdHTTP1.Get(strUrl);
        strSession := Copy(
          Idhttp1.Response.RawHeaders.Values ['Pragma'], 1, 30);
        IdHTTP1.Request.CustomHeaders.Clear;
        IdHTTP1.Request.CustomHeaders.AddValue('Pragma', strSession);

As an alternative you can add the sessionid in the URL:

sURLSessionEquals = '?SESSIONID=';
strUrl := strUrl + sURLSessionEquals + strSessionID;
If you want to make sure this code is effective you can look at the server memory consumption, but also do a direct test adding this code to the server application:
  ShowMessage(IntToStr(TDSSessionManager.Instance.GetSessionCount));

This second change keeps memory consumption under control in the testing scenario, but again does not really affect the throughput. 

An alternative to change the session management behavior is to ask the server to close the session from within a server method, by using one of the custom parameters of the "invocation metadata": 

 

function TServerMethods1.ReverseString(Value: string): string;
begin
  GetInvocationMetadata.CloseSession := True;
  Result := System.StrUtils.ReverseString(Value);
end;

You might want to add this code to a specific "Close" method a client could potentially code when leaving the application.

Threading Crashes

Finally, the blog post mentioned refers to a pretty severe crash in the server in case of two threads hitting at the same time. This has been confirmed... and fixed by the R&D team. Actually the main issue is fixed, but others related issues are still under investigation. It will appear in a coming Delphi XE3 update, but I can make at least the main workaround available if anyone is currently blocked.

As I showed in my webinar, fix the fixes applied the server can receive a number of concurrent requests without problem. While I value speed and memory considerations, I think stability is even more important and having improvements in the coming update is certainly relevant.

Winding Down

There were many interesting questions in the webinar (just ended) and I'll soon have the white paper and the demos to download, so expect other blog posts on this topic soon. I think DataSnap is relevant and one of my goals is to push it significantly in future versions of Delphi, so feedback is appreciated.