feat(sqlite): Run VACUUM operation after removing a room

A room can be associated to a lot of data, depending on the number of members in the room.
So freeing space on the filesystem should be worth it in some cases.

An (extreme) example: I have a test account that is in ~60 rooms, a few of those big public rooms, including Matrix HQ. The size of the matrix-sdk-state.sqlite3 file is 542 MB. Using this PR and leaving, then forgetting Matrix HQ brings the DB down to 255 MB.
This commit is contained in:
Kévin Commaille
2025-02-11 15:13:25 +01:00
committed by GitHub
parent 69588d5266
commit 65ee18a52d
4 changed files with 51 additions and 40 deletions
+2
View File
@@ -10,6 +10,8 @@ All notable changes to this project will be documented in this file.
- Implement the new method of `EventCacheStoreMedia` for `SqliteEventCacheStore`.
([#4603](https://github.com/matrix-org/matrix-rust-sdk/pull/4603))
- Defragment an sqlite state store after removing a room.
([#4651](https://github.com/matrix-org/matrix-rust-sdk/pull/4651))
## [0.10.0] - 2025-02-04
@@ -36,8 +36,6 @@ use matrix_sdk_store_encryption::StoreCipher;
use ruma::{time::SystemTime, MilliSecondsSinceUnixEpoch, MxcUri, RoomId};
use rusqlite::{params_from_iter, OptionalExtension, Transaction, TransactionBehavior};
use tokio::fs;
#[cfg(not(test))]
use tracing::warn;
use tracing::{debug, trace};
use crate::{
@@ -959,19 +957,10 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
})
.await?;
// If we removed media, use the VACUUM command to defragment the
// database and free space on the filesystem.
// If we removed media, defragment the database and free space on the
// filesystem.
if removed {
if let Err(error) = conn.execute("VACUUM", ()).await {
// Since this is an optimisation step, do not propagate the error
// but log it.
#[cfg(not(test))]
warn!("Failed to vacuum database: {error}");
// We want to know if there is an error with this step during tests.
#[cfg(test)]
return Err(error.into());
}
conn.vacuum().await?;
}
Ok(())
+28 -26
View File
@@ -336,7 +336,7 @@ impl SqliteStateStore {
// Defragment the DB and optimize its size on the filesystem.
// This should have been run in the migration for version 7, to reduce the size
// of the DB as we removed the media cache.
conn.execute_batch("VACUUM").await?;
conn.vacuum().await?;
conn.set_kv("version", vec![12]).await?;
}
@@ -1739,40 +1739,42 @@ impl StateStore for SqliteStateStore {
let this = self.clone();
let room_id = room_id.to_owned();
self.acquire()
.await?
.with_transaction(move |txn| {
let room_info_room_id = this.encode_key(keys::ROOM_INFO, &room_id);
txn.remove_room_info(&room_info_room_id)?;
let conn = self.acquire().await?;
let state_event_room_id = this.encode_key(keys::STATE_EVENT, &room_id);
txn.remove_room_state_events(&state_event_room_id, None)?;
conn.with_transaction(move |txn| -> Result<()> {
let room_info_room_id = this.encode_key(keys::ROOM_INFO, &room_id);
txn.remove_room_info(&room_info_room_id)?;
let member_room_id = this.encode_key(keys::MEMBER, &room_id);
txn.remove_room_members(&member_room_id, None)?;
let state_event_room_id = this.encode_key(keys::STATE_EVENT, &room_id);
txn.remove_room_state_events(&state_event_room_id, None)?;
let profile_room_id = this.encode_key(keys::PROFILE, &room_id);
txn.remove_room_profiles(&profile_room_id)?;
let member_room_id = this.encode_key(keys::MEMBER, &room_id);
txn.remove_room_members(&member_room_id, None)?;
let room_account_data_room_id = this.encode_key(keys::ROOM_ACCOUNT_DATA, &room_id);
txn.remove_room_account_data(&room_account_data_room_id)?;
let profile_room_id = this.encode_key(keys::PROFILE, &room_id);
txn.remove_room_profiles(&profile_room_id)?;
let receipt_room_id = this.encode_key(keys::RECEIPT, &room_id);
txn.remove_room_receipts(&receipt_room_id)?;
let room_account_data_room_id = this.encode_key(keys::ROOM_ACCOUNT_DATA, &room_id);
txn.remove_room_account_data(&room_account_data_room_id)?;
let display_name_room_id = this.encode_key(keys::DISPLAY_NAME, &room_id);
txn.remove_room_display_names(&display_name_room_id)?;
let receipt_room_id = this.encode_key(keys::RECEIPT, &room_id);
txn.remove_room_receipts(&receipt_room_id)?;
let send_queue_room_id = this.encode_key(keys::SEND_QUEUE, &room_id);
txn.remove_room_send_queue(&send_queue_room_id)?;
let display_name_room_id = this.encode_key(keys::DISPLAY_NAME, &room_id);
txn.remove_room_display_names(&display_name_room_id)?;
let dependent_send_queue_room_id =
this.encode_key(keys::DEPENDENTS_SEND_QUEUE, &room_id);
txn.remove_room_dependent_send_queue(&dependent_send_queue_room_id)?;
let send_queue_room_id = this.encode_key(keys::SEND_QUEUE, &room_id);
txn.remove_room_send_queue(&send_queue_room_id)?;
Ok(())
})
.await
let dependent_send_queue_room_id =
this.encode_key(keys::DEPENDENTS_SEND_QUEUE, &room_id);
txn.remove_room_dependent_send_queue(&dependent_send_queue_room_id)?;
Ok(())
})
.await?;
conn.vacuum().await
}
async fn save_send_queue_request(
+18
View File
@@ -136,6 +136,24 @@ pub(crate) trait SqliteAsyncConnExt {
self.execute_batch("PRAGMA journal_size_limit = 10000000;").await.map_err(Error::from)?;
Ok(())
}
/// Defragment the database and free space on the filesystem.
///
/// Only returns an error in tests, otherwise the error is only logged.
async fn vacuum(&self) -> Result<()> {
if let Err(error) = self.execute_batch("VACUUM").await {
// Since this is an optimisation step, do not propagate the error
// but log it.
#[cfg(not(any(test, debug_assertions)))]
tracing::warn!("Failed to vacuum database: {error}");
// We want to know if there is an error with this step during tests.
#[cfg(any(test, debug_assertions))]
return Err(error.into());
}
Ok(())
}
}
#[async_trait]