-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat (client): flag to disable FKs on incoming TXs in SQLite #1281
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevin-dp I think its possible for a non-satellite transaction to happen between dissabling FK checks and the actual satellite transaction, and again possible afterwards, before they are turned on again.
Our adapter mutex is at the transaction level only. Do we not need another mutex, a connection level one, that satellite can hold during what is in effect multiple transactions?
@kevin-dp Do you have some numbers about this performance improvement? I'm wondering if using |
You're right, concurrent TXs could occur between the DAL and the Satellite process which could get badly interleaved.
All async drivers are built on top of our generic drivers. What we are trying to achieve here is to group So that the Satellite process could do: adapter.group((a) => {
a.run({ sql: `PRAGMA foreign_keys = OFF` })
...
a.run({ sql: `PRAGMA foreign_keys = ON` })
}) We can extend the generic database adapter with the |
@samwillis did some initial experiments with disabling FKs and has some numbers about the speedup it can provide.
Do you mean to defer/disable FKs inside every TX and thus calling that inside the adapter's |
Yes, that's what I meant. I suggested the defer because I thought that a possible performance degradation could be SQLite doing foreign key checks for every INSERT/UPDATE in the Satellite transaction and just deferring all checks to the end would be faster. |
… also disable FK checks when applying remote migrations.
ba748f5
to
1d0ccb3
Compare
I added an implementation of To be honest, this PR adds quite some additional complexity around the drivers for this optimization. I don't think i like it. |
The performance issue that this PR is addressing is related to large initial syncs. What we have found is that under some circumstances the time to apply the transaction increases exponentially. With the linearlite example, 1100 issues + their comments takes under a second (to fully load the app), and has scaled to that point almost linearly. At 1200 it's up to something like 5-10 seconds, and at 1300 it's at over 30. I forget the exact numbers (currently sitting in an airplane awaiting takeoff). It's directly related to the order of the inserts and if the FK is pointing to a row inserted before or after its own row. I'm suspicious it affects the WASM SQLite specifically, but need to test. I think it's related to doing a full scan for each FK reference, and it may be that there is memory allocations that cause the slow down with WASM. Deferring FK checks does not solve the problem unfortunately, only turning them off does. However, there is another potential fix, to topologically sort the inserts based on FK, this almost entirely eliminates the slow down, but unfortunately won't solve the problem for circular references. I'm not sure yet which solution we will go for, maybe both. |
@kevin-dp agreed, it's looking quite complex. Maybe we should take a look at the topological sort of the inserts before we make a decision? |
@samwillis Thanks for the input! We would be interested to try to reproduce it in the Dart client. But first we would like to reproduce it with the official client. In the linearlite example we've added this. It creates 1500 issues and 1 comment per issue (using faker to get random values). <button
onClick={async () => {
const total = 1500
for (let i = 0; i < total; i++) {
console.log(i, 'out of', total)
const title = faker.company.name()
const date = new Date()
const issue_id = uuidv4()
db.issue.create({
data: {
id: issue_id,
title: title,
username: 'testuser',
priority: Priority.NONE,
status: Status.BACKLOG,
description: faker.lorem.paragraphs(3),
modified: date,
created: date,
kanbanorder: generateKeyBetween(null, null),
},
})
db.comment.create({
data: {
id: uuidv4(),
issue_id: issue_id,
body: faker.lorem.paragraphs(1),
created_at: new Date(),
username: 'testuser',
},
})
}
}}
>
INSERT RANDOM
</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, nice work! I like the refactoring of the transaction
adapter API and the group
one is quite simple so I think it works well, left a couple of comments w.r.t. tests and conventions
…ragma afterwards.
@kevin-dp If we are going ahead with this could we maybe name the new grouping method |
Not a fan of |
Agreed with @samwillis and @msfstef to rename |
This PR introduces an additional flag that can be enabled when electrifying a SQLite database. When the flag is enabled, the Satellite process disables FK checks on incoming transactions. This flag is useful for performance reasons as skipping FK checks greatly speeds up initial data sync when there is a lot of data.
Note that disabling FK checks can't be done transactionally so we have to disable FKs, apply the TX, and then re-enable FKs. Therefore, it is important that no transactions are ran concurrently (this is ensured by the mutex that our drivers hold).
We assume that Electric exlusively owns the DB connection and thus that nobody executes a concurrent TX directly on the DB connection.