You are not logged in.
When calling TRestServerDB.CreateSQLMultiIndex and TRestServerDB.CreateMissingTables, is it possible to obtain a string copy of the generated SQL script?
The second param of TRestServerDB.TransactionBegin is SessinID,
How can I get that SessionID? (TRestClientDB provides SessionID, but I didn't find it from TRestServerDB).
It is not "Delphi Win64 is less good for internal SQLite 3 on my computer". Actually, I did get the similar results using Delphi 10.4.2, x64, when running the official test project https://github.com/synopse/mORMot2/blob … onsole.dpr . Such results indicate Direct (internal) SQLite layer is faster then External SQLite layer, as you noted.
However, my updated test project https://gist.github.com/wxinix/325c64a2 … 4ae02ff75f indicates otherwise - that is, External SQLite layer is faster than Direct SQLite layer.
I am happy with the good-enough performance either way. Just not sure what caused the difference. If you could take a quick peek, that would be great. But if you are not interested, that is also fine. Thanks again for you feedback and the continued performance improvements.
I don't know why your Delphi Win64 is less good for the internal SQLite3 on your computer.
The external DB layer is very thin: most of the time is spent inside the SQlite3 engine itself. Our ORM is able to bypass the whole "external" layer. So it makes sense to have close numbers.
I have fixed the sample.
And also rewritten some part of the ORM process for better performance.
Now the direct SQLite3 layer seems faster than the external SQLite3 layer for both reads and writes.
[
I rerun my test with the latest commit - quite contrary, it seems external SQLite3 layer is still much faster than direct SQLite3 layer for writes. I am using Delphi 10.4.2, win64.
I noticed that in your sample code, https://github.com/synopse/mORMot2/blob … tCases.pas, TRestClientDB class is used, like below:
TRestClientDB.Client.BatchStart
....
TRestClientDB.Client.BatchAdd
...
TRestClientDB.Client.BatchSend
But in my testing code, I use TRestServerDB class directly, together with TSQLRestBatch class:
LBatch := TSQLRestBatch.Create
...
LBatch.Add
...
TRestServerDB.BatchSend(LBatch...
My question is - is the latest performance improvements only for TRestClientDB? It seems TRestServerDB.BatchSend performance is not improved with the latest commits.
In production, I would use the internal Sqlite3 engine for sure.
Especially with my latest commits.
Hi @ab thank you for the latest commit, and advice.
In case of a real-time time-series database where WRITES (BatchAdd, every couple of seconds) are much more frequent than READS (every couple of minutes) - is the internal SQLite engine's cache still working? I think the cache would be less effective?
Thanks again.
@wxinix
I just committed some code refactoring which enhanced performance on my side. Please check and see if it is better on your side.
HI @ab, @ttomas
Thank you for the advice and feedback.
I built with the latest commit - still it seems SQLite External File provides the best BatchAdd performance (~350k records/second).
There got to be some (nice) reason for such a stunning performance for an SQL DB. Regardless, my goal is to find out which settings provides the best WRITE performance- given the testing results, the answer appears to be SQLite External File .
Note - SQLite External File also gives very good READ performance (slightly worse than SQLite in Memory). So overall, SQLite External File appears to be the best option?
Any comments? Insights or advice?
This is not consistent with my previous finding about mORMot BatchAdd and previous benchmarks.
I don't have any idea why you find this.
Perhaps the Delphi Win64 memory manager is not very efficient? What if you try on Win32?
HI ab, I uploaded the testing project to my github gist , you can try and see yourself. In BOTH x86 and Win64 cases, mORMot2 SQLite external file DB is much faster than the SQLite in memory DB.
Delphi 10.4.2, Win64 build, results:
LServer_SQLiteMem.BatchSend 6001 records takes 21.60ms (277785 records/second).
LServer_SQLiteExtFile.BatchSend 6001 records takes 16.84ms (356248 records/second).
Delphi 10.4.2, x86 build, results:
LServer_SQLiteMem.BatchSend 6001 records takes 28.33ms (211809 records/second).
LServer_SQLiteExtFile.BatchSend 6001 records takes 21.48ms (279363 records/second).
I continued with my performance testing - this time, I compared mORMot2 SQLite in memory vs mORMot2 External SQLite File DB. I am very surprised that the External SQLite File DB is much faster than the SQLite in MEM DB - 25% faster!
Note:
- For External SQLite File DB, I used TSQLDBSQLite3ConnectionProperties, and SynchronousMode := Off, and LockingMode := Exclusive
- Delphi 10.4.2, Win64 Build
Details below - but does that make sense? I expect the In-MEM DB faster than External File DB.
- Single Table, Multi-Threaded, BatchAdd via a background worker thread, the SQLite In MEM DB Results:
20210928 18564227 " debug Test_BatchAdd_STable_MThread_MemDB::thr_1 - Orm.Server.BatchSend 6000 records takes 22.54ms (266098 rec/sec), code 200
20210928 18564229 " debug Test_BatchAdd_STable_MThread_MemDB::thr_2 - Orm.Server.BatchSend 6000 records takes 23.09ms (259830 rec/sec), code 200
20210928 18564230 " debug Test_BatchAdd_STable_MThread_MemDB::thr_3 - Orm.Server.BatchSend 6000 records takes 23.42ms (256191 rec/sec), code 200
- Single Table, Multi-Threaded, BatchAdd via a background worker thread, External SQLite File DB, SynchronousMode := Off, LockingModel := Exclusive
20210928 18564257 " debug Test_BatchAdd_STable_MThread_SQLite::thr_1 - Orm.Server.BatchSend 6000 records takes 17.34ms (345861 rec/sec), code 200
20210928 18564258 " debug Test_BatchAdd_STable_MThread_SQLite::thr_3 - Orm.Server.BatchSend 6000 records takes 18.15ms (330578 rec/sec), code 200
20210928 18564259 " debug Test_BatchAdd_STable_MThread_SQLite::thr_2 - Orm.Server.BatchSend 6000 records takes 17.71ms (338676 rec/sec), code 200
If you really need consistent write to PostgreSQL, I guess my shared TSQLRestBatch trick which is sent in a background thread may be the only solution.
Thank you ab, for the advice.
I implemented as advised - using a background worker thread to do the BatchAdd work, and rerun the test.
Note - I am using ZEOS for PostgreSQL connection.
The results are very satisfying.
- mORMot static object list - 760,000 records/sec
- mORMot SQLite in-memory - 290,000 records/sec
- PostgreSQL - 110,000 records/sec
Should you be interested - the record type is defined as follows:
TSQLRecordSample = class(TSQLRecord)
strict private
FAmount: Currency;
FBirthDate: TDateTime;
FCreatedAt: TCreateTime;
FLastChange: TModTime;
FName: RawUTF8;
published
property Amount: Currency read FAmount write FAmount;
property BirthDate: TDateTime read FBirthDate write FBirthDate;
property CreatedAt: TCreateTime read FCreatedAt write FCreatedAt;
property LastChange: TModTime read FLastChange;
property Name: RawUTF8 index 40 read FName write FName stored AS_UNIQUE;
end;
wxinix wrote:But, PostgreSQL layer DROPS the single-threaded insertion rate from Zeo's 100,000 record/sec to about 50,000 records/sec
I think this depends on data you insert. ZEOS interally uses binary pg protocol, so if inserted data is mostly numbers it can be faster compared to mORMot Postgres layer, where text pg protocol is used
Thank you for the advice. I'll keep this in mind. But for now - I'll stick to ZEOS, which seems has faster performance for my use case.
I have just included this "host:port" parsing.
Thank you it works.
Note - all the insertion cases are targeting ONE single table. For each test case, the table will be emptied in advance.
----------------------------
1. What if you insert 150,000 records from three concurrent threads?
It doesn't change the overall average insert rate
2. What if you insert 50,000 records from one thread, but 3 times in a row?
Similar (slightly slower 1~2%) insertion rate, compared to one-time 150,000 records.
3. What if you use our direct PostgreSQL layer instead of Zeos?
mORMot PostgreSQL layer IMPROVES the multi-threaded (3 threads) insertion rate by x6 times, increasing from Zeo's 5000 records/sec to a much higher 30000 records/sec
But, PostgreSQL layer DROPS the single-threaded insertion rate from Zeo's 100,000 record/sec to about 50,000 records/sec
4. What if you use SQLite3 instead of PostgreSQL?
If I use mORMOt internal in-mem SQLite3, the multi-threaded insertions is about 4~6% FASTER then the single-thread insertion, around 220,000 records/sec
If I use mORMot static object list, the multi-threaded insertions is about 4~6% FASTER then the singled-thread insertion, around 500,000 records/sec
---------------------------
IN SUMMARY:
- mORMot in-mem SQLite, and static object list: multi-threaded batch insertion rate is FASTER than single-threaded insertion
- for PostgreSQL, Zeos has the best single-threaded batch insertion rate (100,000 records/sec), mORMot Postgresql Layer has the best multi-threaded batch insertion rate (50,000 records/sec).
Are the above making sense?
You should rather try ExecuteNoResult() I guess for such statements with no result.
Thank you. Yes - that works.
mormot.db.sql.postgres, TSqlDBPostgresStatement.Execute
line 624
fTotalRowsRetrieved := PQ.ntuples(fRes);
Would throw Exception ESqlDBPostgres, with message
"TSqlDBPostgresStatement.ExecutePrepared: result expected but statement did not return tuples'.
The statement being executed is
fSql 'CREATE SCHEMA IF NOT EXISTS test'
Line 293 of mormot.db.sql.postgres
fPGConn := PQ.SetDBLogin(pointer(Properties.ServerName), nil, nil, nil, pointer(Properties.DatabaseName), pointer(Properties.UserID),
pointer(Properties.PassWord));
When Propertyies.ServerName is set to be "localhost:5432" , it will thrown exception "Connection to database postgres failed [could not translate host name "localhost:5432" to address: Unknownhost]'.
To make it work, Properties.ServerName must NOT include the port, like this "localhost". Port cannot be specified as part of the ServerName.
The second parameter of libpq API, PQ.SetDBLogin is actually for specifying the port. But it is not used by mORMot (i.e., nil for the default 5432)
Hence, this is a missing piece here that a user cannot specify a port other than the default 5432.
Follwing this post about the thread-safe of TRestServerDB, I did some test about multi-threaded BatchAdd.
The database is PostgreSQL, and I use TSQLDBZEOSConnectionProperties
Test 1: I use TRestServerDB.BatchAdd to insert 150,000 records into one table in a single thread
Test 2: I use TRestServerDB.BatchAdd to insert 50,000 records in the the same table, but from three different concurrent threads
Both Test 1, and Test 2 uses the SAME instance of TRestServerDB.
Test1: is quite fast, achieving a rate of 100,000 records/second
Test2: very slow, the overall rate is 5000 records/second
Is this expected? Any tricks to improve the multi-threaded Batch insert?
Note - If I split the data into three different tables (i.e., the same table structure but diff table names) - the results are still similar. Multi-threaded insert is very slow.
I would use the default memory manager on Delphi, and mORMot x86_64 memory manager on FPC.
You may try our mORMot x86_64 memory manager on Delphi Win64, which has less collisions for smallest memory blocks, so may be a bit faster on heavily multi-thread process.
Thank you again, ab!
I run another around of tests using the following Mem Manager:
Delphi Default
FastMM4-AVX
mormot.core.fpcx64mm
FastMM5
Delphi 10.4.2, Windows Server 2016, and x64 Release Build
And I found that FastMM5 would give the best performance, for both Multi, and Single thread.
FastMM5 > FastMM4-AVX > Delphi MM/mormot.core.fpcx64mm
On my PC, the regression tests are faster on Win64 than Win32.
So if your app is significantly slower on Win64, my guess is that it does not come from normal work of the framework, but on your use case.
Enable the logs, or try to do some profiler, to find out what is actually slower.Which compiler do you use? Which Memory Manager?
Thank you ab for the heads up.
I use Delphi 10.4.2, and FastMM4-AVX memory manager (https://github.com/maximmasiutin/FastMM4-AVX), Release build. But my previous test mistakenly enabled FullDebugMode for the RELEASE build.
Now that with your reminder, I disabled FullDebugMode. And re-run the test (using the Static Object List DB)
x86
Single Thread : 440842 records/sec inserts
Multi Thread (2 threads) : 474982 records/sec inserts
x64:
Single Thread : 471051 records/sec inserts
Multi Thread (2 threads) : 486679 records/sec inserts
So, in my case, using Delphi default MM,
Single Thread, x64 is 7.0% FASTER than 32bit
Multi Thread, x64 is 2.5% FASTER than 32 bit.
So you are right, x64 is indeed somewhat faster than x86.
Question - do you have any recommendation for memory manager to use? Thank you again.
I found mormot2 seems to be significantly slower with x64 built
The same application in x86 (32bit) build is almost 2 times faster then its x64 built. Is this expected?
In other words, is x86 (32bit) preferred for performance oriented application?
I changed back to FastMM4-AVX , and things seem to be working normal again.
TAlgoSynLZ.AlgoCompressDestLen exists.
@ab - you are right.
I found the problem - it is related to FastMM5.
At mormot.core.buffers line 5276
if {%H-}dst = '' then
SetString(dst, nil, AlgoCompressDestLen(Head.UnCompressedSize));
If FastMM5 is enabled (i.e., included in the project file), line 5276 would have access violation at address 0x00000001, and no log file will be archived.
If I removed FastMM5, then there won't be access violation, log files can be correctly archived.
I don't understand why FastMM5 would contribute to this, but guess I'll have to remove FastMM5?
EventArchiveSynLz ends up access violation at mormot.core.buffers, line 5276,
SetString(dst, nil, AlgoCompressDestLen(Head.UncompressedSize))
AlgoCompressDestLen appears to be an undefined abstract method.
I have a project that references mormot.db.raw.sqlite.static.
Windows x64 build gave strange hints, complaining certain variables are declared but never used in 'mormot.db.raw.sqlite3.static' --- see below screen.
Is this something that I should worry?
There are two versions of FormatUTF8, one is defined in mormot.core.text, the other in mormot.core.json
Is this an intentional design?
I found some interesting post long time ago, about automatic UI generation. https://synopse.info/forum/viewtopic.php?id=280
Is this going to be (yet) supported by mORMot2?
Of course, the whole REST framework is thread-safe.
Thank you very much.
Is it allowed to change SQL Model (e.g., adding more tables), and call VirtualTableExternalRegister again, after TRestServerDB instance has been created?
For example:
FSQLModel := TSQLModel.Create([FTable1, FTable2]);
FServer := TRestServerDB.Create(FSQLModel, SQLITE_MEMORY_DATABASE_NAME, False, '');
Then, I need to add more tables FTable3, FTables4 to FSQLModel, LATER after FServer is created. Can I used the following code?
FSQLModel.AddTable(FTable3);
FSQLModel.AddTable(FTable4);
Is TRestServerDB threadsafe?
In one running process, there is an active TRestServerDB instance, which itself is a SQLite in memory server.
I'd like to access that same TRestServerDB instance from different threads. Is that allowed?
Thank you very much @ab.
It works!
@ab, many thanks for your feedback.
To explain things better, I created a project for you to replicate the issue I encountered. You can download it from here
Given the following record definition:
TSegment = class(TSQLRecord)
strict private
FID: Int64;
FAttrs: Variant;
published
property Attrs: Variant read FAttrs write FAttrs;
end;
And the following JSON string:
LJSON := '{ "RowID": 1, "Attrs": {"SegmentID": "1976-113", "From":"LEXINGTON AV/72 ST","To":"LEXINGTON AV/57 ST"} }'
ObjectLoadJSON (LSegment, LJSON, nil, [jpoHandleCustomVariants]) would return False, indicating invalid JSON format. By debugging, it shows that the parsing broke at "Attrs" - when the parser expects its value to be double quoted.
In other words, if the value of a Variant field is NOT DOUBLE QUOTED, ObjectLoadJSON would fail.
Note: ObjectLoadJSON had been working no problem with early mORMot2 (before June 2021). I suspect some changes introduced to mormot.core.json changed the behavior of ObjectLoadJSON.
Is this an intentional design, or a potential bug?
Thanks.
I downloaded from https://synopse.info/files/mormot2static.7z, today,
But it still seems to be the old 3.35 version.
This is a following up of this post https://synopse.info/forum/viewtopic.php?id=5887
It seems EventArchiveSynLZ would crash at mormot.core.buffers, Line 5073
if {%H-}dst = '' then
SetString(dst, nil, AlgoCompressDestLen(Head.UnCompressedSize)); // CRASHHHHHHHH at this line
Thank you @ab and @flydev
It turns out the problem was caused by a StackOverFlow exception when the software was trying to sort an array in a background thread (spawned by OmniThread Lib). I used EurekaLog (with multi-threading enabled) to figure that out. So it is not mORMot2 or TSynLog problem.
But I do have a question - it seems that TSynLog cannot capture the StackOverFlow exception that happened in a background thread?
I guess the first sentence is not finished.
Enable the logs and see where exactly the EIOERror is triggered.
I doubt it comes from TSynLog.
The whole story is here https://en.delphipraxis.net/topic/5092- … violation/
Basically, I had a multi-threaded application (mORMot2), which also uses TSynLog for logging purpose. I have TSynLogPerThreadMode = ptIdentifiedInOnFile
Then, the application after started, would run normal for a couple of hours, then it crashed. By "a couple of hours" - it is really random, sometimes it is 2~3 hours, sometimes more.
The crash is only logged by Windows Event Viewer, as Access Violation, with the fault offset. TSynLog itself didn't log anything. madExcept (if enabled) didn't log anything either. FastMM5 (if enabled) didn't log anything, either.
From the fault offset, I can trace into somewhere in EIOError code.
If I turn off TSynLog completely, then the access violation would be gone, and never happened. Therefore, I suspect it is something related to TSynLog under multi-threaded environment.
Right now, I had to turn of TSynLog and the application seems running all right without any problem.
If sllException is enabled, will TSynLog conflict with MadExcept or EurekaLog, in terms of Exception reporting?
How would TSynLog work, if TSynLogPerThreadMode = ptIdentifiedInOnFile, and
I am scratching my head, because I am occasionally having EIOError with "File access denied" problem. By "occasionally" - I meant - the application (mORMot2 based) will run normally for a couple of hours then it crashes with "File access defined" error.
Any advice? This might not be mORMot2 problem but I am asking just in case folks here may share some insights.
OK- it seems fine based on SQLite documentation:
https://www.sqlite.org/inmemorydb.html
"Opening two database connections each with the filename ":memory:" will create two independent in-memory databases."
Is it allowed to have multiple TRestServerDB instances in one Process?
The TRestServerDB instances are created using:
FServer := TRestServerDB.Create(FSQLModel, SQLITE_MEMORY_DATABASE_NAME, False, '');
Before asking from abstract, enable the logs and check how many time take each step.
Perhaps the connection is recreated, or whatever.
I can't comment here. It could come from very diverse reasons. Also 6014*3 = 18,000 rows per seconds seems a pretty low number to me.(and please don't put the logs here, but e.g. as an external gist, as asked by the forum rules)
I am pretty sure that the connection is not re-created.
This log snapshot shows that: BatchAdd 6014 rows takes 2.14 s
https://www.dropbox.com/s/cnbqzmzpmtub6 … 5.png?dl=0
This log snapshot shows that: for the NEXT MINUTE, BatchAdd 6014 rows takes 263.39ms
https://www.dropbox.com/s/ggfi5aqrpill4 … 9.png?dl=0
I don't understand why it has x10 speed difference, for the same number of rows. Any heads up appreciated!
mORMot2, Zeos, PostgresQL
Every ONE minute, the software (that uses mORMot2 as the ORM) will BATCH ADD a fixed number of 6014 rows. Again, This happens EVERY ONE minute.
I logged the time for each BATCH ADD operation.
I am perplexed by the pattern I observed - for every 5 consecutive BATCH ADD operations (that happened in 5 consecutive minutes), I saw the following pattern repeated like below:
BatchAdd 6014 rows takes about 2 sec
BatchAdd 6014 rows takes about 300 ms
BatchAdd 6014 rows takes about 300 ms
BatchAdd 6014 rows takes about 300 ms
BatchAdd 6014 rows takes about 300 ms
In other words, every 5 minutes, the BatchAdd is slower to insert, then in between much faster.
What could cause this pattern? Could this because I "misuse" mORMot, or some magic switch misplaced?
I'd expect EACH BatchAdd to have relatively similar time......
Yes. The TDateTimeMS field has a setter.
TDateTimeTS ?
Where do you think the problem comes from?
Yes. I meant TDateTimeMS.
The problem of ObjectLoadJSON ended up in this part of code: mormot.core.rtti, LINE 3305-3309
function TRttiProp.SetValue(Instance: TObject; const Value: variant): boolean;
var
k: TRttiKind;
v64: Int64;
f64: double;
u: RawUtf8;
begin
result := false; // invalid or unsupported type
k := TypeInfo.Kind;
if k in rkOrdinalTypes then
if VariantToInt64(Value, v64) then
SetInt64Value(Instance, v64)
else
exit
else if k in rkStringTypes then
if VarIsEmptyOrNull(Value) then // otherwise would set 'null' text
SetAsString(Instance, '')
else if VariantToUtf8(Value, u) then
SetAsString(Instance, u)
else
exit
else if k = rkFloat then
if VariantToDouble(Value, f64) then // <<--- the value is a DateTime string "2021-05-20T21:21:20.819", but the code is trying to convert it to a Double.
SetFloatProp(Instance, f64)
else
exit
else if k = rkVariant then
SetVariantProp(Instance, Value)
else
exit;
result := true;
end;
ObjectLoadJSON cannot load a TDateTimeTS field, like the following (which was saved using ObjectToJSON).
{"ID":0,"CreateTime":"2021-05-20T21:21:20.819"}
Bug?
I am trying to look for EventArchiveSynLZ in mORMot2, but it seems missing. Is this purposeful?
Also RTree are available at ORM level.
@ab
Thank you. I am looking at spatialite https://www.gaia-gis.it/fossil/libspatialite/index
Do you see any problem that using mORMot2 + spatialite (as SQLite extension)? Advice?
I am suffering a similar/related issue (mORMot2).
To use ObjectTOJSON, I have to explicitly specify ObjectToJSON(myObject, [woStoreStoredFalse]), otherwise, the object won't be correctly serialized (i.e., those AS_UNIQUE fields will be missing).
PostGIS is a Postgres extension, so the same protocol is used between DB and clients, and on SynDB level it works
Thank you, but at ORM level, what data type should I use for Geometry type? Blob?
May be this is an out-of-touch question:
can mORMot work with Spatial Database, like PostgreSQL/PostGIS?
OK.
I believe there is a bug of mORMot2
mormot.db.sql.zeos, line 694
sTableName := meta.AddEscapeCharToWildcards(sTableName); // <---- this line should be removed, because AddEscapeCharToWildcards is called TWICE!
// do not escape https://synopse.info/forum/viewtopic.php?pid=34896#p34896
res := meta.GetColumns('', sSchema,
meta.AddEscapeCharToWildcards(sTableName), '');
I use Postgresql connection class. It seems that mormot.db.sql.zeos, the following method doesn't work properly?
procedure TSqlDBZeosConnectionProperties.GetFields(const aTableName: RawUtf8;
out Fields: TSqlDBColumnDefineDynArray);
Line 696
res := meta.GetColumns('', sSchema,
meta.AddEscapeCharToWildcards(sTableName), '');
always returns EMPTY, thus the returned
out Fields: TSqlDBColumnDefineDynArray
is always empty. On the other hand, however, from the backend PostgreSQL server log, I can see that the server is queried with correctly generated SQL, which indeed returned the list of fields.
Because returned Fields is always empty, CreateMissingTables will always try to recreate existing tables because of the empty Fields returned.
Is this a bug of mORMot2, or I am doing something wrong somewhere?