SQLITE3 and multithreading - errors, mutexes, serialization, error codes?
Added by John Davidson almost 12 years ago
I am using an SQLITE3 dbo backend in a multi-threaded app, such that: a main, headless thread has its own database connection taken from a master connection pool; each WApplication web client has its own connection to the same database file, taken from the master connection pool; the web clients use query models to display the database, in some cases with multiple query models all accessing the same underlying SQLITE3 table (ie different views of the same tables).
If the main thread runs jobs without any web clients (eg its jobs are initiated by the scheduler rather than initiated by a user) the jobs run without errors. However, when one or more clients are updating their query models to view changes in the database made by the headless thread, the headless thread occasionally throws exceptions when it's trying to write to the db.
I've tried using a global mutex, that locks on any writes or query model refreshes, but I still get errors. In any case I don't like doing that because it sort of defeats the point of multithreading (I think?).
A couple of questions:
Is there a way to see the error codes the SQLITE3 backend is returning, if any, when it fails to write to the db? I presume it's some sort of "database busy" error but I'd like to see for sure. I've searched the documentation but see no mention of exception reporting for SQLITE3.
Is the SQLITE3 backend even implemented for multi-threading (serialized) in Wt::Dbo? If it's not implemented in serialized mode, is there a way to turn that on (for instance with a FULLMUTEX flag) when I create the connection pool or something?
Generally speaking, should each client have its own database connection from the pool, or should I have all the clients and the headless thread sharing the one connection? As far as I can tell, the various connections are blocking each other.
Generally speaking, should I be using global mutexes to manage this, or does Wt::Dbo provide some other mechanism (like grabbing the WApplication::UpdateLock or something)? I'm quite new to multithreading and I don't have a great grasp of it.
thanks in advance
John
Replies (5)
RE: SQLITE3 and multithreading - errors, mutexes, serialization, error codes? - Added by Koen Deforche almost 12 years ago
Hey John,
This is a relevant question in the sense that we've also encountered these issues in SQlite based applications.
First of all, you should read up on sqlite concurrency. The default concurrency model easily gets you in deadlocks (which is being avoided and throws the exception) because a write operation needs a write lock which must happen without any reading transactions are active. They have implemented a new wal mode which gets rid of that.
W.r.t. the sqlite error, we are providing the actual error message of sqlite in the exception,
However it does not eliminate the dead lock risk since regularly it needs to merge the write-ahead log in the main datab9
ase file. We haven't actually seen this problem ever happening though.
You really need to move to a real servrt-base database such as Postgres to avoid these problems entirely.
With respect to the error msg, you would want to get the error code in the exception? That is something we should make available indeed.
Regards,
Koen
RE: SQLITE3 and multithreading - errors, mutexes, serialization, error codes? - Added by John Davidson almost 12 years ago
Thanks Koen,
I started to think that might be the case, and looked into Postgres a couple of days ago. I stumbled on some benchmarks which showed the Postgres Dbo backend was slow, 3x or 4x slower than SQLITE3, if I recall due to it not being digital or something (?). Should I expect a big performance hit moving from SQLITE3 to Postgres, assuming the Postgres server is running on the same machine as the Wt app?
What is the fastest backend? Each of my apps' jobs can involve upwards of 20,000 queries, so they're slow enough as it is!
John
RE: SQLITE3 and multithreading - errors, mutexes, serialization, error codes? - Added by Koen Deforche almost 12 years ago
Hey,
Another option which I thought of was to use Sqlite3 with a connection pool of size 1. That will effectively serialize all transactions (wt only takes a connection for the duration of a transaction).
But I do believe that postgres will scale up better as it allows concurrency. There is indeed serialization overhead in the protocol which is especially costly for datetime data but not so much otherwise. Another option is mysql which has the reputation of being faster for simple SQL operations although I don't know if that's still true.
Regards,
Koen
RE: SQLITE3 and multithreading - errors, mutexes, serialization, error codes? - Added by John Davidson almost 12 years ago
Thanks Koen
I set the connectionpool size to 1 and indeed, the exceptions disappeared! But loading the web UI became quite slow... I think I will investigate Postgres further.
Meanwhile, I am trying to catch the exceptions, but I'm getting no results. Can you tell me if this code is right for displaying errors?
try {
session.createTables();
}
catch (Wt::Dbo::Exception exError) {
std::cout << "ERROR CREATING DATABASE - CODE: " + exError.code() + "\n";
}
try {
session.execute("CREATE INDEX fS ON files(size)");
session.execute("CREATE INDEX fP ON files(path)");
session.execute("CREATE INDEX fN ON files(name)");
}
catch (Wt::Dbo::Exception exError) {
std::cout << "ERROR CREATING INDEX- CODE: " + exError.code() + "\n";
}
Also, while I'm asking, why is
session.execute("CREATE INDEX fS ON files(size)");
(and all the index creation commands) throwing an exception in the first place? It's a valid SQLITE3 command. Do I have my session.execute syntax wrong? Or is there an issue with adding indexes immediately after creating a table? Should I do it some other way?
thanks
John
RE: SQLITE3 and multithreading - errors, mutexes, serialization, error codes? - Added by Matt Russell over 10 years ago
We had a similar problem. Our solution to get around the DB locks in our multi-threaded (some headless) app was to overload the "Wt::Dbo::Transaction" with an object that sets a [recursive] mutex if we specify a write operation is going to be run.
i.e.
MySqliteTransaction transaction{session, true}
...
object.modify()->some_property = something_else;
...
transaction.commit();
The MySqliteTransaction sees the true and locks a mutex.
Our app isn't meant to be used by many users though.. Performance issues would surely manifest if we had many concurrent users.