#1 Re: mORMot 1 » Memory leak in mORMotWrappers » 2019-06-18 08:42:42

isa

Hi, since I'm not getting reply, I just wanted to ask to make sure.
In order to solve this problem I added the following line:
varClear(result);

I added this in TWrapperContext.ContextFromMethods
Right before calling TDocVariant.NewFast(result);

Just wanted to ask if this is safe to do? The wrapper system still works as intended, but I suppose this will not break anything else right?
Just want to make sure.

#2 Re: mORMot 1 » Memory leak in mORMotWrappers » 2019-06-12 13:49:33

isa

Thank you for the fast reply,

I use Delphi XE 10.2

I don't know the Alt-F2 key, but trying Alt-F5 or Ctrl-F7 during debugging, I do see that the value is unassigned and becomes Null after the first call of ContextFromMethods.
After that, when entering ContextFromMethods the next time, I see result being assigned from the very beginning, and NewFast makes it Null again.

But I forgot to give you some extra details regarding the memory leak. The memory leak doesn't happen until you call AddItem inside the for loop.
So once again.
1st time calling ContextFromMethods, which is called by the loop of "CreateFromModel", no problems, memory leaks whatsoever.
2nd time calling ContextFromMethods, nothing goes wrong even after calling NewFast, but from there on in that for loop, every single AddItem causes a memory leak. I made sure if it wasn't the "ContextFromMethod" inside the AddItem call sent as parameter, which it wasn't because even when you call this following line in that for loop, it creates a memory leak:
TDocVariantData(result).AddItem('a');

And when I add VarClear(result); before calling TDocVariant.NewFast(result); The memory leak is gone.

Also of course all this code I mentioned is from mORMotWrappers.pas

Hope this helps.
If necessary I can provide you more info that you need.

#3 mORMot 1 » Memory leak in mORMotWrappers » 2019-06-12 12:25:48

isa
Replies: 3

There is a memory leak caused When WrapperMethod is called, upon visiting the wrapper page to generate mORMotClient code.

The memory leak is specifically caused by the method function TWrapperContext.ContextFromMethods(int: TInterfaceFactory): variant;
Where "Result" can already be initialized by a previous call of this very method, resulting into calling init again on the same TDocVariantData, which results into AnsiString memory leaks.

I don't know with 100% certainty, if this is caused by our specific mustache files or other configurations, but I'm quite certain this is not caused by my code.

Now you could say, (As I initially thought), how come result is assigned before you even enter the method? Well it seems like the previous TDocVariantData result instance created by that function, seems to remain there, I suppose as "garbage".
Similar bihaviour can be seen in SynCommons.pas, in the function:
function _ObjFast(const NameValuePairs: array of const): variant;

There you can see that the first thing that is done is, is checking if result needs to be cleared prior to getting initialized again. So exact same concept I think, however in case of the method "ContextFromMethods" (Which is called in loops in quite some places), there is no such precaution available.
In ContextFromMethods, in order to initialize the resulting TDocVariantData, the method "TDocVariant.NewFast" is used, which on its turn calls InitFast, which doesn't do such a check such as in _ObjFast.

Calling VarClear(Result) before NewFast is called inside the method "ContextFromMethods", seems to solve the issue. But I don't know If it's dangerous to call it, meaning that I don't know with certainty, that initialized result data is somehow referenced somewhere else, and can be used.

So, it would be nice if someone else can look at this, confirm this, and possible also think for a (better) solution.

Kind Regards.

#4 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-04-26 14:13:18

isa
pvn0 wrote:

I am happy your solution works for you, however I think you have misread the fix. The reason you get exception in DB.TransactionBegin is because the race condition creates a soft lock always thinking there is an active transaction when in reality there is not and as a result the db sqlite code will throw exception in something like Exception: "no transaction active" when the TransactionBegin tries to rollback a previous transaction that is no longer active. 

Either way, I have tested this fix with the sample program I provided in this thread and it solves this problem.


