From 5135f3b007e366a23c9ef554c6bd540a4eebea72 Mon Sep 17 00:00:00 2001 From: Petr Kracik Date: Sat, 29 Apr 2023 23:54:09 +0200 Subject: [PATCH 01/11] improvement: use appropriate paths for systemd example --- contrib/nostr-rs-relay.service | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contrib/nostr-rs-relay.service b/contrib/nostr-rs-relay.service index 4c74242..601108b 100644 --- a/contrib/nostr-rs-relay.service +++ b/contrib/nostr-rs-relay.service @@ -1,12 +1,14 @@ [Unit] Description=nostr-rs-relay + [Service] User=REPLACE_WITH_YOUR_USERNAME -WorkingDirectory=/usr/bin/ +WorkingDirectory=/var/lib/nostr-rs-relay Environment=RUST_LOG=warn,nostr_rs_relay=info -ExecStart=nostr-rs-relay --config /etc/nostr-rs-relay/config.toml +ExecStart=/usr/bin/nostr-rs-relay --config /etc/nostr-rs-relay/config.toml TimeoutStopSec=10 Restart=on-failure RestartSec=5 + [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target From beffeb4d869fd0078cc52528938fee965e91a328 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 20 Apr 2023 12:01:28 +0100 Subject: [PATCH 02/11] improvement: add a configurable postgres write conn string This adds a new configurable connection string for postgres writes. --- src/config.rs | 4 +++- src/db.rs | 25 ++++++++++++++++++++++--- src/repo/postgres.rs | 38 ++++++++++++++++++++------------------ 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9ac0398..32ac2d3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,6 +26,7 @@ pub struct Database { pub min_conn: u32, pub max_conn: u32, pub connection: String, + pub connection_write: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -80,7 +81,7 @@ pub struct Limits { pub struct Authorization { pub pubkey_whitelist: Option>, // If present, only allow these pubkeys to publish events pub nip42_auth: bool, // if true enables NIP-42 authentication - pub nip42_dms: bool, // if true send DMs only to their authenticated recipients + pub nip42_dms: bool, // if true send DMs only to their authenticated recipients } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -260,6 +261,7 @@ impl Default for Settings { min_conn: 4, max_conn: 8, connection: "".to_owned(), + connection_write: None, }, grpc: Grpc { event_admission_server: None, diff --git a/src/db.rs b/src/db.rs index a4f648e..27aafaf 100644 --- a/src/db.rs +++ b/src/db.rs @@ -45,8 +45,8 @@ pub const DB_FILE: &str = "nostr.db"; /// Will panic if the pool could not be created. pub async fn build_repo(settings: &Settings, metrics: NostrMetrics) -> Arc { match settings.database.engine.as_str() { - "sqlite" => Arc::new(build_sqlite_pool(settings, metrics).await), - "postgres" => Arc::new(build_postgres_pool(settings, metrics).await), + "sqlite" => Arc::new(build_sqlite_pool(&settings, metrics).await), + "postgres" => Arc::new(build_postgres_pool(&settings, metrics).await), _ => panic!("Unknown database engine"), } } @@ -70,7 +70,26 @@ async fn build_postgres_pool(settings: &Settings, metrics: NostrMetrics) -> Post .connect_with(options) .await .unwrap(); - let repo = PostgresRepo::new(pool, metrics); + + let write_pool: PostgresPool = match &settings.database.connection_write { + Some(cfg_write) => { + let mut options_write: PgConnectOptions = cfg_write.as_str().parse().unwrap(); + options_write.log_statements(LevelFilter::Debug); + options_write.log_slow_statements(LevelFilter::Warn, Duration::from_secs(60)); + + PoolOptions::new() + .max_connections(settings.database.max_conn) + .min_connections(settings.database.min_conn) + .idle_timeout(Duration::from_secs(60)) + .connect_with(options_write) + .await + .unwrap() + } + None => pool.clone(), + }; + + let repo = PostgresRepo::new(pool, write_pool, metrics); + // Panic on migration failure let version = repo.migrate_up().await.unwrap(); info!("Postgres migration completed, at v{}", version); diff --git a/src/repo/postgres.rs b/src/repo/postgres.rs index 6e8fea6..8fc46eb 100644 --- a/src/repo/postgres.rs +++ b/src/repo/postgres.rs @@ -28,13 +28,15 @@ pub type PostgresPool = sqlx::pool::Pool; pub struct PostgresRepo { conn: PostgresPool, + conn_write: PostgresPool, metrics: NostrMetrics, } impl PostgresRepo { - pub fn new(c: PostgresPool, m: NostrMetrics) -> PostgresRepo { + pub fn new(c: PostgresPool, cw: PostgresPool, m: NostrMetrics) -> PostgresRepo { PostgresRepo { conn: c, + conn_write: cw, metrics: m, } } @@ -81,17 +83,17 @@ async fn delete_expired(conn: PostgresPool) -> Result { impl NostrRepo for PostgresRepo { async fn start(&self) -> Result<()> { // begin a cleanup task for expired events. - cleanup_expired(self.conn.clone(), Duration::from_secs(600)).await?; + cleanup_expired(self.conn_write.clone(), Duration::from_secs(600)).await?; Ok(()) } async fn migrate_up(&self) -> Result { - Ok(run_migrations(&self.conn).await?) + Ok(run_migrations(&self.conn_write).await?) } async fn write_event(&self, e: &Event) -> Result { // start transaction - let mut tx = self.conn.begin().await?; + let mut tx = self.conn_write.begin().await?; let start = Instant::now(); // get relevant fields from event and convert to blobs. @@ -455,7 +457,7 @@ ON CONFLICT (id) DO NOTHING"#, } async fn create_verification_record(&self, event_id: &str, name: &str) -> Result<()> { - let mut tx = self.conn.begin().await?; + let mut tx = self.conn_write.begin().await?; sqlx::query("DELETE FROM user_verification WHERE \"name\" = $1") .bind(name) @@ -481,7 +483,7 @@ ON CONFLICT (id) DO NOTHING"#, sqlx::query("UPDATE user_verification SET verified_at = $1, fail_count = 0 WHERE id = $2") .bind(Utc.timestamp_opt(verify_time as i64, 0).unwrap()) .bind(id as i64) - .execute(&self.conn) + .execute(&self.conn_write) .await?; info!("verification updated for {}", id); @@ -491,7 +493,7 @@ ON CONFLICT (id) DO NOTHING"#, async fn fail_verification(&self, id: u64) -> Result<()> { sqlx::query("UPDATE user_verification SET failed_at = now(), fail_count = fail_count + 1 WHERE id = $1") .bind(id as i64) - .execute(&self.conn) + .execute(&self.conn_write) .await?; Ok(()) } @@ -499,7 +501,7 @@ ON CONFLICT (id) DO NOTHING"#, async fn delete_verification(&self, id: u64) -> Result<()> { sqlx::query("DELETE FROM user_verification WHERE id = $1") .bind(id as i64) - .execute(&self.conn) + .execute(&self.conn_write) .await?; Ok(()) } @@ -551,7 +553,7 @@ ON CONFLICT (id) DO NOTHING"#, async fn create_account(&self, pub_key: &Keys) -> Result { let pub_key = pub_key.public_key().to_string(); - let mut tx = self.conn.begin().await?; + let mut tx = self.conn_write.begin().await?; let result = sqlx::query("INSERT INTO account (pubkey, balance) VALUES ($1, 0);") .bind(pub_key) @@ -577,7 +579,7 @@ ON CONFLICT (id) DO NOTHING"#, ) .bind(admission_cost as i64) .bind(pub_key) - .execute(&self.conn) + .execute(&self.conn_write) .await?; Ok(()) } @@ -594,7 +596,7 @@ ON CONFLICT (id) DO NOTHING"#, let result = sqlx::query_as::<_, (bool, i64)>(query) .bind(pub_key) - .fetch_optional(&self.conn) + .fetch_optional(&self.conn_write) .await? .ok_or(error::Error::SqlxError(RowNotFound))?; @@ -614,14 +616,14 @@ ON CONFLICT (id) DO NOTHING"#, sqlx::query("UPDATE account SET balance = balance + $1 WHERE pubkey = $2") .bind(new_balance as i64) .bind(pub_key) - .execute(&self.conn) + .execute(&self.conn_write) .await? } false => { sqlx::query("UPDATE account SET balance = balance - $1 WHERE pubkey = $2") .bind(new_balance as i64) .bind(pub_key) - .execute(&self.conn) + .execute(&self.conn_write) .await? } }; @@ -631,7 +633,7 @@ ON CONFLICT (id) DO NOTHING"#, /// Create invoice record async fn create_invoice_record(&self, pub_key: &Keys, invoice_info: InvoiceInfo) -> Result<()> { let pub_key = pub_key.public_key().to_string(); - let mut tx = self.conn.begin().await?; + let mut tx = self.conn_write.begin().await?; sqlx::query( "INSERT INTO invoice (pubkey, payment_hash, amount, status, description, created_at, invoice) VALUES ($1, $2, $3, $4, $5, now(), $6)", @@ -658,7 +660,7 @@ ON CONFLICT (id) DO NOTHING"#, let (pubkey, prev_invoice_status, amount) = sqlx::query_as::<_, (String, InvoiceStatus, i64)>(query) .bind(payment_hash) - .fetch_optional(&self.conn) + .fetch_optional(&self.conn_write) .await? .ok_or(error::Error::SqlxError(RowNotFound))?; @@ -672,14 +674,14 @@ ON CONFLICT (id) DO NOTHING"#, sqlx::query(query) .bind(&status) .bind(payment_hash) - .execute(&self.conn) + .execute(&self.conn_write) .await?; if prev_invoice_status.eq(&InvoiceStatus::Unpaid) && status.eq(&InvoiceStatus::Paid) { sqlx::query("UPDATE account SET balance = balance + $1 WHERE pubkey = $2") .bind(amount) .bind(&pubkey) - .execute(&self.conn) + .execute(&self.conn_write) .await?; } @@ -698,7 +700,7 @@ LIMIT 1; "#; match sqlx::query_as::<_, (i64, String, String, String)>(query) .bind(pubkey.public_key().to_string()) - .fetch_optional(&self.conn) + .fetch_optional(&self.conn_write) .await .unwrap() { From 993fec4eed84f7c05b3c2d19ab2eef3f2214f67f Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sun, 30 Apr 2023 10:10:06 -0500 Subject: [PATCH 03/11] improvement: document pg connection_write config --- config.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.toml b/config.toml index 085790f..a3edb91 100644 --- a/config.toml +++ b/config.toml @@ -52,6 +52,10 @@ description = "A newly created nostr-rs-relay.\n\nCustomize this with your own i # sqlite. #connection = "postgresql://postgres:nostr@localhost:7500/nostr" +# Optional database connection string for writing. Use this for +# postgres clusters where you want to separate reads and writes to +# different nodes. Ignore for single-database instances. +#connection_write = "postgresql://postgres:nostr@localhost:7500/nostr" [grpc] # gRPC interfaces for externalized decisions and other extensions to From 1c1b1a18025c6f6b7863b2f9ddf311791dafaa98 Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sun, 30 Apr 2023 11:13:03 -0500 Subject: [PATCH 04/11] build: upgrade checkout action for github ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6861681..8c80345 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: test_nostr-rs-relay: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Update local toolchain run: | From 04db2203bb646e4f42bddb3d0c63fdc76eccf1ca Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Wed, 3 May 2023 07:22:44 -0500 Subject: [PATCH 05/11] perf: use standard allocator, limit sqlite mmap to 4GB This is an experimental change to see if we can reduce memory usage with large SQLite databases. If successful, we'll do this again and further reduce the database mmap size. This will cause greater use of the page cache, but that is more easily reclaimed by the kernel, and should reduce memory pressure, as well as making it clearer how much memory the application is actually using for connections, subscriptions, etc. --- Cargo.lock | 21 --------------------- Cargo.toml | 2 -- src/main.rs | 7 ------- src/repo/sqlite_migration.rs | 2 +- 4 files changed, 1 insertion(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9533514..b515fab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,7 +1689,6 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "tikv-jemallocator", "tokio", "tokio-tungstenite", "tonic", @@ -2959,26 +2958,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tikv-jemalloc-sys" -version = "0.5.3+5.3.0-patched" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "tikv-jemallocator" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20612db8a13a6c06d57ec83953694185a367e16945f66565e8028d2c0bd76979" -dependencies = [ - "libc", - "tikv-jemalloc-sys", -] - [[package]] name = "time" version = "0.1.45" diff --git a/Cargo.toml b/Cargo.toml index b04ef07..25f4f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,6 @@ bech32 = "0.9.1" url = "2.3.1" qrcode = { version = "0.12.0", default-features = false, features = ["svg"] } nostr = { version = "0.18.0", default-features = false, features = ["base", "nip04", "nip19"] } -[target.'cfg(not(target_env = "msvc"))'.dependencies] -tikv-jemallocator = "0.5" [dev-dependencies] anyhow = "1" diff --git a/src/main.rs b/src/main.rs index 97aa032..712c902 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,13 +9,6 @@ use std::sync::mpsc::{Receiver as MpscReceiver, Sender as MpscSender}; use std::thread; use tracing::info; -#[cfg(not(target_env = "msvc"))] -use tikv_jemallocator::Jemalloc; - -#[cfg(not(target_env = "msvc"))] -#[global_allocator] -static GLOBAL: Jemalloc = Jemalloc; - /// Start running a Nostr relay server. fn main() { let args = CLIArgs::parse(); diff --git a/src/repo/sqlite_migration.rs b/src/repo/sqlite_migration.rs index 709a572..60544b2 100644 --- a/src/repo/sqlite_migration.rs +++ b/src/repo/sqlite_migration.rs @@ -19,7 +19,7 @@ PRAGMA foreign_keys = ON; PRAGMA journal_size_limit = 32768; PRAGMA temp_store = 2; -- use memory, not temp files PRAGMA main.cache_size = 20000; -- 80MB max cache size per conn -pragma mmap_size = 17179869184; -- cap mmap at 16GB +pragma mmap_size = 4294967296; -- cap mmap at 4GB "##; /// Latest database version From a2d1d78e233adb21c6db16c4487f66f2a5ede2fb Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sat, 6 May 2023 14:42:59 -0500 Subject: [PATCH 06/11] docs: reformatting --- docs/reverse-proxy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reverse-proxy.md b/docs/reverse-proxy.md index 157ba5c..c5e0c14 100644 --- a/docs/reverse-proxy.md +++ b/docs/reverse-proxy.md @@ -120,7 +120,7 @@ Assumptions: * `Traefik` is running in `Docker`, using `docker compose` and labels for the static configuration. An equivalent setup useing a Traefik config file is possible too (but not covered here). * Strict Transport Security is enabled. * Hostname for the relay is `relay.example.com`, email adres for ACME certificates provider is `name@example.com`. -* ipv6 is enabled, a viable private ipv6 subnet is specified in the example below. +* ipv6 is enabled, a viable private ipv6 subnet is specified in the example below. * Relay is running on port `8080`. ``` @@ -196,4 +196,4 @@ services: ### Traefik Notes -Traefik will take care of the provisioning and renewal of certificates. In case of an ipv4-only relay, simply detele the `enable_ipv6:` and `ipam:` entries in the `networks:` section of the docker-compose file. \ No newline at end of file +Traefik will take care of the provisioning and renewal of certificates. In case of an ipv4-only relay, simply detele the `enable_ipv6:` and `ipam:` entries in the `networks:` section of the docker-compose file. From 1595ec783d1312206c0a1d7c66b5bfa1ff83f3a4 Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sat, 6 May 2023 14:43:30 -0500 Subject: [PATCH 07/11] docs: allow host header prefix matching, required for Damus compatibility --- docs/reverse-proxy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reverse-proxy.md b/docs/reverse-proxy.md index c5e0c14..e6c6d0d 100644 --- a/docs/reverse-proxy.md +++ b/docs/reverse-proxy.md @@ -29,7 +29,7 @@ frontend fe_prod bind :80 http-request set-header X-Forwarded-Proto https if { ssl_fc } redirect scheme https code 301 if !{ ssl_fc } - acl host_relay hdr(host) -i relay.example.com + acl host_relay hdr(host) -i -m beg relay.example.com use_backend relay if host_relay # HSTS (1 year) http-response set-header Strict-Transport-Security max-age=31536000 From 2bcddf8bbfe11a2db6419303fb8935e41687558d Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sat, 6 May 2023 15:40:56 -0500 Subject: [PATCH 08/11] perf: disable sqlite mmap to reduce memory pressure --- src/repo/sqlite_migration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repo/sqlite_migration.rs b/src/repo/sqlite_migration.rs index 60544b2..f2ccab0 100644 --- a/src/repo/sqlite_migration.rs +++ b/src/repo/sqlite_migration.rs @@ -19,7 +19,7 @@ PRAGMA foreign_keys = ON; PRAGMA journal_size_limit = 32768; PRAGMA temp_store = 2; -- use memory, not temp files PRAGMA main.cache_size = 20000; -- 80MB max cache size per conn -pragma mmap_size = 4294967296; -- cap mmap at 4GB +pragma mmap_size = 0; -- disable mmap (default) "##; /// Latest database version From 0582a891cce223782fc1a1a2c1052599217d2024 Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sun, 7 May 2023 19:32:50 -0500 Subject: [PATCH 09/11] perf: switch to jemalloc allocator --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 2 ++ src/main.rs | 6 ++++++ 3 files changed, 29 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b515fab..9533514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,6 +1689,7 @@ dependencies = [ "serde_json", "sqlx", "thiserror", + "tikv-jemallocator", "tokio", "tokio-tungstenite", "tonic", @@ -2958,6 +2959,26 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.3+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20612db8a13a6c06d57ec83953694185a367e16945f66565e8028d2c0bd76979" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.1.45" diff --git a/Cargo.toml b/Cargo.toml index 25f4f03..b04ef07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,8 @@ bech32 = "0.9.1" url = "2.3.1" qrcode = { version = "0.12.0", default-features = false, features = ["svg"] } nostr = { version = "0.18.0", default-features = false, features = ["base", "nip04", "nip19"] } +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = "0.5" [dev-dependencies] anyhow = "1" diff --git a/src/main.rs b/src/main.rs index 712c902..6a9116d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,12 @@ use std::sync::mpsc as syncmpsc; use std::sync::mpsc::{Receiver as MpscReceiver, Sender as MpscSender}; use std::thread; use tracing::info; +#[cfg(not(target_env = "msvc"))] +use tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; /// Start running a Nostr relay server. fn main() { From 4d746fad853d3cf1037e5e34e519ff52a965cfbd Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sun, 7 May 2023 19:33:10 -0500 Subject: [PATCH 10/11] docs: helpful ubuntu packages for building --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ec3cff4..09a66f7 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,11 @@ https://hub.docker.com/r/scsibug/nostr-rs-relay Building `nostr-rs-relay` requires an installation of Cargo & Rust: https://www.rust-lang.org/tools/install +The following OS packages will be helpful; on Debian/Ubuntu: +```console +$ sudo apt-get install build-essentials cmake protobuf-compiler pkg-config libssl-dev +``` + Clone this repository, and then build a release version of the relay: ```console From eba7a32615b6862c20e96bed9442022ca7ee0894 Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sun, 7 May 2023 19:38:18 -0500 Subject: [PATCH 11/11] perf: reduce SQLite connection count and idle lifetime On lightly loaded relays, we free up memory faster by letting idle connections be reclaimed in 10 seconds instead of the default 10 minutes. This also sets the minimum to zero connections, instead of always trying to hold one open. --- config.toml | 2 +- src/repo/sqlite.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config.toml b/config.toml index a3edb91..3dea8ee 100644 --- a/config.toml +++ b/config.toml @@ -42,7 +42,7 @@ description = "A newly created nostr-rs-relay.\n\nCustomize this with your own i # Database connection pool settings for subscribers: # Minimum number of SQLite reader connections -#min_conn = 4 +#min_conn = 0 # Maximum number of SQLite reader connections. Recommend setting this # to approx the number of cores. diff --git a/src/repo/sqlite.rs b/src/repo/sqlite.rs index 91b48e6..b156f0c 100644 --- a/src/repo/sqlite.rs +++ b/src/repo/sqlite.rs @@ -62,7 +62,7 @@ impl SqliteRepo { "writer", settings, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE, - 1, + 0, 2, false, ); @@ -70,7 +70,7 @@ impl SqliteRepo { "maintenance", settings, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE, - 1, + 0, 2, true, ); @@ -1199,6 +1199,7 @@ pub fn build_pool( .test_on_check_out(true) // no noticeable performance hit .min_idle(Some(min_size)) .max_size(max_size) + .idle_timeout(Some(Duration::from_secs(10))) .max_lifetime(Some(Duration::from_secs(30))) .build(manager) .unwrap();