address database upgrade slowness (#107)

* give a rule of thumb for update time in the documentation
* log the SQLite3 version, which can affect performance
* do the vacuum in non-WAL mode, to correctly set the page size and to
  avoid very slow behavior on older SQLite3 versions. Larger page sizes
  are generally faster (including subsequent vacuum operations).
  This won't help much for the first vacuum after this change, but it
  will help afterward.
* likewise, set the page size properly on "moonfire-nvr init".
This commit is contained in:
Scott Lamb 2021-02-10 21:44:06 -08:00
parent 4e67af9909
commit ff1615a0b4
4 changed files with 27 additions and 8 deletions

View File

@ -84,7 +84,10 @@ $ nvr pull # updates the docker image to the latest binary
$ nvr upgrade # runs the upgrade
```
You can run the system in read-only mode, although you'll find this only
As a rule of thumb, on a Raspberry Pi 4 with a 1 GiB database, an upgrade might
take about four minutes for each schema version and for the final vacuum.
Next, you can run the system in read-only mode, although you'll find this only
works in the "insecure" setup. (Authorization requires writing the database.)
```

View File

@ -88,7 +88,6 @@ fn upgrade(args: &Args, target_ver: i32, conn: &mut rusqlite::Connection) -> Res
bail!("Database is at negative version {}!", old_ver);
}
info!("Upgrading database from version {} to version {}...", old_ver, target_ver);
set_journal_mode(&conn, args.preset_journal)?;
for ver in old_ver .. target_ver {
info!("...from version {} to version {}", ver, ver + 1);
let tx = conn.transaction()?;
@ -106,11 +105,16 @@ fn upgrade(args: &Args, target_ver: i32, conn: &mut rusqlite::Connection) -> Res
pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> {
db::set_integrity_pragmas(conn)?;
set_journal_mode(&conn, args.preset_journal)?;
upgrade(args, db::EXPECTED_VERSION, conn)?;
// WAL is the preferred journal mode for normal operation; it reduces the number of syncs
// without compromising safety.
set_journal_mode(&conn, "wal")?;
// As in "moonfire-nvr init": try for page_size=16384 and wal for the reasons explained there.
//
// Do the vacuum prior to switching back to WAL for two reasons:
// * page_size only takes effect on a vacuum in non-WAL mode.
// https://www.sqlite.org/pragma.html#pragma_page_size
// * vacuum is a huge transaction, and on old versions of SQLite3, that's best done in
// non-WAL mode. https://www.sqlite.org/wal.html
if !args.no_vacuum {
info!("...vacuuming database after upgrade.");
conn.execute_batch(r#"
@ -118,6 +122,8 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> {
vacuum;
"#)?;
}
set_journal_mode(&conn, "wal")?;
info!("...done.");
Ok(())

View File

@ -51,9 +51,15 @@ pub fn run(args: &Args) -> Result<(), Error> {
return Ok(());
}
// Use WAL mode (which is the most efficient way to preserve database integrity) with a large
// page size (so reading large recording_playback rows doesn't require as many seeks). Changing
// the page size requires doing a vacuum in non-WAL mode. This will be cheap on an empty
// database. https://www.sqlite.org/pragma.html#pragma_page_size
conn.execute_batch(r#"
pragma journal_mode = wal;
pragma journal_mode = delete;
pragma page_size = 16384;
vacuum;
pragma journal_mode = wal;
"#)?;
db::init(&mut conn)?;
info!("Database initialized.");

View File

@ -30,6 +30,7 @@
use db::dir;
use failure::{Error, Fail};
use log::info;
use nix::fcntl::FlockArg;
use rusqlite;
use std::path::Path;
@ -43,7 +44,7 @@ pub mod sql;
pub mod ts;
pub mod upgrade;
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum OpenMode {
ReadOnly,
ReadWrite,
@ -71,8 +72,11 @@ fn open_dir(db_dir: &Path, mode: OpenMode) -> Result<dir::Fd, Error> {
/// The returned `dir::Fd` holds the lock and should be kept open as long as the `Connection` is.
fn open_conn(db_dir: &Path, mode: OpenMode) -> Result<(dir::Fd, rusqlite::Connection), Error> {
let dir = open_dir(db_dir, mode)?;
let db_path = db_dir.join("db");
info!("Opening {} in {:?} mode with SQLite version {}",
db_path.display(), mode, rusqlite::version());
let conn = rusqlite::Connection::open_with_flags(
db_dir.join("db"),
db_path,
match mode {
OpenMode::ReadOnly => rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
OpenMode::ReadWrite => rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE,