From a0e129304c02674b02394b2808c0ed8665cff090 Mon Sep 17 00:00:00 2001 From: Matt Stavola Date: Sat, 30 Oct 2021 01:15:00 -0700 Subject: [PATCH] feat(lib): Reintroduce engine version file Right now if you boot up MeiliSearch and point it to a DB directory created with a previous version of MeiliSearch the existing indexes will be deleted. This used to be prevented by a startup check which would compare the current engine version vs what was stored in the DB directory's version file, but this functionality seems to have been lost after a few refactorings of the code. In order to go back to the old behavior we'll need to reintroduce the VERSION file that used to be present; I considered reusing the metadata.json file used in the dumps feature, but this seemed like the simpler and more approach. As the intent is just to restore functionality, the implementation is quite basic. I imagine that in the future we could build on this and do things like compatibility across major/minor versions and even migrating between formats. This PR was made thanks to @mbStavola Closes #1840 --- meilisearch-lib/src/index_controller/mod.rs | 8 +++ .../src/index_controller/versioning/error.rs | 16 ++++++ .../src/index_controller/versioning/mod.rs | 56 +++++++++++++++++++ meilisearch-lib/src/snapshot.rs | 11 ++++ 4 files changed, 91 insertions(+) create mode 100644 meilisearch-lib/src/index_controller/versioning/error.rs create mode 100644 meilisearch-lib/src/index_controller/versioning/mod.rs diff --git a/meilisearch-lib/src/index_controller/mod.rs b/meilisearch-lib/src/index_controller/mod.rs index e16f06df2..692a174fd 100644 --- a/meilisearch-lib/src/index_controller/mod.rs +++ b/meilisearch-lib/src/index_controller/mod.rs @@ -39,6 +39,7 @@ use crate::update_file_store::UpdateFileStore; mod dump_actor; pub mod error; +pub mod versioning; /// Concrete implementation of the IndexController, exposed by meilisearch-lib pub type MeiliSearch = IndexController; @@ -162,6 +163,11 @@ impl IndexControllerBuilder { .max_task_store_size .ok_or_else(|| anyhow::anyhow!("Missing update database size"))?; + let db_exists = db_path.as_ref().exists(); + if db_exists { + versioning::check_version_file(db_path.as_ref())?; + } + if let Some(ref path) = self.import_snapshot { log::info!("Loading from snapshot {:?}", path); load_snapshot( @@ -189,6 +195,8 @@ impl IndexControllerBuilder { let meta_env = options.open(&db_path)?; let update_file_store = UpdateFileStore::new(&db_path)?; + // Create or overwrite the version file for this DB + versioning::create_version_file(db_path.as_ref())?; let index_resolver = Arc::new(create_index_resolver( &db_path, diff --git a/meilisearch-lib/src/index_controller/versioning/error.rs b/meilisearch-lib/src/index_controller/versioning/error.rs new file mode 100644 index 000000000..4dc29e862 --- /dev/null +++ b/meilisearch-lib/src/index_controller/versioning/error.rs @@ -0,0 +1,16 @@ +#[derive(thiserror::Error, Debug)] +pub enum VersionFileError { + #[error("Version file is missing or the previous MeiliSearch engine version was below 0.24.0. Use a dump to update Meilisearch.")] + MissingVersionFile, + #[error("Version file is corrupted and thus MeiliSearch is unable to determine the version of the database.")] + MalformedVersionFile, + #[error( + "Expected MeiliSearch engine version: {major}.{minor}.{patch}, current engine version: {}. To update Meilisearch use a dump.", + env!("CARGO_PKG_VERSION").to_string() + )] + VersionMismatch { + major: String, + minor: String, + patch: String, + }, +} diff --git a/meilisearch-lib/src/index_controller/versioning/mod.rs b/meilisearch-lib/src/index_controller/versioning/mod.rs new file mode 100644 index 000000000..eba5d477e --- /dev/null +++ b/meilisearch-lib/src/index_controller/versioning/mod.rs @@ -0,0 +1,56 @@ +use std::fs; +use std::io::ErrorKind; +use std::path::Path; + +use self::error::VersionFileError; + +mod error; + +pub const VERSION_FILE_NAME: &str = "VERSION"; + +static VERSION_MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR"); +static VERSION_MINOR: &str = env!("CARGO_PKG_VERSION_MINOR"); +static VERSION_PATCH: &str = env!("CARGO_PKG_VERSION_PATCH"); + +// Persists the version of the current MeiliSearch binary to a VERSION file +pub fn create_version_file(db_path: &Path) -> anyhow::Result<()> { + let version_path = db_path.join(VERSION_FILE_NAME); + fs::write( + version_path, + format!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH), + )?; + + Ok(()) +} + +// Ensures Meilisearch version is compatible with the database, returns an error versions mismatch. +pub fn check_version_file(db_path: &Path) -> anyhow::Result<()> { + let version_path = db_path.join(VERSION_FILE_NAME); + + match fs::read_to_string(&version_path) { + Ok(version) => { + let version_components = version.split('.').collect::>(); + let (major, minor, patch) = match &version_components[..] { + [major, minor, patch] => (major.to_string(), minor.to_string(), patch.to_string()), + _ => return Err(VersionFileError::MalformedVersionFile.into()), + }; + + if major != VERSION_MAJOR || minor != VERSION_MINOR { + return Err(VersionFileError::VersionMismatch { + major, + minor, + patch, + } + .into()); + } + } + Err(error) => { + return match error.kind() { + ErrorKind::NotFound => Err(VersionFileError::MissingVersionFile.into()), + _ => Err(error.into()), + } + } + } + + Ok(()) +} diff --git a/meilisearch-lib/src/snapshot.rs b/meilisearch-lib/src/snapshot.rs index 2f3ee8474..556e7fabd 100644 --- a/meilisearch-lib/src/snapshot.rs +++ b/meilisearch-lib/src/snapshot.rs @@ -9,6 +9,7 @@ use tokio::time::sleep; use walkdir::WalkDir; use crate::compression::from_tar_gz; +use crate::index_controller::versioning::VERSION_FILE_NAME; use crate::tasks::task::Job; use crate::tasks::TaskStore; @@ -102,6 +103,7 @@ impl SnapshotJob { let temp_snapshot_dir = tempfile::tempdir()?; let temp_snapshot_path = temp_snapshot_dir.path(); + self.snapshot_version_file(temp_snapshot_path)?; self.snapshot_meta_env(temp_snapshot_path)?; self.snapshot_file_store(temp_snapshot_path)?; self.snapshot_indexes(temp_snapshot_path)?; @@ -133,6 +135,15 @@ impl SnapshotJob { Ok(()) } + fn snapshot_version_file(&self, path: &Path) -> anyhow::Result<()> { + let dst = path.join(VERSION_FILE_NAME); + let src = self.src_path.join(VERSION_FILE_NAME); + + fs::copy(src, dst)?; + + Ok(()) + } + fn snapshot_meta_env(&self, path: &Path) -> anyhow::Result<()> { let mut options = heed::EnvOpenOptions::new(); options.map_size(self.meta_env_size);