My bad, indeed you call DB.BeingTransaction BEFORE Inherited transaction.
Thanks for your input.

#5 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-04-26 12:59:29

isa
pvn0 wrote:

@ab, @isa,

Please see #191 pull request. I've tested and I believe this is best fix.

Thank you for sharing your attempt on fixing it.
However that does not work for my needs.
In your fix you simply check the result of the "DB.TransactionBegin"  call alongside the existing result check of  "inherited TransactionBegin(aTable,SessionID)"


Problem is that FB.TransactionBegin Doesn't simply return False, but raises an Exception. So your check wouldn't make any difference in my scenario. Still leaving the Transaction at mormot level not rolled back.

Once again my solution was to change the Code in the Except block to:

except
  on ESQLite3Exception do
  begin
    result := false;
    try
      // releasing fTransactionActive flag
      inherited RollBack(SessionID);
    Except
      //
    end;
  end;
end;

As I did state above. This works good enough for me. Not saying this is the best solution, but your solution unfortunately doesn't fix my issue.

#6 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-02-21 14:17:01

isa

@ab
I have been using my fix for a while now, and my issues are solved.
Could you perhaps look into this? Is there perhaps a reason you don't want to change this? Or do you perhaps have other plans with mORMot, now that you switched to AI development?

#7 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-01-30 11:45:12

isa
pvn0 wrote:

@isa , your 1st solution fixes the problem, nice work! smile @ab please review this.

Thank you for the the confirmation, and once again thank you for replying to this post and taking your time and effort to look this up.
Really appreciate that!

Thank you.

#8 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-01-30 11:04:42

isa

Indeed, this seems exactly like the issue. Especially if between the Commit succes and first Transaction Start failure an exception took place of this type: 'cannot rollback - no transaction is active'.
Thank you very much for your time and effort!

The solution I did was the following, inside the TransactionBegin in mORMotSQLite3.pas:

function TSQLRestServerDB.TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean;
begin
  result := inherited TransactionBegin(aTable,SessionID);
  if not result then
    exit; // fTransactionActive flag was already set
  try
    DB.TransactionBegin; // will call DB.Lock
  except
    on ESQLite3Exception do
    begin
      result := false;
      try
        // releasing fTransactionActive flag
        inherited RollBack(SessionID);
      Except
        //
      end;
    end;
  end;
end;

Now this seems a little uglyish to have a try except in a try except, so I also had this as a solution:

function TSQLRestServerDB.TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean;
begin
  result := inherited TransactionBegin(aTable,SessionID);
  if not result then
    exit; // fTransactionActive flag was already set
  try
    try
      DB.TransactionBegin; // will call DB.Lock
    finally
        if NOT DB.TransactionActive then
          inherited RollBack(SessionID);
    end
  except
    on ESQLite3Exception do
      result := false;
  end;
end;

Now that 2nd one should also be okay, as the only scenario where DB.TransactionBegin ends with fTransactionActive remaining false, is when the call Execute('ROLLBACK TRANSACTION') throws an exception, which is caught by the try except.
So both solutions should be fine, but I did prefer the 1st solution, because me not being able to fully trust fTransactionActive being thread safe, especially when SessionID's are not unique per transaction.

Once again thank you for your efforts. Really appreciate that.

Since you already have that code, is it also possible for you to test whether or not the issue is actually solved after adding the fix I mentioned to mORMotSQLite3.pas?

#9 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-01-29 11:01:19

isa
pvn0 wrote:

It took me a while but I see your point, you are correct, there is a chance that TransactionBegin will call

Execute('ROLLBACK TRANSACTION;')

and throw exception just because fTransactionActive is not actually thread safe.

However, this seems to be accounted for, if you look at TSQLRestServerDB.TransactionBegin :

function TSQLRestServerDB.TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean;
begin
  result := inherited TransactionBegin(aTable,SessionID);
  if not result then
    exit; // fTransactionActive flag was already set
  try
    DB.TransactionBegin; // will call DB.Lock
  except
    on ESQLite3Exception do
      result := false;
  end;
