mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-17 01:44:00 +00:00
Fix postgres connection options causing slow query speed. (#5150)
* Adding a query speed check. * Fixing slow queries due to connection config options. * Remove pointless set_config sql function. * Removing pointless bool. * Removing comment * Removing test.sh changes. * Add analyze to speed up query * Trying to fix DB perf connection try #1 * Try encoding option * Fix woodpecker * Try to use path character. * Fixing lemmy config location. * Removing pointless connection options. * Use OnceLock to create a once-init psql connection. * Fixing comment. * Fix host encoding for dev DB. * Address PR comments. * Revert query mut change.
This commit is contained in:
parent
a55e7fd9fe
commit
917e408735
4 changed files with 100 additions and 30 deletions
|
@ -122,7 +122,6 @@ steps:
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
- export LEMMY_CONFIG_LOCATION=./config/config.hjson
|
|
||||||
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
|
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
|
||||||
- diff config/defaults.hjson config/defaults_current.hjson
|
- diff config/defaults.hjson config/defaults_current.hjson
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
@ -147,7 +146,6 @@ steps:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
# same as scripts/db_perf.sh but without creating a new database server
|
# same as scripts/db_perf.sh but without creating a new database server
|
||||||
- export LEMMY_CONFIG_LOCATION=config/config.hjson
|
|
||||||
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
|
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
|
@ -176,8 +174,8 @@ steps:
|
||||||
RUST_BACKTRACE: "1"
|
RUST_BACKTRACE: "1"
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
LEMMY_TEST_FAST_FEDERATION: "1"
|
LEMMY_TEST_FAST_FEDERATION: "1"
|
||||||
|
LEMMY_CONFIG_LOCATION: ../../config/config.hjson
|
||||||
commands:
|
commands:
|
||||||
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
|
||||||
- cargo test --workspace --no-fail-fast
|
- cargo test --workspace --no-fail-fast
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ use diesel_async::{
|
||||||
ManagerConfig,
|
ManagerConfig,
|
||||||
},
|
},
|
||||||
AsyncConnection,
|
AsyncConnection,
|
||||||
RunQueryDsl,
|
|
||||||
};
|
};
|
||||||
use futures_util::{future::BoxFuture, Future, FutureExt};
|
use futures_util::{future::BoxFuture, Future, FutureExt};
|
||||||
use i_love_jesus::CursorKey;
|
use i_love_jesus::CursorKey;
|
||||||
|
@ -47,7 +46,7 @@ use rustls::{
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock, OnceLock},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
@ -59,6 +58,8 @@ pub const SITEMAP_LIMIT: i64 = 50000;
|
||||||
pub const SITEMAP_DAYS: Option<TimeDelta> = TimeDelta::try_days(31);
|
pub const SITEMAP_DAYS: Option<TimeDelta> = TimeDelta::try_days(31);
|
||||||
pub const RANK_DEFAULT: f64 = 0.0001;
|
pub const RANK_DEFAULT: f64 = 0.0001;
|
||||||
|
|
||||||
|
/// Some connection options to speed up queries
|
||||||
|
const CONNECTION_OPTIONS: [&str; 1] = ["geqo_threshold=12"];
|
||||||
pub type ActualDbPool = Pool<AsyncPgConnection>;
|
pub type ActualDbPool = Pool<AsyncPgConnection>;
|
||||||
|
|
||||||
/// References a pool or connection. Functions must take `&mut DbPool<'_>` to allow implicit
|
/// References a pool or connection. Functions must take `&mut DbPool<'_>` to allow implicit
|
||||||
|
@ -345,10 +346,37 @@ pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult<Option<DbUrl>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a few additional config options necessary for starting lemmy
|
||||||
|
fn build_config_options_uri_segment(config: &str) -> String {
|
||||||
|
let mut url = Url::parse(config).expect("Couldn't parse postgres connection URI");
|
||||||
|
|
||||||
|
// Set `lemmy.protocol_and_hostname` so triggers can use it
|
||||||
|
let lemmy_protocol_and_hostname_option =
|
||||||
|
"lemmy.protocol_and_hostname=".to_owned() + &SETTINGS.get_protocol_and_hostname();
|
||||||
|
let mut options = CONNECTION_OPTIONS.to_vec();
|
||||||
|
options.push(&lemmy_protocol_and_hostname_option);
|
||||||
|
|
||||||
|
// Create the connection uri portion
|
||||||
|
let options_segments = options
|
||||||
|
.iter()
|
||||||
|
.map(|o| "-c ".to_owned() + o)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
url.set_query(Some(&format!("options={options_segments}")));
|
||||||
|
url.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
|
fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
|
||||||
let fut = async {
|
let fut = async {
|
||||||
|
/// Use a once_lock to create the postgres connection config, since this config never changes
|
||||||
|
static POSTGRES_CONFIG_WITH_OPTIONS: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
|
let config =
|
||||||
|
POSTGRES_CONFIG_WITH_OPTIONS.get_or_init(|| build_config_options_uri_segment(config));
|
||||||
|
|
||||||
// We only support TLS with sslmode=require currently
|
// We only support TLS with sslmode=require currently
|
||||||
let mut conn = if config.contains("sslmode=require") {
|
let conn = if config.contains("sslmode=require") {
|
||||||
let rustls_config = DangerousClientConfigBuilder {
|
let rustls_config = DangerousClientConfigBuilder {
|
||||||
cfg: ClientConfig::builder(),
|
cfg: ClientConfig::builder(),
|
||||||
}
|
}
|
||||||
|
@ -369,24 +397,6 @@ fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConne
|
||||||
AsyncPgConnection::establish(config).await?
|
AsyncPgConnection::establish(config).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::select((
|
|
||||||
// Change geqo_threshold back to default value if it was changed, so it's higher than the
|
|
||||||
// collapse limits
|
|
||||||
functions::set_config("geqo_threshold", "12", false),
|
|
||||||
// Change collapse limits from 8 to 11 so the query planner can find a better table join
|
|
||||||
// order for more complicated queries
|
|
||||||
functions::set_config("from_collapse_limit", "11", false),
|
|
||||||
functions::set_config("join_collapse_limit", "11", false),
|
|
||||||
// Set `lemmy.protocol_and_hostname` so triggers can use it
|
|
||||||
functions::set_config(
|
|
||||||
"lemmy.protocol_and_hostname",
|
|
||||||
SETTINGS.get_protocol_and_hostname(),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)
|
|
||||||
.await
|
|
||||||
.map_err(ConnectionError::CouldntSetupConfiguration)?;
|
|
||||||
Ok(conn)
|
Ok(conn)
|
||||||
};
|
};
|
||||||
fut.boxed()
|
fut.boxed()
|
||||||
|
@ -498,7 +508,7 @@ static EMAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
});
|
});
|
||||||
|
|
||||||
pub mod functions {
|
pub mod functions {
|
||||||
use diesel::sql_types::{BigInt, Bool, Text, Timestamptz};
|
use diesel::sql_types::{BigInt, Text, Timestamptz};
|
||||||
|
|
||||||
sql_function! {
|
sql_function! {
|
||||||
#[sql_name = "r.hot_rank"]
|
#[sql_name = "r.hot_rank"]
|
||||||
|
@ -521,8 +531,6 @@ pub mod functions {
|
||||||
|
|
||||||
// really this function is variadic, this just adds the two-argument version
|
// really this function is variadic, this just adds the two-argument version
|
||||||
sql_function!(fn coalesce<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(x: diesel::sql_types::Nullable<T>, y: T) -> T);
|
sql_function!(fn coalesce<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(x: diesel::sql_types::Nullable<T>, y: T) -> T);
|
||||||
|
|
||||||
sql_function!(fn set_config(setting_name: Text, new_value: Text, is_local: Bool) -> Text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*";
|
pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*";
|
||||||
|
|
|
@ -734,6 +734,7 @@ mod tests {
|
||||||
structs::LocalUserView,
|
structs::LocalUserView,
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use diesel_async::SimpleAsyncConnection;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::PostAggregates,
|
aggregates::structs::PostAggregates,
|
||||||
impls::actor_language::UNDETERMINED_ID,
|
impls::actor_language::UNDETERMINED_ID,
|
||||||
|
@ -774,7 +775,7 @@ mod tests {
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
||||||
utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT},
|
utils::{build_db_pool, build_db_pool_for_tests, get_conn, DbPool, RANK_DEFAULT},
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
PostSortType,
|
PostSortType,
|
||||||
SubscribedType,
|
SubscribedType,
|
||||||
|
@ -782,7 +783,10 @@ mod tests {
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use std::{collections::HashSet, time::Duration};
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const POST_WITH_ANOTHER_TITLE: &str = "Another title";
|
const POST_WITH_ANOTHER_TITLE: &str = "Another title";
|
||||||
|
@ -1995,6 +1999,62 @@ mod tests {
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn speed_check() -> LemmyResult<()> {
|
||||||
|
let pool = &build_db_pool().await?;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await?;
|
||||||
|
|
||||||
|
// Make sure the post_view query is less than this time
|
||||||
|
let duration_max = Duration::from_millis(40);
|
||||||
|
|
||||||
|
// Create some dummy posts
|
||||||
|
let num_posts = 1000;
|
||||||
|
for x in 1..num_posts {
|
||||||
|
let name = format!("post_{x}");
|
||||||
|
let url = Some(Url::parse(&format!("https://google.com/{name}"))?.into());
|
||||||
|
|
||||||
|
let post_form = PostInsertForm {
|
||||||
|
url,
|
||||||
|
..PostInsertForm::new(
|
||||||
|
name,
|
||||||
|
data.local_user_view.person.id,
|
||||||
|
data.inserted_community.id,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Post::create(pool, &post_form).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually trigger and wait for a statistics update to ensure consistent and high amount of
|
||||||
|
// accuracy in the statistics used for query planning
|
||||||
|
println!("🧮 updating database statistics");
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
conn.batch_execute("ANALYZE;").await?;
|
||||||
|
|
||||||
|
// Time how fast the query took
|
||||||
|
let now = Instant::now();
|
||||||
|
PostQuery {
|
||||||
|
sort: Some(PostSortType::Active),
|
||||||
|
local_user: Some(&data.local_user_view.local_user),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
println!("Elapsed: {:.0?}", elapsed);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
elapsed.lt(&duration_max),
|
||||||
|
"Query took {:.0?}, longer than the max of {:.0?}",
|
||||||
|
elapsed,
|
||||||
|
duration_max
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup(data, pool).await
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_no_comments_only() -> LemmyResult<()> {
|
async fn post_listings_no_comments_only() -> LemmyResult<()> {
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
|
|
||||||
export PGDATA="$PWD/dev_pgdata"
|
export PGDATA="$PWD/dev_pgdata"
|
||||||
export PGHOST=$PWD
|
export PGHOST=$PWD
|
||||||
|
|
||||||
|
# Necessary to encode the dev db path into proper URL params
|
||||||
|
export ENCODED_HOST=$(printf $PWD | jq -sRr @uri)
|
||||||
|
|
||||||
export PGUSER=postgres
|
export PGUSER=postgres
|
||||||
export DATABASE_URL="postgresql://lemmy:password@/lemmy?host=$PWD"
|
export DATABASE_URL="postgresql://lemmy:password@$ENCODED_HOST/lemmy"
|
||||||
export LEMMY_DATABASE_URL=$DATABASE_URL
|
export LEMMY_DATABASE_URL=$DATABASE_URL
|
||||||
export PGDATABASE=lemmy
|
export PGDATABASE=lemmy
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue