mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-11-26 14:56:27 +03:00
Optimize CipherSyncData for very large vaults
As mentioned in #3111, using a very very large vault causes some issues. Mainly because of a SQLite limit, but, it could also cause issue on MariaDB/MySQL or PostgreSQL. It also uses a lot of memory, and memory allocations. This PR solves this by removing the need of all the cipher_uuid's just to gather the correct attachments. It will use the user_uuid and org_uuid's to get all attachments linked to both, weither the user has access to them or not. This isn't an issue, since the matching is done per cipher and the attachment data is only returned if there is a matching cipher to where the user has access to. I also modified some code to be able to use `::with_capacity(n)` where possible. This prevents re-allocations if the `Vec` increases size, which will happen a lot if there are a lot of ciphers. According to my tests measuring the time it takes to sync, it seems to have lowered the duration a bit more. Fixes #3111
This commit is contained in:
parent
367e1ce289
commit
3181e4e96e
4 changed files with 35 additions and 26 deletions
|
@ -104,16 +104,17 @@ async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value>
|
||||||
// Get all ciphers which are visible by the user
|
// Get all ciphers which are visible by the user
|
||||||
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await;
|
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await;
|
||||||
|
|
||||||
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &mut conn).await;
|
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, CipherSyncType::User, &mut conn).await;
|
||||||
|
|
||||||
// Lets generate the ciphers_json using all the gathered info
|
// Lets generate the ciphers_json using all the gathered info
|
||||||
let mut ciphers_json = Vec::new();
|
let mut ciphers_json = Vec::with_capacity(ciphers.len());
|
||||||
for c in ciphers {
|
for c in ciphers {
|
||||||
ciphers_json.push(c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &mut conn).await);
|
ciphers_json.push(c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &mut conn).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut collections_json = Vec::new();
|
let collections = Collection::find_by_user_uuid(headers.user.uuid.clone(), &mut conn).await;
|
||||||
for c in Collection::find_by_user_uuid(headers.user.uuid.clone(), &mut conn).await {
|
let mut collections_json = Vec::with_capacity(collections.len());
|
||||||
|
for c in collections {
|
||||||
collections_json.push(c.to_json_details(&headers.user.uuid, Some(&cipher_sync_data), &mut conn).await);
|
collections_json.push(c.to_json_details(&headers.user.uuid, Some(&cipher_sync_data), &mut conn).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,9 +149,9 @@ async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value>
|
||||||
#[get("/ciphers")]
|
#[get("/ciphers")]
|
||||||
async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
||||||
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await;
|
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await;
|
||||||
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &mut conn).await;
|
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, CipherSyncType::User, &mut conn).await;
|
||||||
|
|
||||||
let mut ciphers_json = Vec::new();
|
let mut ciphers_json = Vec::with_capacity(ciphers.len());
|
||||||
for c in ciphers {
|
for c in ciphers {
|
||||||
ciphers_json.push(c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &mut conn).await);
|
ciphers_json.push(c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &mut conn).await);
|
||||||
}
|
}
|
||||||
|
@ -1721,12 +1722,9 @@ pub enum CipherSyncType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CipherSyncData {
|
impl CipherSyncData {
|
||||||
pub async fn new(user_uuid: &str, ciphers: &[Cipher], sync_type: CipherSyncType, conn: &mut DbConn) -> Self {
|
pub async fn new(user_uuid: &str, sync_type: CipherSyncType, conn: &mut DbConn) -> Self {
|
||||||
// Generate a list of Cipher UUID's to be used during a query filter with an eq_any.
|
let cipher_folders: HashMap<String, String>;
|
||||||
let cipher_uuids = ciphers.iter().map(|c| c.uuid.clone()).collect();
|
let cipher_favorites: HashSet<String>;
|
||||||
|
|
||||||
let mut cipher_folders: HashMap<String, String> = HashMap::new();
|
|
||||||
let mut cipher_favorites: HashSet<String> = HashSet::new();
|
|
||||||
match sync_type {
|
match sync_type {
|
||||||
// User Sync supports Folders and Favorits
|
// User Sync supports Folders and Favorits
|
||||||
CipherSyncType::User => {
|
CipherSyncType::User => {
|
||||||
|
@ -1738,18 +1736,25 @@ impl CipherSyncData {
|
||||||
}
|
}
|
||||||
// Organization Sync does not support Folders and Favorits.
|
// Organization Sync does not support Folders and Favorits.
|
||||||
// If these are set, it will cause issues in the web-vault.
|
// If these are set, it will cause issues in the web-vault.
|
||||||
CipherSyncType::Organization => {}
|
CipherSyncType::Organization => {
|
||||||
|
cipher_folders = HashMap::with_capacity(0);
|
||||||
|
cipher_favorites = HashSet::with_capacity(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
|
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
|
||||||
let mut cipher_attachments: HashMap<String, Vec<Attachment>> = HashMap::new();
|
let user_org_uuids = UserOrganization::get_org_uuid_by_user(user_uuid, conn).await;
|
||||||
for attachment in Attachment::find_all_by_ciphers(&cipher_uuids, conn).await {
|
let attachments = Attachment::find_all_by_user_and_orgs(user_uuid, &user_org_uuids, conn).await;
|
||||||
|
let mut cipher_attachments: HashMap<String, Vec<Attachment>> = HashMap::with_capacity(attachments.len());
|
||||||
|
for attachment in attachments {
|
||||||
cipher_attachments.entry(attachment.cipher_uuid.clone()).or_default().push(attachment);
|
cipher_attachments.entry(attachment.cipher_uuid.clone()).or_default().push(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
|
// Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
|
||||||
let mut cipher_collections: HashMap<String, Vec<String>> = HashMap::new();
|
let user_cipher_collections = Cipher::get_collections_with_cipher_by_user(user_uuid.to_string(), conn).await;
|
||||||
for (cipher, collection) in Cipher::get_collections_with_cipher_by_user(user_uuid.to_string(), conn).await {
|
let mut cipher_collections: HashMap<String, Vec<String>> =
|
||||||
|
HashMap::with_capacity(user_cipher_collections.len());
|
||||||
|
for (cipher, collection) in user_cipher_collections {
|
||||||
cipher_collections.entry(cipher).or_default().push(collection);
|
cipher_collections.entry(cipher).or_default().push(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1768,14 +1773,14 @@ impl CipherSyncData {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Generate a HashMap with the collections_uuid as key and the CollectionGroup record
|
// Generate a HashMap with the collections_uuid as key and the CollectionGroup record
|
||||||
let user_collections_groups = CollectionGroup::find_by_user(user_uuid, conn)
|
let user_collections_groups: HashMap<String, CollectionGroup> = CollectionGroup::find_by_user(user_uuid, conn)
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|collection_group| (collection_group.collections_uuid.clone(), collection_group))
|
.map(|collection_group| (collection_group.collections_uuid.clone(), collection_group))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Get all organizations that the user has full access to via group assignement
|
// Get all organizations that the user has full access to via group assignement
|
||||||
let user_group_full_access_for_organizations =
|
let user_group_full_access_for_organizations: HashSet<String> =
|
||||||
Group::gather_user_organizations_full_access(user_uuid, conn).await.into_iter().collect();
|
Group::gather_user_organizations_full_access(user_uuid, conn).await.into_iter().collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -584,10 +584,9 @@ async fn view_emergency_access(emer_id: String, headers: Headers, mut conn: DbCo
|
||||||
}
|
}
|
||||||
|
|
||||||
let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &mut conn).await;
|
let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &mut conn).await;
|
||||||
let cipher_sync_data =
|
let cipher_sync_data = CipherSyncData::new(&emergency_access.grantor_uuid, CipherSyncType::User, &mut conn).await;
|
||||||
CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, CipherSyncType::User, &mut conn).await;
|
|
||||||
|
|
||||||
let mut ciphers_json = Vec::new();
|
let mut ciphers_json = Vec::with_capacity(ciphers.len());
|
||||||
for c in ciphers {
|
for c in ciphers {
|
||||||
ciphers_json
|
ciphers_json
|
||||||
.push(c.to_json(&headers.host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &mut conn).await);
|
.push(c.to_json(&headers.host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &mut conn).await);
|
||||||
|
|
|
@ -617,9 +617,9 @@ async fn get_org_details(data: OrgIdData, headers: Headers, mut conn: DbConn) ->
|
||||||
|
|
||||||
async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &mut DbConn) -> Value {
|
async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &mut DbConn) -> Value {
|
||||||
let ciphers = Cipher::find_by_org(org_id, conn).await;
|
let ciphers = Cipher::find_by_org(org_id, conn).await;
|
||||||
let cipher_sync_data = CipherSyncData::new(user_uuid, &ciphers, CipherSyncType::Organization, conn).await;
|
let cipher_sync_data = CipherSyncData::new(user_uuid, CipherSyncType::Organization, conn).await;
|
||||||
|
|
||||||
let mut ciphers_json = Vec::new();
|
let mut ciphers_json = Vec::with_capacity(ciphers.len());
|
||||||
for c in ciphers {
|
for c in ciphers {
|
||||||
ciphers_json.push(c.to_json(host, user_uuid, Some(&cipher_sync_data), conn).await);
|
ciphers_json.push(c.to_json(host, user_uuid, Some(&cipher_sync_data), conn).await);
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,10 +187,15 @@ impl Attachment {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_all_by_ciphers(cipher_uuids: &Vec<String>, conn: &mut DbConn) -> Vec<Self> {
|
// This will return all attachments linked to the user or org
|
||||||
|
// There is no filtering done here if the user actually has access!
|
||||||
|
// It is used to speed up the sync process, and the matching is done in a different part.
|
||||||
|
pub async fn find_all_by_user_and_orgs(user_uuid: &str, org_uuids: &Vec<String>, conn: &mut DbConn) -> Vec<Self> {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
attachments::table
|
attachments::table
|
||||||
.filter(attachments::cipher_uuid.eq_any(cipher_uuids))
|
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||||
|
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||||
|
.or_filter(ciphers::organization_uuid.eq_any(org_uuids))
|
||||||
.select(attachments::all_columns)
|
.select(attachments::all_columns)
|
||||||
.load::<AttachmentDb>(conn)
|
.load::<AttachmentDb>(conn)
|
||||||
.expect("Error loading attachments")
|
.expect("Error loading attachments")
|
||||||
|
|
Loading…
Reference in a new issue