You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/post/2021-03-10-rsqlite-parallel/index.Rmd
+53-16
Original file line number
Diff line number
Diff line change
@@ -13,9 +13,14 @@ tags:
13
13
output: hugodown::hugo_document
14
14
---
15
15
16
-
[SQLite](https://www.sqlite.org/index.html) is a great, full featured SQL database engine. Most likely it is used more than [all other database engines combined](https://www.sqlite.org/mostdeployed.html). The [RSQLite](https://rsqlite.r-dbi.org/) R package embeds SQLite, and lets you query and manipulate SQLite databases from R. It is used in Bioconductor data packages, many deployed Shiny apps, and several other packages and projects. In this post I show how to make it safer to use RSQLite concurrently, from multiple processes.
16
+
[SQLite](https://www.sqlite.org/index.html) is a great, full featured SQL database engine.
17
+
Most likely it is used more than [all other database engines combined](https://www.sqlite.org/mostdeployed.html).
18
+
The [RSQLite](https://rsqlite.r-dbi.org/) R package embeds SQLite, and lets you query and manipulate SQLite databases from R.
19
+
It is used in Bioconductor data packages, many deployed Shiny apps, and several other packages and projects.
20
+
In this post I show how to make it safer to use RSQLite concurrently, from multiple processes.
17
21
18
-
Note that this is an oversimplified description of how SQLite works and I will not talk about different types of locks, WAL mode, etc. Please see the SQLite documentation for the details.
22
+
Note that this is an oversimplified description of how SQLite works and I will not talk about different types of locks, WAL mode, etc.
23
+
Please see the SQLite documentation for the details.
19
24
20
25
## TL;DR
21
26
@@ -25,41 +30,73 @@ Note that this is an oversimplified description of how SQLite works and I will n
25
30
26
31
## Concurrency in SQLite
27
32
28
-
SQLite (and RSQLite) supports concurrent access to the same database, through multiple database connections, possibly from multiple processes. When multiple connections write to the database, SQLite, *with your help*, makes sure that the write operations are performed in a way that preserves the integrity of the database. SQLite makes sure that each query is atomic, and that the database file is never left in a corrupt state. Your job is to group the queries into transactions, so that the database is also kept consistent at the application level.
33
+
SQLite (and RSQLite) supports concurrent access to the same database, through multiple database connections, possibly from multiple processes.
34
+
When multiple connections write to the database, SQLite, *with your help*, makes sure that the write operations are performed in a way that preserves the integrity of the database.
35
+
SQLite makes sure that each query is atomic, and that the database file is never left in a corrupt state.
36
+
Your job is to group the queries into transactions, so that the database is also kept consistent at the application level.
29
37
30
38
## The busy timeout
31
39
32
-
SQLite uses locks to allow only one write transaction at a time. When a second connection is trying to write to the database, while another connection has locked it already, SQLite by default returns an error and aborts the second write operation.
40
+
SQLite uses locks to allow only one write transaction at a time.
41
+
When a second connection is trying to write to the database, while another connection has locked it already, SQLite by default returns an error and aborts the second write operation.
33
42
34
-
This default behavior is most often not acceptable, and you can do better. SQLite lets you set a [*busy timeout*](https://www.sqlite.org/pragma.html#pragma_busy_timeout)*.* If this timeout is set to a non-zero value, then the second connection will re-try the write operation several times, until it succeeds or the timeout expires.
43
+
This default behavior is most often not acceptable, and you can do better.
44
+
SQLite lets you set a [*busy timeout*](https://www.sqlite.org/pragma.html#pragma_busy_timeout)*.* If this timeout is set to a non-zero value, then the second connection will re-try the write operation several times, until it succeeds or the timeout expires.
35
45
36
46
To set the busy timeout from RSQLite, you can set a `PRAGMA` :
37
47
38
48
```r
39
49
dbExecute(con, "PRAGMA busy_timeout = 10 * 1000")
40
50
```
41
51
42
-
This is in milliseconds, and it is best to set it right after opening the connection. (You can also use the new `sqliteSetBusyHandler()` function to set the busy timeout.)
52
+
This is in milliseconds, and it is best to set it right after opening the connection.
53
+
(You can also use the new `sqliteSetBusyHandler()` function to set the busy timeout.)
43
54
44
-
Note that SQLite currently does *not* schedule concurrent transactions fairly. More precisely it does not schedule them at all. If multiple transactions are waiting on the same database, any one of them can be granted access next. Moreover, SQLite does not currently ensure that access is granted as soon as the database is available. Multiple connections might be waiting on the database, even if it is available. Make sure that you set the busy timeout to a high enough value for applications with high concurrency and many writes. It is fine to set it to several minutes, especially if you have made sure that your application does not have a deadlock (see later).
55
+
Note that SQLite currently does *not* schedule concurrent transactions fairly.
56
+
More precisely it does not schedule them at all.
57
+
If multiple transactions are waiting on the same database, any one of them can be granted access next.
58
+
Moreover, SQLite does not currently ensure that access is granted as soon as the database is available.
59
+
Multiple connections might be waiting on the database, even if it is available.
60
+
Make sure that you set the busy timeout to a high enough value for applications with high concurrency and many writes.
61
+
It is fine to set it to several minutes, especially if you have made sure that your application does not have a deadlock (see later).
45
62
46
63
## The `usleep()` issue
47
64
48
-
Unfortunately RSQLite version before 2.2.4 had an issue that prevented good concurrent (write) database performance on Unix. When a connection waits on a lock, it uses the `usleep()` C library function on Unix, but only if SQLite was compiled with the `HAVE_USLEEP` compile-time option. Previous RSQLite versions did not set this option, so SQLite fell back to using the `sleep()` C library function instead. `sleep()` , however can only take an integer number of seconds. Sleeping at least one second between retries is obviously very bad for performance, and it also reduces the number of retries before a certain busy timeout expires, resulting in much more errors. (Or you had to set the timeout to a very large value.)
65
+
Unfortunately RSQLite version before 2.2.4 had an issue that prevented good concurrent (write) database performance on Unix.
66
+
When a connection waits on a lock, it uses the `usleep()` C library function on Unix, but only if SQLite was compiled with the `HAVE_USLEEP` compile-time option.
67
+
Previous RSQLite versions did not set this option, so SQLite fell back to using the `sleep()` C library function instead.
68
+
`sleep()` , however can only take an integer number of seconds.
69
+
Sleeping at least one second between retries is obviously very bad for performance, and it also reduces the number of retries before a certain busy timeout expires, resulting in much more errors.
70
+
(Or you had to set the timeout to a very large value.)
49
71
50
-
Several people experienced this over the years, and we also ran into it in the [liteq package](https://github.com/r-lib/liteq/issues/28). Luckily, this time [Iñaki Ucar](https://github.com/Enchufa2) was persistent enough to track down the issue. The [solution](https://github.com/r-dbi/RSQLite/pull/345) is simple enough: turn on the `HAVE_USLEEP` option. (`usleep()` was not always available in the past, but nowadays it is, so we don't actually have to check for it.)
72
+
Several people experienced this over the years, and we also ran into it in the [liteq package](https://github.com/r-lib/liteq/issues/28).
73
+
Luckily, this time [Iñaki Ucar](https://github.com/Enchufa2) was persistent enough to track down the issue.
74
+
The [solution](https://github.com/r-dbi/RSQLite/pull/345) is simple enough: turn on the `HAVE_USLEEP` option.
75
+
(`usleep()` was not always available in the past, but nowadays it is, so we don't actually have to check for it.)
51
76
52
77
If you have concurrency issues with RSQLite, please update to version 2.2.4 or later.
53
78
54
79
## Avoiding deadlocks
55
80
56
-
Even after updating RSQLite and setting the busy timeout, you can still get `database is locked` errors. This is because in some situations, these errors are the only way to avoid a deadlock. When SQLite detects an unavoidable deadlock, it will not use the busy timeout, but cancels some transactions.
57
-
58
-
By default SQLite transactions are `DEFERRED`, which means that they don't actually start with the `BEGIN` statement, but only with the first operation. If a transaction starts out with a read operation, SQLite assumes that it is a read transaction. If it performs a write operation later, then SQLite tries to upgrade it to a write transaction. Consider two concurrent `DEFERRED` transactions that both start out as read transactions, and then they both upgrade to write transactions. One of them (say the first one) will be upgraded, but the second one will be denied with a busy error, as there can be only one write transactions at a time. We cannot keep the second transaction and retry it later, because the second connection already holds a read lock, and this would not lot the first transaction commit its write operations. Neither transactions can continue, unless the other is canceled, so SQLite will cancel the second and let the first one commit. When the second one is canceled, its busy timeout is simply ignored, as it does not make sense to retry it. (The first transaction can be re-tried, however, using the busy timeout.)
59
-
60
-
One way to avoid deadlocks is to announce write transactions right when they start, with `BEGIN IMMEDIATE`. If all write-transactions are immediate transactions, then no deadlock can occur. (Well, at least not at this level.) Immediate transactions slightly reduce the the concurrency in your application, but often this is a good trade off to avoid deadlocks.
61
-
62
-
As far as I can tell there is no way to use immediate transactions in RSQLite with `dbWithTransaction()`, but you can create a helper function for it. It could look something like this:
81
+
Even after updating RSQLite and setting the busy timeout, you can still get `database is locked` errors.
82
+
This is because in some situations, these errors are the only way to avoid a deadlock.
83
+
When SQLite detects an unavoidable deadlock, it will not use the busy timeout, but cancels some transactions.
84
+
85
+
By default SQLite transactions are `DEFERRED`, which means that they don't actually start with the `BEGIN` statement, but only with the first operation.
86
+
If a transaction starts out with a read operation, SQLite assumes that it is a read transaction.
87
+
If it performs a write operation later, then SQLite tries to upgrade it to a write transaction.
88
+
Consider two concurrent `DEFERRED` transactions that both start out as read transactions, and then they both upgrade to write transactions.
89
+
One of them (say the first one) will be upgraded, but the second one will be denied with a busy error, as there can be only one write transactions at a time.
90
+
We cannot keep the second transaction and retry it later, because the second connection already holds a read lock, and this would not lot the first transaction commit its write operations.
91
+
Neither transactions can continue, unless the other is canceled, so SQLite will cancel the second and let the first one commit.
92
+
When the second one is canceled, its busy timeout is simply ignored, as it does not make sense to retry it.
93
+
(The first transaction can be re-tried, however, using the busy timeout.)
94
+
95
+
One way to avoid deadlocks is to announce write transactions right when they start, with `BEGIN IMMEDIATE`. If all write-transactions are immediate transactions, then no deadlock can occur.
96
+
(Well, at least not at this level.) Immediate transactions slightly reduce the the concurrency in your application, but often this is a good trade off to avoid deadlocks.
97
+
98
+
As far as I can tell there is no way to use immediate transactions in RSQLite with `dbWithTransaction()`, but you can create a helper function for it.
Copy file name to clipboardExpand all lines: content/post/2021-03-10-rsqlite-parallel/index.md
+5-5
Original file line number
Diff line number
Diff line change
@@ -11,18 +11,18 @@ tags:
11
11
- concurrency
12
12
- parallel
13
13
output: hugodown::hugo_document
14
-
rmd_hash: 8a05c15c1b4c841c
14
+
rmd_hash: bc1a2cff4e774145
15
15
16
16
---
17
17
18
-
[SQLite](https://www.sqlite.org/index.html) is a great, full featured SQL database engine. Most likely it is used more than [all other database engines combined](https://www.sqlite.org/mostdeployed.html). The [RSQLite](https://github.com/r-dbi/RSQLite) R package embeds SQLite, and lets you query and manipulate SQLite databases from R. It is used in Bioconductor data packages, many deployed Shiny apps, and several other packages and projects. In this post I show how to make it safer to use RSQLite concurrently, from multiple processes.
18
+
[SQLite](https://www.sqlite.org/index.html) is a great, full featured SQL database engine. Most likely it is used more than [all other database engines combined](https://www.sqlite.org/mostdeployed.html). The `{RSQLite}` R package embeds SQLite, and lets you query and manipulate SQLite databases from R. It is used in Bioconductor data packages, many deployed Shiny apps, and several other packages and projects. In this post I show how to make it safer to use RSQLite concurrently, from multiple processes.
19
19
20
20
Note that this is an oversimplified description of how SQLite works and I will not talk about different types of locks, WAL mode, etc. Please see the SQLite documentation for the details.
21
21
22
22
## TL;DR
23
23
24
24
- Always set the SQLite busy timeout.
25
-
- If you use Unix, update RSQLite to the version that will be released soon.
25
+
- If you use Unix, update RSQLite to at least version 2.2.4.
26
26
- Use `IMMEDIATE` write transactions. (You can make use of the `dbWithWriteTransaction()` function at the end of this post.)
27
27
28
28
## Concurrency in SQLite
@@ -47,11 +47,11 @@ Note that SQLite currently does *not* schedule concurrent transactions fairly. M
47
47
48
48
## The `usleep()` issue
49
49
50
-
Unfortunately previous versions of RSQLite had an issue that prevented good concurrent (write) database performance on Unix. When a connection waits on a lock, it uses the `usleep()` C library function on Unix, but only if SQLite was compiled with the `HAVE_USLEEP` compile-time option. Previous RSQLite versions did not set this option, so SQLite fell back to using the [`sleep()`](https://rdrr.io/r/datasets/sleep.html) C library function instead. [`sleep()`](https://rdrr.io/r/datasets/sleep.html) , however can only take an integer number of seconds. Sleeping at least one second between retries is obviously very bad for performance, and it also reduces the number of retries before a certain busy timeout expires, resulting in much more errors. (Or you had to set the timeout to a very large value.)
50
+
Unfortunately RSQLite version before 2.2.4 had an issue that prevented good concurrent (write) database performance on Unix. When a connection waits on a lock, it uses the `usleep()` C library function on Unix, but only if SQLite was compiled with the `HAVE_USLEEP` compile-time option. Previous RSQLite versions did not set this option, so SQLite fell back to using the [`sleep()`](https://rdrr.io/r/datasets/sleep.html) C library function instead. [`sleep()`](https://rdrr.io/r/datasets/sleep.html) , however can only take an integer number of seconds. Sleeping at least one second between retries is obviously very bad for performance, and it also reduces the number of retries before a certain busy timeout expires, resulting in much more errors. (Or you had to set the timeout to a very large value.)
51
51
52
52
Several people experienced this over the years, and we also ran into it in the [liteq package](https://github.com/r-lib/liteq/issues/28). Luckily, this time [Iñaki Ucar](https://github.com/Enchufa2) was persistent enough to track down the issue. The [solution](https://github.com/r-dbi/RSQLite/pull/345) is simple enough: turn on the `HAVE_USLEEP` option. (`usleep()` was not always available in the past, but nowadays it is, so we don't actually have to check for it.)
53
53
54
-
If you have concurrency issues with RSQLite, please update to the version that will be released, very soon.
54
+
If you have concurrency issues with RSQLite, please update to version 2.2.4 or later.
0 commit comments