#1 2016-02-05 10:13:37

fabioxgn
Member
Registered: 2015-11-06
Posts: 34

Fastest way to return data from dataset in interface based service

I'm migrating a project from datasnap to mORMot and we override some datasnap funcionallity, but keeping it short, I'm overriding the GetRecords of the dataset on client and server side.

In my first implementation I was returning TBytes from the server, but I noticed that it was a little slow and was consuming a lot of time to marshall the JSON, then I did some tests and tried 2 new things:

1: return TServiceCustomAnswer, but I have a var parameter. So I'd have to write a custom "packet" to return it alongside the data.
2: return RawByteString instead of TBytes, noticed a big improvement on the content length maybe because of better compression.

Here is a sample code of what I'm doing right now (with RawByteString):

function TRESTServerMethods.GetRecords(const ProviderName: string; const Count: Integer; var RecsOut: Integer; const
    Options: Integer; const CommandText: string; const Params: TArray<TArray<Variant>>; const Where: string):
    RawByteString;
begin
    Buffer := VariantArrayToBytes(Provider.GetRecords(Count, RecsOut, Options, CommandText, OleParams, OwnerData));
    SetString(Result, PAnsiChar(Buffer), Length(Buffer));
end;

Also I noticed (using manual testing, did no benchmark) that between TServiceCustomAnswer and JSON with RawByteString there is no to little improvement, but from TBytes to RawByteString there's a big improvement, mainly in content length.

Here's a comparison:

TBytes:
Total time: 350ms
Length: 4211661
Server time: 246.371

RawByteString
Total time: 260ms
Length: 2197186
Server time: 227.480

Total time is measured on the Client Side running locally, it is the total time to open the dataset with 5K+ records. Server time is what mORMot prints on the log for the request.

Notice that the content length using RawByteString é almost 50% of TBytes.

So, my question is:

Is this the fastest way to return this kind of data? Any suggestions?

Offline

#2 2016-02-05 12:14:00

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,182
Website

Re: Fastest way to return data from dataset in interface based service

AFAIK:
- TBytes would return an array of bytes, e.g. (130,100,65,10,...)
- RawByteString would return a Base-64 encoded string  "pbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci..."
So RawByteString would definitively use much less data.

About performance, we designed some base-64 encoding/decoding functions in SynCommons.pas, which are very efficient (much faster e.g. than Delphi RTL/Indy alternatives).
But the ratio of base-64 output bytes to raw input bytes is 4:3 (there is a 33% overhead).
As such, the best idea, in case of huge blob content, would be to use a TServiceCustomAnswer, since there won't be any Base-64 encoding.

Offline

#3 2016-02-05 12:43:56

fabioxgn
Member
Registered: 2015-11-06
Posts: 34

Re: Fastest way to return data from dataset in interface based service

ab wrote:

AFAIK:
- TBytes would return an array of bytes, e.g. (130,100,65,10,...)
- RawByteString would return a Base-64 encoded string  "pbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci..."
So RawByteString would definitively use much less data.

About performance, we designed some base-64 encoding/decoding functions in SynCommons.pas, which are very efficient (much faster e.g. than Delphi RTL/Indy alternatives).
But the ratio of base-64 output bytes to raw input bytes is 4:3 (there is a 33% overhead).
As such, the best idea, in case of huge blob content, would be to use a TServiceCustomAnswer, since there won't be any Base-64 encoding.

Thanks, I'll try to use TServiceCustomAnswer.

Can you give me any tips about how can I send back that "RecsOut" along with the TBytes content in the TServiceCustomAnser?

It is an integer value, but I don't have much experience working with this kind of raw content. Is it possible to add a field to TServiceCustomAnswer? Or do I have to concatened this integer value with the content?

Offline

#4 2016-02-05 12:57:39

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,182
Website

Re: Fastest way to return data from dataset in interface based service

You may add the value within the headers, e.g.

  result.Header := FormatUTF8(BINARY_CONTENT_TYPE_HEADER+#13#10'RecsOut: %',[RecsOut]);
  SetString(result.Content, PAnsiChar(Buffer), Length(Buffer)); 

Or you may put the value at first place in the output binary:

  result.Header := BINARY_CONTENT_TYPE_HEADER;
  SetLength(result.Content, Length(Buffer)+4); 
  PInteger(result.Content)^ := RecsOut;
  move(Buffer[0],result.Content[5],length(Buffer));

Offline

#5 2016-02-05 20:37:29

fabioxgn
Member
Registered: 2015-11-06
Posts: 34

Re: Fastest way to return data from dataset in interface based service

ab wrote:

You may add the value within the headers, e.g.

  result.Header := FormatUTF8(BINARY_CONTENT_TYPE_HEADER+#13#10'RecsOut: %',[RecsOut]);
  SetString(result.Content, PAnsiChar(Buffer), Length(Buffer)); 

Or you may put the value at first place in the output binary:

  result.Header := BINARY_CONTENT_TYPE_HEADER;
  SetLength(result.Content, Length(Buffer)+4); 
  PInteger(result.Content)^ := RecsOut;
  move(Buffer[0],result.Content[5],length(Buffer));

Thanks.

I did some tests with it, and when using the binary encoding, the data isn't compressed.

I did some tests with a dataset with 5.5K records, and it the content was 1.6mb. Then I changed the encoding to TEXT and it got down to 350kb.

In this case with large datasets, I think that compressing would be better, but I'm not sure about small datasets, or if it's going to give me a problem of some kind.

Any thoughts?

Offline

#6 2016-02-06 06:57:55

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,182
Website

Re: Fastest way to return data from dataset in interface based service

Yes, the HTTP server check the "Content-Type:" header value.
If it is JSON or TEXT, it would compress the data.
If it is BINARY_CONTENT_TYPE_HEADER, it is not compressed.

Which kind of client do you use?
You could simply compress the result.content BEFORE sending it.
See in SynLZ.Pas and SynZip.pas: there are ready-to-use functions working with RawByteString variables.

Offline

#7 2016-02-07 00:09:03

fabioxgn
Member
Registered: 2015-11-06
Posts: 34

Re: Fastest way to return data from dataset in interface based service

ab wrote:

Yes, the HTTP server check the "Content-Type:" header value.
If it is JSON or TEXT, it would compress the data.
If it is BINARY_CONTENT_TYPE_HEADER, it is not compressed.

Which kind of client do you use?
You could simply compress the result.content BEFORE sending it.
See in SynLZ.Pas and SynZip.pas: there are ready-to-use functions working with RawByteString variables.


In this case all the clients will be Delphi clients, as this particular method is a TClientDataSet specific method.

I guess that it wouldn't make a diffence if I just use the TEXT_CONTENT_TYPE_HEADER instead of compressing "by hand", as  the content would be converted To RawByteString on both options, is that correct?

Also we have 1500+ integration tests, which opens the datasets, save stuff on the database, etc. Not a single test failed using TEXT and the tests run 15s faster: 4m30s using text encoding instead of 4m45s using binary.

So if I'm not missing something and if in this case with a Delphi client only, my conclusion is that using a TServiceCustomAnswer with TEXT_CONTENT_TYPE_HEADER is the fastest option, right?

It would be even faster with a remote server as the content length is less than 30% (22% in this 5k records dataset) of the original (binary) length.

Last edited by fabioxgn (2016-02-07 00:10:54)

Offline

Board footer

Powered by FluxBB