end;

In your example, DB.TransactionBegin will throw exception and it's caught and result is set to false.
You can inherit from Server classes so you can fine tune this function yourself to return more info if you need.

Personally I think the transaction code is very clever, I like how ActiveTransactionSession is actually released at the TSQLRest level before any transaction commit or rollback actually takes place, so that in a multi-thread env, threads don't need to wait until Database execution has already happened but instead are automatically blocked through a critical lock when they try to Commit.

First of all, thank you for your reply.
I have no problem with how fActiveTransactionSession is released with a commit.
On a commit there are 3 things that needs to be done for another transaction to be able to starts which is in this order:

fActiveTransactionSession := 0;            // this is done thread safe
Execute('COMMIT TRANSACTION;');      // This is the actual release on the SQL database
fTransactionActive := False;                  // This one is set to false at the very last moment in case of a commit, yet on a transaction begin this is the last check that is done before another transaction starts (not necessarily a bad thing), and it seems like it's not thread safe.

Like you mentioned, this is overall good, it works perfectly as intended 99% of the time, if not more. Thing is, I have a rather large mORMot server with lots of users on it. so it can happen like once or twice a week that I have this bug.
I mean it's even stated as comments on the TransactionBegin definition, that a rollback has to be done in case of a sql execution error.

The SQL Execution error does happen in my case because of not being fully thread safe. Not a huge deal, it has proper error handling, so nothing goes wrong, apart from the part that rollback is not called.
And with rollback I mean the rollback method of TSQLRest.RollBack in mORMot.pas, though a full rollback call from TSQLRestServerDB.RollBack should also do the trick of course.

Perhaps this rollback is not done, because not in all cases the SQL Error is due to there not being anything that can be rolled back.
Perhaps Arnaud decided to do this, just to make sure that in case the actual rollback on the SQL failed (meaning that the transaction is still active), he wants to avoid calling TSQLRest.RollBack as it is not a wanted scenario. So basically this not necessarily being a bug but a decision.

As you saw the TransactionBegin does an Execute('ROLLBACK TRANSACTION;') in TSQLDataBase.TransactionBegin in case fTransactionActive is True. If you ask me, If this happens, clearly a rollback is called on SQL level. so it should also be called on mORMot level on TSQLRest. That's the inconsistency here.
So if fTransactionActive is True,
Execute('ROLLBACK TRANSACTION;') is called, which is good
fTransactionActive is set to False, which is also good
But fActiveTransactionSession remains what it is. So in that Rollback scenario that happens inside TransactionBegin, in some way also TSQLRest.RollBack in mORMot.pas has to be called. so that TSQLRest in mORMot.pas and TSQLDataBase in SynSQLite3 are in sync.

And once fActiveTransactionSession remains > 0 while fTransactionActive is set to false, you cannot start a transaction anymore whatsoever unless you do a manual Full Rollback first.
And my issue was even worse. I didn't used to use a separate SessionID's for my transactions until a couple months back. Causing journal files to be created after this bug happened, and every single change on the database that happens outside transactions would be stored in those journals, and I had no manual code to call commit, and I also wasn't sure back then if this transaction bug was caused due to some error in the code, thus me not necessarily wanting to commit but do a rollback instead, but that also meant that I would loose all those other changes stored in those journal files. Causing lots of issues with our users.

Luckily the journal file issues are gone ever since using the thread ID of each separate client connection as the session ID, but still this bug happens from time to time, blocking any other transaction to run from that point on until I restart the running REST server.

So yeah, not certain what possible solution I'd go for this, as I also don't want to possibly introduce other unwanted behavior as a result of my changes and me potentially overlooking thins.

#10 Re: mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-01-28 14:30:04

isa

I just noticed on the definition of TransactionBegin the following comment:
// - must be aborted with Rollback if any SQL statement failed

