refactor: format
cargo fmt
This commit is contained in:
committed by
Greg Heartsfield
parent
440217e1ee
commit
6df92f9580
+101
-56
@@ -1,32 +1,33 @@
|
||||
//! Event persistence and querying
|
||||
//use crate::config::SETTINGS;
|
||||
use crate::config::Settings;
|
||||
use crate::error::{Result,Error::SqlError};
|
||||
use crate::db::QueryResult;
|
||||
use crate::error::Result;
|
||||
use crate::error::Error::SqlError;
|
||||
use crate::event::{single_char_tagname, Event};
|
||||
use crate::hexrange::hex_range;
|
||||
use crate::hexrange::HexSearch;
|
||||
use crate::repo::sqlite_migration::{STARTUP_SQL,upgrade_db};
|
||||
use crate::utils::{is_hex,unix_time};
|
||||
use crate::nip05::{Nip05Name, VerificationRecord};
|
||||
use crate::subscription::{ReqFilter, Subscription};
|
||||
use crate::repo::sqlite_migration::{upgrade_db, STARTUP_SQL};
|
||||
use crate::server::NostrMetrics;
|
||||
use crate::subscription::{ReqFilter, Subscription};
|
||||
use crate::utils::{is_hex, unix_time};
|
||||
use async_trait::async_trait;
|
||||
use hex;
|
||||
use r2d2;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::params;
|
||||
use rusqlite::types::ToSql;
|
||||
use rusqlite::OpenFlags;
|
||||
use tokio::sync::{Mutex, MutexGuard, Semaphore};
|
||||
use std::fmt::Write as _;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::{Mutex, MutexGuard, Semaphore};
|
||||
use tokio::task;
|
||||
use tracing::{debug, info, trace, warn};
|
||||
use async_trait::async_trait;
|
||||
use crate::db::QueryResult;
|
||||
|
||||
use crate::repo::{now_jitter, NostrRepo};
|
||||
|
||||
@@ -54,7 +55,8 @@ pub struct SqliteRepo {
|
||||
|
||||
impl SqliteRepo {
|
||||
// build all the pools needed
|
||||
#[must_use] pub fn new(settings: &Settings, metrics: NostrMetrics) -> SqliteRepo {
|
||||
#[must_use]
|
||||
pub fn new(settings: &Settings, metrics: NostrMetrics) -> SqliteRepo {
|
||||
let write_pool = build_pool(
|
||||
"writer",
|
||||
settings,
|
||||
@@ -110,7 +112,8 @@ impl SqliteRepo {
|
||||
// get relevant fields from event and convert to blobs.
|
||||
let id_blob = hex::decode(&e.id).ok();
|
||||
let pubkey_blob: Option<Vec<u8>> = hex::decode(&e.pubkey).ok();
|
||||
let delegator_blob: Option<Vec<u8>> = e.delegated_by.as_ref().and_then(|d| hex::decode(d).ok());
|
||||
let delegator_blob: Option<Vec<u8>> =
|
||||
e.delegated_by.as_ref().and_then(|d| hex::decode(d).ok());
|
||||
let event_str = serde_json::to_string(&e).ok();
|
||||
// check for replaceable events that would hide this one; we won't even attempt to insert these.
|
||||
if e.is_replaceable() {
|
||||
@@ -130,7 +133,7 @@ impl SqliteRepo {
|
||||
// the same author/kind/tag value exist, and we can ignore
|
||||
// this event.
|
||||
if repl_count.ok().is_some() {
|
||||
return Ok(0)
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
// ignore if the event hash is a duplicate.
|
||||
@@ -249,21 +252,24 @@ impl SqliteRepo {
|
||||
|
||||
#[async_trait]
|
||||
impl NostrRepo for SqliteRepo {
|
||||
|
||||
async fn start(&self) -> Result<()> {
|
||||
db_checkpoint_task(self.maint_pool.clone(), Duration::from_secs(60),
|
||||
self.write_in_progress.clone(),
|
||||
self.checkpoint_in_progress.clone()).await?;
|
||||
cleanup_expired(self.maint_pool.clone(), Duration::from_secs(600),
|
||||
self.write_in_progress.clone()).await
|
||||
db_checkpoint_task(
|
||||
self.maint_pool.clone(),
|
||||
Duration::from_secs(60),
|
||||
self.write_in_progress.clone(),
|
||||
self.checkpoint_in_progress.clone()
|
||||
).await?;
|
||||
cleanup_expired(
|
||||
self.maint_pool.clone(),
|
||||
Duration::from_secs(600),
|
||||
self.write_in_progress.clone()
|
||||
).await
|
||||
}
|
||||
|
||||
async fn migrate_up(&self) -> Result<usize> {
|
||||
let _write_guard = self.write_in_progress.lock().await;
|
||||
let mut conn = self.write_pool.get()?;
|
||||
task::spawn_blocking(move || {
|
||||
upgrade_db(&mut conn)
|
||||
}).await?
|
||||
task::spawn_blocking(move || upgrade_db(&mut conn)).await?
|
||||
}
|
||||
/// Persist event to database
|
||||
async fn write_event(&self, e: &Event) -> Result<u64> {
|
||||
@@ -322,9 +328,14 @@ impl NostrRepo for SqliteRepo {
|
||||
// thread pool waiting for queries to finish under high load.
|
||||
// Instead, don't bother spawning threads when they will just
|
||||
// block on a database connection.
|
||||
let sem = self.reader_threads_ready.clone().acquire_owned().await.unwrap();
|
||||
let self=self.clone();
|
||||
let metrics=self.metrics.clone();
|
||||
let sem = self
|
||||
.reader_threads_ready
|
||||
.clone()
|
||||
.acquire_owned()
|
||||
.await
|
||||
.unwrap();
|
||||
let self = self.clone();
|
||||
let metrics = self.metrics.clone();
|
||||
task::spawn_blocking(move || {
|
||||
{
|
||||
// if we are waiting on a checkpoint, stop until it is complete
|
||||
@@ -349,7 +360,10 @@ impl NostrRepo for SqliteRepo {
|
||||
}
|
||||
// check before getting a DB connection if the client still wants the results
|
||||
if abandon_query_rx.try_recv().is_ok() {
|
||||
debug!("query cancelled by client (before execution) (cid: {}, sub: {:?})", client_id, sub.id);
|
||||
debug!(
|
||||
"query cancelled by client (before execution) (cid: {}, sub: {:?})",
|
||||
client_id, sub.id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -362,7 +376,9 @@ impl NostrRepo for SqliteRepo {
|
||||
if let Ok(mut conn) = self.read_pool.get() {
|
||||
{
|
||||
let pool_state = self.read_pool.state();
|
||||
metrics.db_connections.set((pool_state.connections - pool_state.idle_connections).into());
|
||||
metrics
|
||||
.db_connections
|
||||
.set((pool_state.connections - pool_state.idle_connections).into());
|
||||
}
|
||||
for filter in sub.filters.iter() {
|
||||
let filter_start = Instant::now();
|
||||
@@ -379,7 +395,7 @@ impl NostrRepo for SqliteRepo {
|
||||
let mut last_successful_send = Instant::now();
|
||||
// execute the query.
|
||||
// make the actual SQL query (with parameters inserted) available
|
||||
conn.trace(Some(|x| {trace!("SQL trace: {:?}", x)}));
|
||||
conn.trace(Some(|x| trace!("SQL trace: {:?}", x)));
|
||||
let mut stmt = conn.prepare_cached(&q)?;
|
||||
let mut event_rows = stmt.query(rusqlite::params_from_iter(p))?;
|
||||
|
||||
@@ -397,7 +413,10 @@ impl NostrRepo for SqliteRepo {
|
||||
if slow_first_event && client_id.starts_with('0') {
|
||||
debug!(
|
||||
"filter first result in {:?} (slow): {} (cid: {}, sub: {:?})",
|
||||
first_event_elapsed, serde_json::to_string(&filter)?, client_id, sub.id
|
||||
first_event_elapsed,
|
||||
serde_json::to_string(&filter)?,
|
||||
client_id,
|
||||
sub.id
|
||||
);
|
||||
}
|
||||
first_result = false;
|
||||
@@ -407,8 +426,14 @@ impl NostrRepo for SqliteRepo {
|
||||
{
|
||||
if self.checkpoint_in_progress.try_lock().is_err() {
|
||||
// lock was held, abort this query
|
||||
debug!("query aborted due to checkpoint (cid: {}, sub: {:?})", client_id, sub.id);
|
||||
metrics.query_aborts.with_label_values(&["checkpoint"]).inc();
|
||||
debug!(
|
||||
"query aborted due to checkpoint (cid: {}, sub: {:?})",
|
||||
client_id, sub.id
|
||||
);
|
||||
metrics
|
||||
.query_aborts
|
||||
.with_label_values(&["checkpoint"])
|
||||
.inc();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -416,7 +441,10 @@ impl NostrRepo for SqliteRepo {
|
||||
|
||||
// check if this is still active; every 100 rows
|
||||
if row_count % 100 == 0 && abandon_query_rx.try_recv().is_ok() {
|
||||
debug!("query cancelled by client (cid: {}, sub: {:?})", client_id, sub.id);
|
||||
debug!(
|
||||
"query cancelled by client (cid: {}, sub: {:?})",
|
||||
client_id, sub.id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
row_count += 1;
|
||||
@@ -432,19 +460,31 @@ impl NostrRepo for SqliteRepo {
|
||||
// the queue has been full for too long, abort
|
||||
info!("aborting database query due to slow client (cid: {}, sub: {:?})",
|
||||
client_id, sub.id);
|
||||
metrics.query_aborts.with_label_values(&["slowclient"]).inc();
|
||||
metrics
|
||||
.query_aborts
|
||||
.with_label_values(&["slowclient"])
|
||||
.inc();
|
||||
let ok: Result<()> = Ok(());
|
||||
return ok;
|
||||
}
|
||||
// check if a checkpoint is trying to run, and abort
|
||||
if self.checkpoint_in_progress.try_lock().is_err() {
|
||||
// lock was held, abort this query
|
||||
debug!("query aborted due to checkpoint (cid: {}, sub: {:?})", client_id, sub.id);
|
||||
metrics.query_aborts.with_label_values(&["checkpoint"]).inc();
|
||||
debug!(
|
||||
"query aborted due to checkpoint (cid: {}, sub: {:?})",
|
||||
client_id, sub.id
|
||||
);
|
||||
metrics
|
||||
.query_aborts
|
||||
.with_label_values(&["checkpoint"])
|
||||
.inc();
|
||||
return Ok(());
|
||||
}
|
||||
// give the queue a chance to clear before trying again
|
||||
debug!("query thread sleeping due to full query_tx (cid: {}, sub: {:?})", client_id, sub.id);
|
||||
debug!(
|
||||
"query thread sleeping due to full query_tx (cid: {}, sub: {:?})",
|
||||
client_id, sub.id
|
||||
);
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
// TODO: we could use try_send, but we'd have to juggle
|
||||
@@ -465,10 +505,12 @@ impl NostrRepo for SqliteRepo {
|
||||
if filter_start.elapsed() > slow_cutoff && client_id.starts_with('0') {
|
||||
debug!(
|
||||
"query filter req (slow): {} (cid: {}, sub: {:?}, filter: {})",
|
||||
serde_json::to_string(&filter)?, client_id, sub.id, filter_count
|
||||
serde_json::to_string(&filter)?,
|
||||
client_id,
|
||||
sub.id,
|
||||
filter_count
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
warn!("Could not get a database connection for querying");
|
||||
@@ -504,7 +546,8 @@ impl NostrRepo for SqliteRepo {
|
||||
let start = Instant::now();
|
||||
conn.execute_batch("PRAGMA optimize;").ok();
|
||||
info!("optimize ran in {:?}", start.elapsed());
|
||||
}).await?;
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -557,8 +600,7 @@ impl NostrRepo for SqliteRepo {
|
||||
let ok: Result<()> = Ok(());
|
||||
ok
|
||||
})
|
||||
.await?
|
||||
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Update verification record as failed
|
||||
@@ -596,7 +638,7 @@ impl NostrRepo for SqliteRepo {
|
||||
let ok: Result<()> = Ok(());
|
||||
ok
|
||||
})
|
||||
.await?
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Get the latest verification record for a given pubkey.
|
||||
@@ -684,14 +726,15 @@ fn override_index(f: &ReqFilter) -> Option<String> {
|
||||
// queries for multiple kinds default to kind_index, which is
|
||||
// significantly slower than kind_created_at_index.
|
||||
if let Some(ks) = &f.kinds {
|
||||
if f.ids.is_none() &&
|
||||
ks.len() > 1 &&
|
||||
f.since.is_none() &&
|
||||
f.until.is_none() &&
|
||||
f.tags.is_none() &&
|
||||
f.authors.is_none() {
|
||||
return Some("kind_created_at_index".into());
|
||||
}
|
||||
if f.ids.is_none()
|
||||
&& ks.len() > 1
|
||||
&& f.since.is_none()
|
||||
&& f.until.is_none()
|
||||
&& f.tags.is_none()
|
||||
&& f.authors.is_none()
|
||||
{
|
||||
return Some("kind_created_at_index".into());
|
||||
}
|
||||
}
|
||||
// if there is an author, it is much better to force the authors index.
|
||||
if f.authors.is_some() {
|
||||
@@ -726,7 +769,9 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
|
||||
|
||||
// check if the index needs to be overriden
|
||||
let idx_name = override_index(f);
|
||||
let idx_stmt = idx_name.as_ref().map_or_else(|| "".to_owned(), |i| format!("INDEXED BY {i}"));
|
||||
let idx_stmt = idx_name
|
||||
.as_ref()
|
||||
.map_or_else(|| "".to_owned(), |i| format!("INDEXED BY {i}"));
|
||||
let mut query = format!("SELECT e.content FROM event e {idx_stmt}");
|
||||
// query parameters for SQLite
|
||||
let mut params: Vec<Box<dyn ToSql>> = vec![];
|
||||
@@ -744,9 +789,7 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
|
||||
params.push(Box::new(ex));
|
||||
}
|
||||
Some(HexSearch::Range(lower, upper)) => {
|
||||
auth_searches.push(
|
||||
"(author>? AND author<?)".to_owned(),
|
||||
);
|
||||
auth_searches.push("(author>? AND author<?)".to_owned());
|
||||
params.push(Box::new(lower));
|
||||
params.push(Box::new(upper));
|
||||
}
|
||||
@@ -903,7 +946,7 @@ fn _query_from_sub(sub: &Subscription) -> (String, Vec<Box<dyn ToSql>>, Vec<Stri
|
||||
.map(|s| format!("SELECT distinct content, created_at FROM ({s})"))
|
||||
.collect();
|
||||
let query: String = subqueries_selects.join(" UNION ");
|
||||
(query, params,indexes)
|
||||
(query, params, indexes)
|
||||
}
|
||||
|
||||
/// Build a database connection pool.
|
||||
@@ -1003,13 +1046,17 @@ pub fn delete_expired(conn: &mut PooledConnection) -> Result<usize> {
|
||||
}
|
||||
|
||||
/// Perform database WAL checkpoint on a regular basis
|
||||
pub async fn db_checkpoint_task(pool: SqlitePool, frequency: Duration, write_in_progress: Arc<Mutex<u64>>, checkpoint_in_progress: Arc<Mutex<u64>>) -> Result<()> {
|
||||
pub async fn db_checkpoint_task(
|
||||
pool: SqlitePool,
|
||||
frequency: Duration,
|
||||
write_in_progress: Arc<Mutex<u64>>,
|
||||
checkpoint_in_progress: Arc<Mutex<u64>>) -> Result<()> {
|
||||
// TODO; use acquire_many on the reader semaphore to stop them from interrupting this.
|
||||
tokio::task::spawn(async move {
|
||||
// WAL size in pages.
|
||||
let mut current_wal_size = 0;
|
||||
// WAL threshold for more aggressive checkpointing (10,000 pages, or about 40MB)
|
||||
let wal_threshold = 1000*10;
|
||||
let wal_threshold = 1000 * 10;
|
||||
// default threshold for the busy timer
|
||||
let busy_wait_default = Duration::from_secs(1);
|
||||
// if the WAL file is getting too big, switch to this
|
||||
@@ -1079,7 +1126,6 @@ pub fn checkpoint_db(conn: &mut PooledConnection) -> Result<usize> {
|
||||
Ok(wal_size as usize)
|
||||
}
|
||||
|
||||
|
||||
/// Produce a arbitrary list of '?' parameters.
|
||||
fn repeat_vars(count: usize) -> String {
|
||||
if count == 0 {
|
||||
@@ -1113,7 +1159,6 @@ fn log_pool_stats(name: &str, pool: &SqlitePool) {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// Check if the pool is fully utilized
|
||||
fn _pool_at_capacity(pool: &SqlitePool) -> bool {
|
||||
let state: r2d2::State = pool.state();
|
||||
|
||||
Reference in New Issue
Block a user