mirror of
https://github.com/chatmail/core.git
synced 2026-05-06 16:36:59 +03:00
sql: organize connection pool as a stack rather than a queue
When connection pool is organized as a stack, it always returns most recently used connection. Because each connection has its own page cache, using the connection with fresh cache improves performance. I commented out `oauth2::tests::test_oauth_from_mx` because it requires network connection, turned off the Wi-Fi and ran the tests. Before the change, with a queue: ``` $ hyperfine "cargo test" Benchmark 1: cargo test Time (mean ± σ): 56.424 s ± 4.515 s [User: 183.181 s, System: 128.156 s] Range (min … max): 52.123 s … 68.193 s 10 runs ``` With a stack: ``` $ hyperfine "cargo test" Benchmark 1: cargo test Time (mean ± σ): 29.887 s ± 1.377 s [User: 101.226 s, System: 45.573 s] Range (min … max): 26.591 s … 31.010 s 10 runs ``` On version 1.107.1: ``` $ hyperfine "cargo test" Benchmark 1: cargo test Time (mean ± σ): 43.658 s ± 1.079 s [User: 202.582 s, System: 50.723 s] Range (min … max): 41.531 s … 45.170 s 10 runs ```
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- use transaction in `Contact::add_or_lookup()` #4059
|
- use transaction in `Contact::add_or_lookup()` #4059
|
||||||
|
- Organize the connection pool as a stack rather than a queue to ensure that
|
||||||
|
connection page cache is reused more often. #4065
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -848,7 +848,6 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
"criterion",
|
"criterion",
|
||||||
"crossbeam-queue",
|
|
||||||
"deltachat_derive",
|
"deltachat_derive",
|
||||||
"email",
|
"email",
|
||||||
"encoded-words",
|
"encoded-words",
|
||||||
@@ -870,6 +869,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pgp",
|
"pgp",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ backtrace = "0.3"
|
|||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
bitflags = "1.3"
|
bitflags = "1.3"
|
||||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||||
crossbeam-queue = "0.3"
|
|
||||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||||
escaper = "0.1"
|
escaper = "0.1"
|
||||||
@@ -58,6 +57,7 @@ num-derive = "0.3"
|
|||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
percent-encoding = "2.2"
|
percent-encoding = "2.2"
|
||||||
|
parking_lot = "0.12"
|
||||||
pgp = { version = "0.9", default-features = false }
|
pgp = { version = "0.9", default-features = false }
|
||||||
pretty_env_logger = { version = "0.4", optional = true }
|
pretty_env_logger = { version = "0.4", optional = true }
|
||||||
qrcodegen = "1.7.0"
|
qrcodegen = "1.7.0"
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
//! Connection pool.
|
//! # SQLite connection pool.
|
||||||
|
//!
|
||||||
|
//! The connection pool holds a number of SQLite connections and allows to allocate them.
|
||||||
|
//! When allocated connection is dropped, underlying connection is returned back to the pool.
|
||||||
|
//!
|
||||||
|
//! The pool is organized as a stack. It always allocates the most recently used connection.
|
||||||
|
//! Each SQLite connection has its own page cache, so allocating recently used connections
|
||||||
|
//! improves the performance compared to, for example, organizing the pool as a queue
|
||||||
|
//! and returning the least recently used connection each time.
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossbeam_queue::ArrayQueue;
|
use parking_lot::Mutex;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
||||||
|
|
||||||
@@ -12,7 +20,7 @@ use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InnerPool {
|
struct InnerPool {
|
||||||
/// Available connections.
|
/// Available connections.
|
||||||
connections: ArrayQueue<Connection>,
|
connections: Mutex<Vec<Connection>>,
|
||||||
|
|
||||||
/// Counts the number of available connections.
|
/// Counts the number of available connections.
|
||||||
semaphore: Arc<Semaphore>,
|
semaphore: Arc<Semaphore>,
|
||||||
@@ -23,7 +31,9 @@ impl InnerPool {
|
|||||||
///
|
///
|
||||||
/// The connection could be new or returned back.
|
/// The connection could be new or returned back.
|
||||||
fn put(&self, connection: Connection) {
|
fn put(&self, connection: Connection) {
|
||||||
self.connections.force_push(connection);
|
let mut connections = self.connections.lock();
|
||||||
|
connections.push(connection);
|
||||||
|
drop(connections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,22 +84,19 @@ pub struct Pool {
|
|||||||
impl Pool {
|
impl Pool {
|
||||||
/// Creates a new connection pool.
|
/// Creates a new connection pool.
|
||||||
pub fn new(connections: Vec<Connection>) -> Self {
|
pub fn new(connections: Vec<Connection>) -> Self {
|
||||||
|
let semaphore = Arc::new(Semaphore::new(connections.len()));
|
||||||
let inner = Arc::new(InnerPool {
|
let inner = Arc::new(InnerPool {
|
||||||
connections: ArrayQueue::new(connections.len()),
|
connections: Mutex::new(connections),
|
||||||
semaphore: Arc::new(Semaphore::new(connections.len())),
|
semaphore,
|
||||||
});
|
});
|
||||||
for connection in connections {
|
|
||||||
inner.connections.force_push(connection);
|
|
||||||
}
|
|
||||||
Pool { inner }
|
Pool { inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a connection from the pool.
|
/// Retrieves a connection from the pool.
|
||||||
pub async fn get(&self) -> Result<PooledConnection> {
|
pub async fn get(&self) -> Result<PooledConnection> {
|
||||||
let permit = self.inner.semaphore.clone().acquire_owned().await?;
|
let permit = self.inner.semaphore.clone().acquire_owned().await?;
|
||||||
let conn = self
|
let mut connections = self.inner.connections.lock();
|
||||||
.inner
|
let conn = connections
|
||||||
.connections
|
|
||||||
.pop()
|
.pop()
|
||||||
.context("got a permit when there are no connections in the pool")?;
|
.context("got a permit when there are no connections in the pool")?;
|
||||||
let conn = PooledConnection {
|
let conn = PooledConnection {
|
||||||
|
|||||||
Reference in New Issue
Block a user