Which is basically what I end up doing in my "bugfix" proposal.
Reading this comment, how am I supposed to write correctly a proper transaction clause?
As this does not comply with that comment:

TransactionBegin
try
  ...
  Commit
finally
  Rollback
end;

Any SQL Exception is caught INSIDE TransactionBegin, so there is no way for us to tell whether or not an exception occurred, and if or not this is an exception as a result of a failed SQL statement.

EDIT: NOTE: I forgot to mention. That in my code whenever TransactionBegin fails, I stop that code, as I don't want to proceed doing actions on the database, if the transaction hasn't started yet, nor do I want to call rollback, in case transactionBegin failed, because another transaction was in progress. So I either need specific details as to why the TransactionBegin Failed, or upon failure, transactionBegin should do a proper fallback depending on why it failed.

#11 mORMot 1 » Potential Bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin » 2019-01-28 11:14:18

isa
Replies: 13

Hi,

I think there is a bug in mORMotSQLite3 TSQLRestServerDB.TransactionBegin.

In that method, whenever inherited TransactionBegin(aTable,SessionID) is called, and the result is true,
that means that TSQLRest.fTransactionActiveSession is set to the SessionID of that transaction.

So the transaction session is successfully allocated to this very transaction.
then there is the following code:

try
  DB.TransactionBegin; // will call DB.Lock
except
  on ESQLite3Exception do
      result := false;
end;

So if DB.TransactionBegin were to throw an exception, the exception is caught returning a False, which indicates that running the transaction failed.
However, returning a False is not enough! Because earlier inherited TransactionBegin(aTable,SessionID) succeeded, TSQLRest.fTransactionActiveSession is still set to SessionID, and is NOT reset.
Meaning that whenever this situation occurs (that DB.TransactionBegin throws an exception), fTransactionActiveSession Remains > 0, meaning that NO other transaction can be start, because
inherited TransactionBegin(aTable,SessionID) (which is the first thing that is called whenever you call TransactionBegin on mORMotSQLite3) will always return false as a result.
It ONLY returns true, when fTransactionActiveSession=0.

So EVERY other TransactionBegin call on mORMotSQLite3 will fail if even once the DB.TransactionBegin call in the highlighted code above fails.

Correct me if I'm wrong but that's my conclusion.
Because if we reach the call DB.TransactionBegin, it means that inherited TransactionBegin(aTable,SessionID), meaning that fTransactionActiveSession and fTransactionTable are both allocated to that very call.
meaning that if this transaction fails, inherited rollback needs to be called to release back fTransactionTable and especially fTransactionActiveSession, because that blocks any other attempt to do a transaction on that database.

This issue can be avoided by doing this:

try
  TransactionBegin
  ...
  Commit
finally
  Rollback
end;

So by basically moving the TransactionBegin INSIDE the Try finally block, that even if TransactionBegin were to fail, and even if there is no actual transaction active, it still calls Rollback, which on its turn Calls the TSQLRest.Rollback in mORMot.pas, which resets fTransactionActiveSession back to 0.
This is a weird solution and shouldn't be necessary, because whenever something goes wrong in DB.TransactionBegin, it still Calls Execute('ROLLBACK TRANSACTION;'); So Me doing an extra rollback in that scenario shouldn't be necessary. And procedure wise doesn't make sense to call Rollback if BeginTransaction failed. Unless it was meant to be used like that which i doubt.
Also this code is not really safe, If TransactionBegin were to fail for other reasons, unless you always use a different sessionID's for each other transaction call you do, especially when done on other threads.

Nonetheless I think this would be a possible solution that would make more sense:
So the exact same code as above, but changed as following: (Note: this is not necessarily the solution I suggest, is just to give an idea)

try
  DB.TransactionBegin; // will call DB.Lock
except
  on ESQLite3Exception do
  begin
    result := false;
    try
      // resetting fTransactionActive flag
      inherited RollBack;
    Except
      //
    end;
  end;
