From 55d6a81a75c279aa6138e88f4e9a948e2c8f6b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 2 Oct 2025 17:12:10 +0200 Subject: [PATCH] Introduce a new /indexes/{indexUid}/compact route --- .../meilisearch/src/routes/indexes/compact.rs | 100 ++++++++++++++++++ crates/meilisearch/src/routes/indexes/mod.rs | 7 +- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 crates/meilisearch/src/routes/indexes/compact.rs diff --git a/crates/meilisearch/src/routes/indexes/compact.rs b/crates/meilisearch/src/routes/indexes/compact.rs new file mode 100644 index 000000000..5228a58b6 --- /dev/null +++ b/crates/meilisearch/src/routes/indexes/compact.rs @@ -0,0 +1,100 @@ +use actix_web::web::{self, Data}; +use actix_web::{HttpRequest, HttpResponse}; +use index_scheduler::IndexScheduler; +use meilisearch_types::error::ResponseError; +use meilisearch_types::index_uid::IndexUid; +use meilisearch_types::keys::actions; +use meilisearch_types::tasks::KindWithContent; +use serde::Serialize; +use tracing::debug; +use utoipa::OpenApi; + +use super::ActionPolicy; +use crate::analytics::{Aggregate, Analytics}; +use crate::extractors::authentication::GuardedData; +use crate::extractors::sequential_extractor::SeqHandler; +use crate::routes::SummarizedTaskView; + +#[derive(OpenApi)] +#[openapi( + paths(compact), + tags( + ( + name = "Compact an index", + description = "The /compact route uses compacts the database to reoganize and make it smaller and more efficient.", + external_docs(url = "https://www.meilisearch.com/docs/reference/api/compact"), + ), + ), +)] +pub struct CompactApi; + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service(web::resource("").route(web::post().to(SeqHandler(compact)))); +} + +/// Compact an index +#[utoipa::path( + post, + path = "{indexUid}/compact", + tag = "Compact an index", + security(("Bearer" = ["search", "*"])), + params(("indexUid" = String, Path, example = "movies", description = "Index Unique Identifier", nullable = false)), + responses( + (status = ACCEPTED, description = "Task successfully enqueued", body = SummarizedTaskView, content_type = "application/json", example = json!( + { + "taskUid": 147, + "indexUid": null, + "status": "enqueued", + "type": "documentDeletion", + "enqueuedAt": "2024-08-08T17:05:55.791772Z" + } + )), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] +pub async fn compact( + index_scheduler: GuardedData, Data>, + index_uid: web::Path, + req: HttpRequest, + analytics: web::Data, +) -> Result { + let index_uid = IndexUid::try_from(index_uid.into_inner())?; + + analytics.publish(IndexesCompactAggregator, &req); + + let task = KindWithContent::CompactIndex { index_uid: index_uid.to_string() }; + let task = + match tokio::task::spawn_blocking(move || index_scheduler.register(task, None, false)) + .await? + { + Ok(task) => task, + Err(e) => return Err(e.into()), + }; + + debug!(returns = ?task, "Compact the {index_uid} index"); + Ok(HttpResponse::Accepted().json(SummarizedTaskView::from(task))) +} + +#[derive(Serialize)] +pub struct IndexesCompactAggregator; + +impl Aggregate for IndexesCompactAggregator { + fn event_name(&self) -> &'static str { + "Indexes Compacted" + } + + fn aggregate(self: Box, _new: Box) -> Box { + Box::new(Self) + } + + fn into_event(self: Box) -> serde_json::Value { + serde_json::to_value(*self).unwrap_or_default() + } +} diff --git a/crates/meilisearch/src/routes/indexes/mod.rs b/crates/meilisearch/src/routes/indexes/mod.rs index 8e994bb43..d3c399dec 100644 --- a/crates/meilisearch/src/routes/indexes/mod.rs +++ b/crates/meilisearch/src/routes/indexes/mod.rs @@ -28,6 +28,7 @@ use crate::extractors::sequential_extractor::SeqHandler; use crate::routes::is_dry_run; use crate::Opt; +pub mod compact; pub mod documents; mod enterprise_edition; pub mod facet_search; @@ -49,8 +50,9 @@ pub use enterprise_edition::proxy::{PROXY_ORIGIN_REMOTE_HEADER, PROXY_ORIGIN_TAS (path = "/", api = facet_search::FacetSearchApi), (path = "/", api = similar::SimilarApi), (path = "/", api = settings::SettingsApi), + (path = "/", api = compact::CompactApi), ), - paths(list_indexes, create_index, get_index, update_index, delete_index, get_index_stats), + paths(list_indexes, create_index, get_index, update_index, delete_index, get_index_stats, compact::compact), tags( ( name = "Indexes", @@ -80,7 +82,8 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(web::scope("/search").configure(search::configure)) .service(web::scope("/facet-search").configure(facet_search::configure)) .service(web::scope("/similar").configure(similar::configure)) - .service(web::scope("/settings").configure(settings::configure)), + .service(web::scope("/settings").configure(settings::configure)) + .service(web::scope("/compact").configure(compact::configure)), ); }