Use BEGIN IMMEDIATE for all SQLite transactions by default #13878
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

Summary
Our assumptions about SQLite write concurrency have been that opening a transaction would wait for the write lock to be acquired, and that it would wait up until the 100s timeout that we have configured before raising a db locked error.
Unfortunately, this is not the case: https://forum.djangoproject.com/t/sqlite-and-database-is-locked-error/26994/6
Instead, because the default transaction type is DEFERRED, a write lock is not acquired when the transaction is opened, but only on the first write operation. If a write lock cannot be acquired, SQLite does not respect the 100 second timeout, and instead immediately throws a DatabaseLocked error.
Our expectations are significantly better satisfied by the BEGIN IMMEDIATE transaction, which opens a write lock immediately when the transaction is opened. So, to make Kolibri behave the way we expect, this PR creates a custom SQLite database backend that implements this behaviour.
The possible downside to this is if we wrapped every request in an atomic transaction, so every request was acquring a write lock on the database - we do not use this Django option, so I think that is not an issue. However, we should continue not to use
ATOMIC_REQUESTS = True. This may also cause some performance degradation if any parts of our codebase are opening transactions for long periods of time, but as this solves a critical issue for data collection, I think this is reasonable to move forward with for now.References
Running the locust load tests against this PR results in no failures:

This is in contrast to running without this fix that results in a relatively high percentage of failures, with all of them happening in the trackprogress endpoint, being indicative of potential data loss:

Reviewer guidance
Run the performance test.
Check the reported 503 during class copying is fixed by this.