end;

Once again, because we know fTransactionActiveSession is allocated to that specific call because of the succeeding inherited call, I think it should be safe to do an inherited Rollback call that releases back fTransactionActiveSession.
Once again, correct me if I'm wrong, or feel free to suggest better solutions if possible.



Now, of course there is a reason why I suggest this fix, because I have an issue as following:
Imagine 2 consecutively running threads (2 clients) that both tries to do a transaction at the same time. Useful to note that I always pass GetCurrentThreadId as SessionID.
Now this is the exact scenario that happens:

> Thread 1: BeginTransaction Enter
> Thread 1: BeginTransaction Exit (success)
> Thread 1: Commit Enter
> Thread 1: Execute('COMMIT TRANSACTION') (NOTE: It might be possible that this line takes longer, or is called after Thread 2 has started, but the important part is that it has proceeded enough so that at the point when Thread 2 calls Execute('ROLLBACK TRANSACTION;'), it throws an exception)
> Thread 1: fTransactionActiveSession := 0
> Thread 2: BeginTransaction Enter (Initial check succeeds, because inherited BeginTransaction returns true because fTransactionActiveSession = 0) (IMPORTANT NOTE!!: At this point Thread 1 has NOT set fTransactionActive back to False yet in SynSQLite3 in TSQLDataBase)
> Thread 2: TSQLDataBase.TransactionBegin ENTER
> Thread 2: TSQLDataBase.TransactionBegin Throws Exception: Because fTransactionActive is still true from Thread 1, so instead this code is called by Thread 2: Execute('ROLLBACK TRANSACTION;'); which throws an exception because there is no Running transaction as Thread 1 already called Execute('COMMIT TRANSACTION')
                  the exception: ESQLite3Exception {"ErrorCode":1,"SQLite3ErrorCode":2,"Message":"Error SQLITE_ERROR (1) using 3.8.7.4 - 'cannot rollback - no transaction is active' extended_errcode=1"}
