Optimize federated language updates to avoid unnecessary db writes (fixes #2772)

This commit is contained in:
Felix Ableitner 2023-03-21 21:11:40 +01:00
parent 6f513793cb
commit a9f8e4489b
7 changed files with 58 additions and 16 deletions

1
Cargo.lock generated
View file

@ -2517,6 +2517,7 @@ dependencies = [
"diesel-derive-newtype",
"diesel_ltree",
"diesel_migrations",
"futures",
"lemmy_utils",
"once_cell",
"regex",

View file

@ -80,7 +80,7 @@ impl PerformApub for GetCommunity {
let site_id =
Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into());
let mut site = Site::read_from_apub_id(context.pool(), site_id).await?;
let mut site = Site::read_from_apub_id(context.pool(), &site_id.into()).await?;
// no need to include metadata for local site (its already available through other endpoints).
// this also prevents us from leaking the federation private key.
if let Some(s) = &site {

View file

@ -71,7 +71,7 @@ impl Object for ApubSite {
data: &Data<Self::DataType>,
) -> Result<Option<Self>, LemmyError> {
Ok(
Site::read_from_apub_id(data.pool(), object_id)
Site::read_from_apub_id(data.pool(), &object_id.into())
.await?
.map(Into::into),
)

View file

@ -41,6 +41,7 @@ async-trait = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
tracing-error = { workspace = true }
futures = { workspace = true }
deadpool = { version = "0.9.5", features = ["rt_tokio_1"], optional = true }
[dev-dependencies]

View file

@ -68,6 +68,13 @@ impl LocalUserLanguage {
for_local_user_id: LocalUserId,
) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?;
let lang_ids = convert_update_languages(conn, language_ids).await?;
// No need to update if languages are unchanged
let current = LocalUserLanguage::read(pool, for_local_user_id).await?;
if current == lang_ids {
return Ok(());
}
conn
.build_transaction()
@ -79,7 +86,6 @@ impl LocalUserLanguage {
.execute(conn)
.await?;
let lang_ids = convert_update_languages(conn, language_ids).await?;
for l in lang_ids {
let form = LocalUserLanguageForm {
local_user_id: for_local_user_id,
@ -129,6 +135,13 @@ impl SiteLanguage {
let conn = &mut get_conn(pool).await?;
let for_site_id = site.id;
let instance_id = site.instance_id;
let lang_ids = convert_update_languages(conn, language_ids).await?;
// No need to update if languages are unchanged
let current = SiteLanguage::read(pool, site.id).await?;
if current == lang_ids {
return Ok(());
}
conn
.build_transaction()
@ -141,7 +154,6 @@ impl SiteLanguage {
.execute(conn)
.await?;
let lang_ids = convert_update_languages(conn, language_ids).await?;
for l in lang_ids {
let form = SiteLanguageForm {
site_id: for_site_id,
@ -243,11 +255,16 @@ impl CommunityLanguage {
for_community_id: CommunityId,
) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?;
if language_ids.is_empty() {
language_ids = SiteLanguage::read_local(pool).await?;
}
// No need to update if languages are unchanged
let current = CommunityLanguage::read(pool, for_community_id).await?;
if current == language_ids {
return Ok(());
}
conn
.build_transaction()
.run(|conn| {

View file

@ -21,6 +21,7 @@ use crate::{
};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods};
use diesel_async::RunQueryDsl;
use futures::future::OptionFuture;
#[async_trait]
impl Crud for Community {
@ -41,6 +42,14 @@ impl Crud for Community {
async fn create(pool: &DbPool, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let existing = OptionFuture::from(
form
.actor_id
.as_ref()
.map(|id| Community::read_from_apub_id(pool, id)),
)
.await;
// Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
let community_ = insert_into(community)
.values(form)
.on_conflict(actor_id)
@ -49,13 +58,16 @@ impl Crud for Community {
.get_result::<Self>(conn)
.await?;
let site_languages = SiteLanguage::read_local(pool).await;
if let Ok(langs) = site_languages {
// if site exists, init user with site languages
CommunityLanguage::update(pool, langs, community_.id).await?;
} else {
// otherwise, init with all languages (this only happens during tests)
CommunityLanguage::update(pool, vec![], community_.id).await?;
// Initialize languages for new community
if existing.is_none() {
let site_languages = SiteLanguage::read_local(pool).await;
if let Ok(langs) = site_languages {
// if site exists, init community with site languages
CommunityLanguage::update(pool, langs, community_.id).await?;
} else {
// otherwise, init with all languages (this only happens during tests)
CommunityLanguage::update(pool, vec![], community_.id).await?;
}
}
Ok(community_)

View file

@ -10,6 +10,7 @@ use crate::{
};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use futures::future::OptionFuture;
use url::Url;
#[async_trait]
@ -25,6 +26,14 @@ impl Crud for Site {
async fn create(pool: &DbPool, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let existing = OptionFuture::from(
form
.actor_id
.as_ref()
.map(|id_| Site::read_from_apub_id(pool, id_)),
)
.await;
// Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
let site_ = insert_into(site)
.values(form)
.on_conflict(actor_id)
@ -33,8 +42,11 @@ impl Crud for Site {
.get_result::<Self>(conn)
.await?;
// initialize with all languages
SiteLanguage::update(pool, vec![], &site_).await?;
// initialize languages if site is newly created
if existing.is_none() {
// initialize with all languages
SiteLanguage::update(pool, vec![], &site_).await?;
}
Ok(site_)
}
@ -57,9 +69,8 @@ impl Crud for Site {
}
impl Site {
pub async fn read_from_apub_id(pool: &DbPool, object_id: Url) -> Result<Option<Self>, Error> {
pub async fn read_from_apub_id(pool: &DbPool, object_id: &DbUrl) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
let object_id: DbUrl = object_id.into();
Ok(
site
.filter(actor_id.eq(object_id))