> Thread 2: Sets fTransactionActive to False in TSQLDataBase.TransactionBegin
> Thread 2: BeginTransaction Exit (while fTransactionActiveSession is still set to Thread 2)
> Thread 1: Commit Exit. (doesn't matter whether or not Thread 2 or 1 exits first)

So as you can see in that example, fTransactionActive (in SynSQLite3 in TSQLDataBase) is set to false too late after fTransactionActiveSession (in mORMot in TSQLRest) is set to 0 by Thread 1, causing TransactionBegin of Thread 2 to proceed further than it should be, causing it to throw an exception and leaving the transaction locked because it doesn't reset fTransactionActiveSession after an exception was thrown.
fTransactionActive (in SynSQLite3 in TSQLDataBase) is in this case the culprit, but that doesn't mean that TransactionBegin should be less safe, because other code is in the wrong. Nevertheless also it does seem to me like fTransactionActive might not be thread safe, and am not sure if that might be related to this issue.


Hope my explanation makes sense and isn't too complicated, can add more details if necessary. Or explain further.

NOTE: The exact order of the calls between the two threads might not be 100% correct, but it's more or less in that order, hopefully making my issue clear.

#12 mORMot 1 » SQLite Transactions Thread safety » 2018-11-15 08:16:45

isa
Replies: 0

Hi,

I have a mormot server that uses a SQLite Database. Multiple users can access the database simultaneously.
I use Transactions on the server code from time to time, as it's really handy to rollback stuff in case something goes wrong alongside the the code. And in certain cases to gain significant performance increase, depending on the amount of changes I need to push on the database.

Now My question was, how thread safe is this? I did see you can pass session ID's so that each transaction is locked to that session ID. I currently am not using it. As I initially never encountered any problems. But recently I have some problems.
I saw that a rollback from another thread stopped a transaction from another. And the commit from the transaction that was accidentally stopped gave an internal SQL exception saying that there is nothing to commit, meaning it actually passed the check from mORMotSQLite3.pas if there is any running transaction, but failed only in SQL level. So those lines don't seem to be thread safe as is.

I presume using session ID's will solve that issue? Is there a way to generate a session ID per thread and re-retrieve the same ID when committing or calling rollback, or do we provide these sessionID's completely ourselves?

Also I somehow, perhaps by my code's mistake, end up very rarely with a transactionBegin, without any rollback or commit being called, Looking at the logs, it's Typically a result of when a couple transaction exceptions occur when multiple users try to do similar things simultaneously, such as, cannot start transacttion due to an ongoing transaction. This transaction is then there for some time, and we have no obvious way to merge the journal file created in the meanwhile with the database, causing data loss on the database.
Even though all our transactions look like this.:

TransactionBegin
try
  ...
  Commit
finally
  Rollback
end

So it's weird to see we can end up in such scenarios. I presume it has to do with the multi thread issues, and perhaps it will be solved once we use sessionID's.
But just to make sure. Is there a way to prevent this?
Like a transaction Auto commit? Or are there any transaction Mutexes?

Thanks upfront

#13 Re: mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-26 14:43:49

isa
ab wrote:

Downsizing the CacheSize seems a good idea, then!

But you may try do to it before BackupServer:

temp := CacheSize
CacheSize := 1024;
BackupServer
CacheSize := temp;

since the backup process doesn't really need the cache IIRC.
So the memory won't grow up at all.

Nice tip, thank you once again, and with this my issue should be solved indeed.
Thanks again!

#14 Re: mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-26 09:54:39

isa
ab wrote:

Page_size can't be changed once a file is created.

And cache_size only at file opening IIRC (to be verified).

Weird,
I must have seen things, testing now again indeed changing page size doesn't seem to do much, and I should leave it be then. Guess I'll only adjust the Cache size.
However like I said resetting the cache size does seem to drop the ram usage. So e.g. my example:

I start a single server. Has 17MB of ram usage.
I do a backup with the standard cache size of 10000, and end up with ram usage of roughly 100MB (the DB in quesiton is 89MB)
I now tried this.

BackupServer
temp := CacheSize
CacheSize := 1;
CacheSize := temp;

And this leaves me with 19Mb of ram usage after calling it.
But nonetheless, you have been a great help to me. Once again thank you

#15 Re: mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-26 07:38:40

isa
ab wrote:

I don't know anything which disable SQlite3 internal cache.
So in your case, I would stick e.g. to 2MB or 4MB cache size for each DB, and rely on the OS global disk cache.

In practice, a 2MB cache size is big enough.
It is the default Sqlite3 value https://www.sqlite.org/pragma.html#pragma_cache_size
The OS will do proper caching for you.

Thank you very much Arnaud. This ended up being it.
I didn't realize that CacheFlush didn't clear that cache in the beginning, later on I was still puzzled because the cache size was set to 10000 Default by SynSQLite3 and my ram usage was significantly larger than 10MB.
But I ended up being wrong.

When combined with the PRAGMA page_size which normally seems to have a default of 1024, instead it seems like SynSQLite3 sets this to 4096 by default. Which means a total cache size of 40MB if I'm correct, for each instance I have. Which seems to be the reason of my issue And Backup was my culprit because it was by far the easiest and fastest way to fill the cache.

Once again thank you!

EDIT: Though I notice now, somehow my math was wrong? setting cache size to 2000 and page size to 1024 still utilizes 20MB of ram, only after setting it to 200 and 1024 the ram usage was lowered to 2MB. I suppose I'm missing something.
EDIT 2: I also can sort of Simulate a Cache flush by setting these values to their minimum (cache 1, and page size 512), and then setting it back to their original sizes, but don't know if that's a safe thing to do. Or if it has some side effects.

#16 Re: mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-25 14:58:34

isa
ab wrote:

I was talking about the SQLite3 internal cache, not the SynSQLite3 JSON cache.

The SQlite3 internal cache is just filled up to its size, never flushed.
The SynSQLite3 JSON cache is flushed on write.

I see, thank you very much, than that might be very well be the cause of the issue.
Do you happen to know by heart how I can clear that/flush that? Is it even possible?
And is that cache significant in terms of performance?

Thanks again.

pvn0 wrote:

I understand now, my bad. So it's a single time increase only per server object instance that accumulates because you have so many instances running. Have you tried to step through code in BackupBackground (and associated background thread ) to see which call allocates this memory? I have my suspicions but want to see if you come to the same conclusion because my dataset is so small it's hard to pinpoint.

No I haven't tried that, perhaps I should, but what ab mentioned does make sense, nonetheless I will look into this further tomorrow morning. Thank you

#17 Re: mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-25 12:30:23

isa
pvn0 wrote:

I did a quick test on my setup , doing BackupBackground on a 50KB db every 10 seconds does not increase memory over time, I am however running 64bit and using stock sqlite.org 64bit precompiled dll, so maybe that's something you could look into. If you still have issues then create a reproducable example that I can run and test.

Hi thank you for your reply, but no, that's not the issue.
The memory usage doesn't increase with every BackupBackground call.
It only increased ram usage, the initial time you use BackupBackground. After that, no matter how many times you call BackupBackground, it does NOT increase the RAM usage. However this only goes for that specific instance of TSQLRestServerDB.

Now if you look at my case, let's say that I only had 1 instance of TSQLRestServerDB running, and it does a backup every night. That would increase the RAM usage only on the first call, so the first night it calls that backup. After that, it doesn't increase anymore the nights after. So It's a single time increase only.
Now my problem is, that I don't have a single instance of TSQLRestServerDB, but 200 running at the same time.

So each instance separately increases the ram usage, for the first BackupBackground that was done for each separate server.

So this is my situation:
I have a thread that runs in the background that calls once every night at 3:00 BackupBackground once for every running instance of TSQLRestServerDB I have.

So my server uses up roughly 1.5GB of RAM.
After the first night that becomes roughly 3.7GB, Which leaves a VERY little headroom for other actions on that server.
But after that first night, it remains more or less around 3.7, since only the first BackupBackground call uses up ram, after that it's fine.

After roughly 1 week of uptime I start to see out of memory exceptions here and there, so I restart my server once a week.

I did also write a small fix for this that is temporarily avoiding the issue, which is doing a quick Free and recreate of the running TSQLRestServerDB instance after the backup for it was done. This clears the used up memory from the backup, and seems to work fine. But would rather avoid that if possible, as I want to know the reason why it keeps that RAM space allocated.

#18 Re: mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-24 16:36:00

isa

Thank you very much for the fast reply,

But no, I don't think the cache is the issue. As you did state that they are cleared on every write operation on the server, and they barely get full after some monitoring. The backup on the other hand seemed like a major issue after monitoring what was using so much ram.
I tested manually on 6 servers with varying Database sizes, ranging from 20MB up to over 200MB. After calling BackupBackground one by one on each of these databases, I noticed a growth in RAM usage with at least 9MB, up to 90MB of ram usage with each separately single call of BackupBackground. The average was around 35MB increase in Ram usage after every BackupBackground  call.
And like I said, that increase in RAM persists, even after manually flushing the cache, or even completely disabling the cache. Tried other stuff with page sizes etc, but no luck either.

Looked for other stuff, and am still searching to prevent or clear this somehow, as I do feel like those spaces are used up unnecessarily, but am not sure of course.

Hoping to find an answer soon.

Thanks again.

#19 mORMot 1 » RAM usage issue TSQLRestServerDB calling BackupBackground » 2018-09-24 15:08:13

isa
Replies: 13

Hi,

I have a server that has multiple instances of TSQLRestServerDB running on it. For each user we have a separate databse that has his own data and do his own stuff.
We do backups on these servers once every night using the BackupBackground call. Making a local copies of the database files.

This works great, everything is fine.

However, lately the users increased, reaching approximately 200 and some of them are intensive users who have lots of data, which have databases up to 200MB. Problem is, because I created a 32bit server application (due to certain libraries I use), I am limited to roughly 4GB of RAM.
Normally I have enough resources to handle all of this on the system this server is running, but the BackupBackground significantly increases the memory usage and is not released. And the amount of memory used up depends on the actual size of the database. It's not perfectly linear, but there is a clearly visible correlation.

If this was only for the moment when the backup was created, no problem, but because this is done for roughly 200 databases. It increases the memory usage A LOT. And can only solve this by stopping the servers. I initially thought this had to do with the cache of the database, but that wasn't the case. That was limited to the default 10MB , and clearing it didn't have any effect. Using different parameters on BackupBackground didn't change anything either, read through the comments for that method and similar methods in the code and couldn't find much.

So my question is, what is using this memory? Why is this persistent? And how can I avoid this?

Thanks upfront.

#20 Re: mORMot 1 » How to see changes since... for ORM Objects? » 2018-08-31 07:15:04

isa
ab wrote:

Do you mean Master/Slave replication?
It is indeed global to the whole table.

There is no built-in mechanism in the ORM to track the changes of only a sub-part of the table.

But it could be very easy to add it in your business code:
1. add a "version" field which is incremented at every change (a global counter via InterlockedIncrement() is enough)
2. query for all records in a given range which have the "version"> latest version you got.

Thanks for a quick reply.
I see, that's kinda like what I initially had in mind using the TModTime, but that still meant going to the server and doing a query on the table.
Now retrieving ONLY the "objects changed since ..." is one thing, which I can indeed handle with the version numbering like you said.

But more importantly I'm currently looking for something to see client side IF there is ANY change on the table, before I have to go and do a full query on the table.
Now I notice that there is a property called InternalState, is that something that can be used for what I described?

#21 mORMot 1 » How to see changes since... for ORM Objects? » 2018-08-30 13:36:24

isa
Replies: 5

Hi,

I have developed an agenda using Delphi with a server and client, where I use ORM objects (TSQLRecords) as each appointment.
There is an automatic refresh system that refreshes the agenda every minute. But that is a full refresh, where the complete date range is re-retrieved that is currently visible on the client side.
That is rather overkill I want to refresh only if there are any changes done on the specific table where these appointment records are stored.

The only thing that I was able to find that came close to it was the audit system, which isn't very practical for this application, very resource heavy and is overkill. I only have to see IF ANY changes took place on said records table since Time X.
So is there any built in system in the ORM to check/track this easily through client side so that it knows it has to do a refresh / retrieve the changes on that table?
Or if not, how can I best implement this?

Thanks upfront

#22 mORMot 1 » How to enable SQLite Journal File Persistence? » 2018-01-09 10:59:51

isa
Replies: 1

Hi, I have a server on which I have multiple db files linked to (so multiple server instances running in this application). And very rarely it happens that somehow a .db file gets locked. Might be a bug in my code or something else not sure.
When this happens, a .db-journal file gets created and all modifications from the moment on when the actual .db file got locked are stored in that journal file one.

However. When such a lock occurs. Chances are that the UI of my server application is also locked, so I cannot stop the server normally, and have to kill the process.
When I restart the server, I would expect (/Want) that the server detects this journal file and merges it with the actual .db file. But instead it disposes this file and the modifications starting from when the actual .db got locked are lost.

I read that you have SQLite options where you set the journal file to persistent, which will merge the journal file with the db file. Does that have to do anything with my question? Can I somehow enable this option in the delphi mormot framework? Or is it already enabled, but me killing the process makes it impossible to merge this file?

Also is there a way I can still merge an existing journal file with a db file later on? Or perhaps a way to read the content of the Journal file, so that I can at least see. what transactions were lost?

Thanks in advance.

Board footer

Powered by FluxBB