test snapshots

This commit is contained in:
mpostma 2021-10-04 18:31:05 +02:00
parent 0448f0ce56
commit 85ae34cf9f
8 changed files with 579 additions and 138 deletions

96
Cargo.lock generated
View File

@ -825,6 +825,12 @@ version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2c9736e15e7df1638a7f6eee92a6511615c738246a052af5ba86f039b65aede" checksum = "f2c9736e15e7df1638a7f6eee92a6511615c738246a052af5ba86f039b65aede"
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.8.1" version = "0.8.1"
@ -849,6 +855,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "downcast"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d"
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -933,6 +945,15 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float-cmp"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -949,6 +970,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fragile"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2"
[[package]] [[package]]
name = "fs_extra" name = "fs_extra"
version = "1.2.0" version = "1.2.0"
@ -1688,6 +1715,7 @@ dependencies = [
"memmap", "memmap",
"milli", "milli",
"mime", "mime",
"mockall",
"num_cpus", "num_cpus",
"obkv", "obkv",
"once_cell", "once_cell",
@ -1847,6 +1875,39 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "mockall"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab571328afa78ae322493cacca3efac6a0f2e0a67305b4df31fd439ef129ac0"
dependencies = [
"cfg-if 1.0.0",
"downcast",
"fragile",
"lazy_static",
"mockall_derive",
"predicates",
"predicates-tree",
]
[[package]]
name = "mockall_derive"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e25b214433f669161f414959594216d8e6ba83b6679d3db96899c0b4639033"
dependencies = [
"cfg-if 1.0.0",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.77",
]
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.3.6" version = "0.3.6"
@ -2119,6 +2180,35 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "predicates"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
dependencies = [
"difference",
"float-cmp",
"normalize-line-endings",
"predicates-core",
"regex",
]
[[package]]
name = "predicates-core"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
[[package]]
name = "predicates-tree"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7dd0fd014130206c9352efbdc92be592751b2b9274dff685348341082c6ea3d"
dependencies = [
"predicates-core",
"treeline",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -3044,6 +3134,12 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.3" version = "0.2.3"

View File

@ -60,4 +60,5 @@ derivative = "2.2.0"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2.0" actix-rt = "2.2.0"
mockall = "0.10.2"
paste = "1.0.5" paste = "1.0.5"

View File

@ -0,0 +1,287 @@
use std::collections::{BTreeSet, HashSet};
use std::fs::create_dir_all;
use std::marker::PhantomData;
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use heed::{EnvOpenOptions, RoTxn};
use milli::update::Setting;
use milli::{obkv_to_json, FieldDistribution, FieldId};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use uuid::Uuid;
use crate::index_controller::update_file_store::UpdateFileStore;
use crate::EnvSizer;
use super::{Checked, Settings};
use super::error::IndexError;
use super::update_handler::UpdateHandler;
use super::error::Result;
pub type Document = Map<String, Value>;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct IndexMeta {
created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub primary_key: Option<String>,
}
impl IndexMeta {
pub fn new(index: &Index) -> Result<Self> {
let txn = index.read_txn()?;
Self::new_txn(index, &txn)
}
pub fn new_txn(index: &Index, txn: &heed::RoTxn) -> Result<Self> {
let created_at = index.created_at(txn)?;
let updated_at = index.updated_at(txn)?;
let primary_key = index.primary_key(txn)?.map(String::from);
Ok(Self {
created_at,
updated_at,
primary_key,
})
}
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct IndexStats {
#[serde(skip)]
pub size: u64,
pub number_of_documents: u64,
/// Whether the current index is performing an update. It is initially `None` when the
/// index returns it, since it is the `UpdateStore` that knows what index is currently indexing. It is
/// later set to either true or false, we we retrieve the information from the `UpdateStore`
pub is_indexing: Option<bool>,
pub field_distribution: FieldDistribution,
}
#[derive(Clone, derivative::Derivative)]
#[derivative(Debug)]
pub struct Index {
pub uuid: Uuid,
#[derivative(Debug = "ignore")]
pub inner: Arc<milli::Index>,
#[derivative(Debug = "ignore")]
pub update_file_store: Arc<UpdateFileStore>,
#[derivative(Debug = "ignore")]
pub update_handler: Arc<UpdateHandler>,
}
impl Deref for Index {
type Target = milli::Index;
fn deref(&self) -> &Self::Target {
self.inner.as_ref()
}
}
impl Index {
pub fn open(
path: impl AsRef<Path>,
size: usize,
update_file_store: Arc<UpdateFileStore>,
uuid: Uuid,
update_handler: Arc<UpdateHandler>,
) -> Result<Self> {
create_dir_all(&path)?;
let mut options = EnvOpenOptions::new();
options.map_size(size);
let inner = Arc::new(milli::Index::new(options, &path)?);
Ok(Index {
inner,
update_file_store,
uuid,
update_handler,
})
}
pub fn inner(&self) -> &milli::Index {
&self.inner
}
pub fn stats(&self) -> Result<IndexStats> {
let rtxn = self.read_txn()?;
Ok(IndexStats {
size: self.size(),
number_of_documents: self.number_of_documents(&rtxn)?,
is_indexing: None,
field_distribution: self.field_distribution(&rtxn)?,
})
}
pub fn meta(&self) -> Result<IndexMeta> {
IndexMeta::new(self)
}
pub fn settings(&self) -> Result<Settings<Checked>> {
let txn = self.read_txn()?;
self.settings_txn(&txn)
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn settings_txn(&self, txn: &RoTxn) -> Result<Settings<Checked>> {
let displayed_attributes = self
.displayed_fields(txn)?
.map(|fields| fields.into_iter().map(String::from).collect());
let searchable_attributes = self
.searchable_fields(txn)?
.map(|fields| fields.into_iter().map(String::from).collect());
let filterable_attributes = self.filterable_fields(txn)?.into_iter().collect();
let sortable_attributes = self.sortable_fields(txn)?.into_iter().collect();
let criteria = self
.criteria(txn)?
.into_iter()
.map(|c| c.to_string())
.collect();
let stop_words = self
.stop_words(txn)?
.map(|stop_words| -> Result<BTreeSet<_>> {
Ok(stop_words.stream().into_strs()?.into_iter().collect())
})
.transpose()?
.unwrap_or_else(BTreeSet::new);
let distinct_field = self.distinct_field(txn)?.map(String::from);
// in milli each word in the synonyms map were split on their separator. Since we lost
// this information we are going to put space between words.
let synonyms = self
.synonyms(txn)?
.iter()
.map(|(key, values)| {
(
key.join(" "),
values.iter().map(|value| value.join(" ")).collect(),
)
})
.collect();
Ok(Settings {
displayed_attributes: match displayed_attributes {
Some(attrs) => Setting::Set(attrs),
None => Setting::Reset,
},
searchable_attributes: match searchable_attributes {
Some(attrs) => Setting::Set(attrs),
None => Setting::Reset,
},
filterable_attributes: Setting::Set(filterable_attributes),
sortable_attributes: Setting::Set(sortable_attributes),
ranking_rules: Setting::Set(criteria),
stop_words: Setting::Set(stop_words),
distinct_attribute: match distinct_field {
Some(field) => Setting::Set(field),
None => Setting::Reset,
},
synonyms: Setting::Set(synonyms),
_kind: PhantomData,
})
}
pub fn retrieve_documents<S: AsRef<str>>(
&self,
offset: usize,
limit: usize,
attributes_to_retrieve: Option<Vec<S>>,
) -> Result<Vec<Map<String, Value>>> {
let txn = self.read_txn()?;
let fields_ids_map = self.fields_ids_map(&txn)?;
let fields_to_display =
self.fields_to_display(&txn, &attributes_to_retrieve, &fields_ids_map)?;
let iter = self.documents.range(&txn, &(..))?.skip(offset).take(limit);
let mut documents = Vec::new();
for entry in iter {
let (_id, obkv) = entry?;
let object = obkv_to_json(&fields_to_display, &fields_ids_map, obkv)?;
documents.push(object);
}
Ok(documents)
}
pub fn retrieve_document<S: AsRef<str>>(
&self,
doc_id: String,
attributes_to_retrieve: Option<Vec<S>>,
) -> Result<Map<String, Value>> {
let txn = self.read_txn()?;
let fields_ids_map = self.fields_ids_map(&txn)?;
let fields_to_display =
self.fields_to_display(&txn, &attributes_to_retrieve, &fields_ids_map)?;
let internal_id = self
.external_documents_ids(&txn)?
.get(doc_id.as_bytes())
.ok_or_else(|| IndexError::DocumentNotFound(doc_id.clone()))?;
let document = self
.documents(&txn, std::iter::once(internal_id))?
.into_iter()
.next()
.map(|(_, d)| d)
.ok_or(IndexError::DocumentNotFound(doc_id))?;
let document = obkv_to_json(&fields_to_display, &fields_ids_map, document)?;
Ok(document)
}
pub fn size(&self) -> u64 {
self.env.size()
}
fn fields_to_display<S: AsRef<str>>(
&self,
txn: &heed::RoTxn,
attributes_to_retrieve: &Option<Vec<S>>,
fields_ids_map: &milli::FieldsIdsMap,
) -> Result<Vec<FieldId>> {
let mut displayed_fields_ids = match self.displayed_fields_ids(txn)? {
Some(ids) => ids.into_iter().collect::<Vec<_>>(),
None => fields_ids_map.iter().map(|(id, _)| id).collect(),
};
let attributes_to_retrieve_ids = match attributes_to_retrieve {
Some(attrs) => attrs
.iter()
.filter_map(|f| fields_ids_map.id(f.as_ref()))
.collect::<HashSet<_>>(),
None => fields_ids_map.iter().map(|(id, _)| id).collect(),
};
displayed_fields_ids.retain(|fid| attributes_to_retrieve_ids.contains(fid));
Ok(displayed_fields_ids)
}
pub fn snapshot(&self, path: impl AsRef<Path>) -> Result<()> {
let mut dst = path.as_ref().join(format!("indexes/{}/", self.uuid));
create_dir_all(&dst)?;
dst.push("data.mdb");
let _txn = self.write_txn()?;
self.inner
.env
.copy_to_path(dst, heed::CompactionOption::Enabled)?;
Ok(())
}
}

View File

@ -1,5 +1,5 @@
pub mod error; pub mod error;
mod index_store; pub mod index_store;
pub mod uuid_store; pub mod uuid_store;
use std::path::Path; use std::path::Path;

View File

@ -11,20 +11,26 @@ use tokio::time::sleep;
use crate::compression::from_tar_gz; use crate::compression::from_tar_gz;
use crate::index_controller::updates::UpdateMsg; use crate::index_controller::updates::UpdateMsg;
use super::index_resolver::HardStateIndexResolver; use super::index_resolver::IndexResolver;
use super::index_resolver::index_store::IndexStore;
use super::index_resolver::uuid_store::UuidStore;
use super::updates::UpdateSender; use super::updates::UpdateSender;
pub struct SnapshotService { pub struct SnapshotService<U, I> {
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
update_sender: UpdateSender, update_sender: UpdateSender,
snapshot_period: Duration, snapshot_period: Duration,
snapshot_path: PathBuf, snapshot_path: PathBuf,
db_name: String, db_name: String,
} }
impl SnapshotService { impl<U, I> SnapshotService<U, I>
where
U: UuidStore + Sync + Send + 'static,
I: IndexStore + Sync + Send + 'static,
{
pub fn new( pub fn new(
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
update_sender: UpdateSender, update_sender: UpdateSender,
snapshot_period: Duration, snapshot_period: Duration,
snapshot_path: PathBuf, snapshot_path: PathBuf,
@ -127,131 +133,161 @@ pub fn load_snapshot(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
//use std::iter::FromIterator; use std::{collections::HashSet, sync::Arc};
//use std::{collections::HashSet, sync::Arc};
//use futures::future::{err, ok}; use futures::future::{err, ok};
//use rand::Rng; use once_cell::sync::Lazy;
//use tokio::time::timeout; use rand::Rng;
//use uuid::Uuid; use uuid::Uuid;
//use super::*; use crate::index::error::IndexError;
use crate::index::test::Mocker;
use crate::index::{Index, error::Result as IndexResult};
use crate::index_controller::index_resolver::IndexResolver;
use crate::index_controller::index_resolver::error::IndexResolverError;
use crate::index_controller::index_resolver::uuid_store::MockUuidStore;
use crate::index_controller::index_resolver::index_store::MockIndexStore;
use crate::index_controller::updates::create_update_handler;
//#[actix_rt::test] use super::*;
//async fn test_normal() {
//let mut rng = rand::thread_rng();
//let uuids_num: usize = rng.gen_range(5..10);
//let uuids = (0..uuids_num)
//.map(|_| Uuid::new_v4())
//.collect::<HashSet<_>>();
//let mut uuid_resolver = MockUuidResolverHandle::new(); fn setup() {
//let uuids_clone = uuids.clone(); static SETUP: Lazy<()> = Lazy::new(|| {
//uuid_resolver if cfg!(windows) {
//.expect_snapshot() std::env::set_var("TMP", ".");
//.times(1) } else {
//.returning(move |_| Box::pin(ok(uuids_clone.clone()))); std::env::set_var("TMPDIR", ".");
}
//let uuids_clone = uuids.clone(); });
//let mut index_handle = MockIndexActorHandle::new();
//index_handle // just deref to make sure the env is setup
//.expect_snapshot() *SETUP
//.withf(move |uuid, _path| uuids_clone.contains(uuid)) }
//.times(uuids_num)
//.returning(move |_, _| Box::pin(ok(()))); #[actix_rt::test]
async fn test_normal() {
//let dir = tempfile::tempdir_in(".").unwrap(); setup();
//let handle = Arc::new(index_handle);
//let update_handle = let mut rng = rand::thread_rng();
//UpdateActorHandleImpl::<Vec<u8>>::new(handle.clone(), dir.path(), 4096 * 100).unwrap(); let uuids_num: usize = rng.gen_range(5..10);
let uuids = (0..uuids_num)
//let snapshot_path = tempfile::tempdir_in(".").unwrap(); .map(|_| Uuid::new_v4())
//let snapshot_service = SnapshotService::new( .collect::<HashSet<_>>();
//uuid_resolver,
//update_handle, let mut uuid_store = MockUuidStore::new();
//Duration::from_millis(100), let uuids_clone = uuids.clone();
//snapshot_path.path().to_owned(), uuid_store
//"data.ms".to_string(), .expect_snapshot()
//); .times(1)
.returning(move |_| Box::pin(ok(uuids_clone.clone())));
//snapshot_service.perform_snapshot().await.unwrap();
//} let mut indexes = uuids.clone().into_iter().map(|uuid| {
let mocker = Mocker::default();
//#[actix_rt::test] mocker.when("snapshot").times(1).then(|_: &Path| -> IndexResult<()> { Ok(()) });
//async fn error_performing_uuid_snapshot() { mocker.when("uuid").then(move |_: ()| uuid);
//let mut uuid_resolver = MockUuidResolverHandle::new(); Index::faux(mocker)
//uuid_resolver });
//.expect_snapshot()
//.times(1) let uuids_clone = uuids.clone();
////abitrary error let mut index_store = MockIndexStore::new();
//.returning(|_| Box::pin(err(UuidResolverError::NameAlreadyExist))); index_store
.expect_get()
//let update_handle = MockUpdateActorHandle::new(); .withf(move |uuid| uuids_clone.contains(uuid))
.times(uuids_num)
//let snapshot_path = tempfile::tempdir_in(".").unwrap(); .returning(move |_| Box::pin(ok(Some(indexes.next().unwrap()))));
//let snapshot_service = SnapshotService::new(
//uuid_resolver, let index_resolver = Arc::new(IndexResolver::new(uuid_store, index_store));
//update_handle,
//Duration::from_millis(100), let dir = tempfile::tempdir().unwrap();
//snapshot_path.path().to_owned(), let update_sender = create_update_handler(index_resolver.clone(), dir.path(), 4096 * 100).unwrap();
//"data.ms".to_string(),
//); let snapshot_path = tempfile::tempdir().unwrap();
let snapshot_service = SnapshotService::new(
//assert!(snapshot_service.perform_snapshot().await.is_err()); index_resolver,
////Nothing was written to the file update_sender,
//assert!(!snapshot_path.path().join("data.ms.snapshot").exists()); Duration::from_millis(100),
//} snapshot_path.path().to_owned(),
"data.ms".to_string(),
//#[actix_rt::test] );
//async fn error_performing_index_snapshot() {
//let uuid = Uuid::new_v4(); snapshot_service.perform_snapshot().await.unwrap();
//let mut uuid_resolver = MockUuidResolverHandle::new(); }
//uuid_resolver
//.expect_snapshot() #[actix_rt::test]
//.times(1) async fn error_performing_uuid_snapshot() {
//.returning(move |_| Box::pin(ok(HashSet::from_iter(Some(uuid))))); setup();
//let mut update_handle = MockUpdateActorHandle::new(); let mut uuid_store = MockUuidStore::new();
//update_handle uuid_store
//.expect_snapshot() .expect_snapshot()
////abitrary error .once()
//.returning(|_, _| Box::pin(err(UpdateActorError::UnexistingUpdate(0)))); .returning(move |_| Box::pin(err(IndexResolverError::IndexAlreadyExists)));
//let snapshot_path = tempfile::tempdir_in(".").unwrap(); let mut index_store = MockIndexStore::new();
//let snapshot_service = SnapshotService::new( index_store
//uuid_resolver, .expect_get()
//update_handle, .never();
//Duration::from_millis(100),
//snapshot_path.path().to_owned(), let index_resolver = Arc::new(IndexResolver::new(uuid_store, index_store));
//"data.ms".to_string(),
//); let dir = tempfile::tempdir().unwrap();
let update_sender = create_update_handler(index_resolver.clone(), dir.path(), 4096 * 100).unwrap();
//assert!(snapshot_service.perform_snapshot().await.is_err());
////Nothing was written to the file let snapshot_path = tempfile::tempdir().unwrap();
//assert!(!snapshot_path.path().join("data.ms.snapshot").exists()); let snapshot_service = SnapshotService::new(
//} index_resolver,
update_sender,
//#[actix_rt::test] Duration::from_millis(100),
//async fn test_loop() { snapshot_path.path().to_owned(),
//let mut uuid_resolver = MockUuidResolverHandle::new(); "data.ms".to_string(),
//uuid_resolver );
//.expect_snapshot()
////we expect the funtion to be called between 2 and 3 time in the given interval. assert!(snapshot_service.perform_snapshot().await.is_err());
//.times(2..4) }
////abitrary error, to short-circuit the function
//.returning(move |_| Box::pin(err(UuidResolverError::NameAlreadyExist))); #[actix_rt::test]
async fn error_performing_index_snapshot() {
//let update_handle = MockUpdateActorHandle::new(); setup();
//let snapshot_path = tempfile::tempdir_in(".").unwrap(); let uuids: HashSet<Uuid> = vec![Uuid::new_v4()].into_iter().collect();
//let snapshot_service = SnapshotService::new(
//uuid_resolver, let mut uuid_store = MockUuidStore::new();
//update_handle, let uuids_clone = uuids.clone();
//Duration::from_millis(100), uuid_store
//snapshot_path.path().to_owned(), .expect_snapshot()
//"data.ms".to_string(), .once()
//); .returning(move |_| Box::pin(ok(uuids_clone.clone())));
//let _ = timeout(Duration::from_millis(300), snapshot_service.run()).await; let mut indexes = uuids.clone().into_iter().map(|uuid| {
//} let mocker = Mocker::default();
// index returns random error
mocker.when("snapshot").then(|_: &Path| -> IndexResult<()> { Err(IndexError::ExistingPrimaryKey) });
mocker.when("uuid").then(move |_: ()| uuid);
Index::faux(mocker)
});
let uuids_clone = uuids.clone();
let mut index_store = MockIndexStore::new();
index_store
.expect_get()
.withf(move |uuid| uuids_clone.contains(uuid))
.once()
.returning(move |_| Box::pin(ok(Some(indexes.next().unwrap()))));
let index_resolver = Arc::new(IndexResolver::new(uuid_store, index_store));
let dir = tempfile::tempdir().unwrap();
let update_sender = create_update_handler(index_resolver.clone(), dir.path(), 4096 * 100).unwrap();
let snapshot_path = tempfile::tempdir().unwrap();
let snapshot_service = SnapshotService::new(
index_resolver,
update_sender,
Duration::from_millis(100),
snapshot_path.path().to_owned(),
"data.ms".to_string(),
);
assert!(snapshot_service.perform_snapshot().await.is_err());
}
} }

View File

@ -3,10 +3,7 @@ use std::fmt;
use meilisearch_error::{Code, ErrorCode}; use meilisearch_error::{Code, ErrorCode};
use crate::{ use crate::{document_formats::DocumentFormatError, index::error::IndexError, index_controller::{update_file_store::UpdateFileStoreError, DocumentAdditionFormat}};
document_formats::DocumentFormatError,
index_controller::{update_file_store::UpdateFileStoreError, DocumentAdditionFormat},
};
pub type Result<T> = std::result::Result<T, UpdateLoopError>; pub type Result<T> = std::result::Result<T, UpdateLoopError>;
@ -28,6 +25,8 @@ pub enum UpdateLoopError {
PayloadError(#[from] actix_web::error::PayloadError), PayloadError(#[from] actix_web::error::PayloadError),
#[error("A {0} payload is missing.")] #[error("A {0} payload is missing.")]
MissingPayload(DocumentAdditionFormat), MissingPayload(DocumentAdditionFormat),
#[error("{0}")]
IndexError(#[from] IndexError),
} }
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for UpdateLoopError impl<T> From<tokio::sync::mpsc::error::SendError<T>> for UpdateLoopError
@ -58,7 +57,6 @@ impl ErrorCode for UpdateLoopError {
match self { match self {
Self::UnexistingUpdate(_) => Code::NotFound, Self::UnexistingUpdate(_) => Code::NotFound,
Self::Internal(_) => Code::Internal, Self::Internal(_) => Code::Internal,
//Self::IndexActor(e) => e.error_code(),
Self::FatalUpdateStoreError => Code::Internal, Self::FatalUpdateStoreError => Code::Internal,
Self::DocumentFormatError(error) => error.error_code(), Self::DocumentFormatError(error) => error.error_code(),
Self::PayloadError(error) => match error { Self::PayloadError(error) => match error {
@ -66,6 +64,7 @@ impl ErrorCode for UpdateLoopError {
_ => Code::Internal, _ => Code::Internal,
}, },
Self::MissingPayload(_) => Code::MissingPayload, Self::MissingPayload(_) => Code::MissingPayload,
Self::IndexError(e) => e.error_code(),
} }
} }
} }

View File

@ -26,16 +26,22 @@ use crate::index::{Index, Settings, Unchecked};
use crate::index_controller::update_file_store::UpdateFileStore; use crate::index_controller::update_file_store::UpdateFileStore;
use status::UpdateStatus; use status::UpdateStatus;
use super::index_resolver::HardStateIndexResolver; use super::index_resolver::index_store::IndexStore;
use super::index_resolver::uuid_store::UuidStore;
use super::index_resolver::IndexResolver;
use super::{DocumentAdditionFormat, Update}; use super::{DocumentAdditionFormat, Update};
pub type UpdateSender = mpsc::Sender<UpdateMsg>; pub type UpdateSender = mpsc::Sender<UpdateMsg>;
pub fn create_update_handler( pub fn create_update_handler<U, I>(
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
db_path: impl AsRef<Path>, db_path: impl AsRef<Path>,
update_store_size: usize, update_store_size: usize,
) -> anyhow::Result<UpdateSender> { ) -> anyhow::Result<UpdateSender>
where
U: UuidStore + Sync + Send + 'static,
I: IndexStore + Sync + Send + 'static,
{
let path = db_path.as_ref().to_owned(); let path = db_path.as_ref().to_owned();
let (sender, receiver) = mpsc::channel(100); let (sender, receiver) = mpsc::channel(100);
let actor = UpdateLoop::new(update_store_size, receiver, path, index_resolver)?; let actor = UpdateLoop::new(update_store_size, receiver, path, index_resolver)?;
@ -95,12 +101,16 @@ pub struct UpdateLoop {
} }
impl UpdateLoop { impl UpdateLoop {
pub fn new( pub fn new<U, I>(
update_db_size: usize, update_db_size: usize,
inbox: mpsc::Receiver<UpdateMsg>, inbox: mpsc::Receiver<UpdateMsg>,
path: impl AsRef<Path>, path: impl AsRef<Path>,
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self>
where
U: UuidStore + Sync + Send + 'static,
I: IndexStore + Sync + Send + 'static,
{
let path = path.as_ref().to_owned(); let path = path.as_ref().to_owned();
std::fs::create_dir_all(&path)?; std::fs::create_dir_all(&path)?;

View File

@ -29,6 +29,8 @@ use codec::*;
use super::error::Result; use super::error::Result;
use super::status::{Enqueued, Processing}; use super::status::{Enqueued, Processing};
use crate::index::Index; use crate::index::Index;
use crate::index_controller::index_resolver::index_store::IndexStore;
use crate::index_controller::index_resolver::uuid_store::UuidStore;
use crate::index_controller::updates::*; use crate::index_controller::updates::*;
use crate::EnvSizer; use crate::EnvSizer;
@ -157,13 +159,17 @@ impl UpdateStore {
)) ))
} }
pub fn open( pub fn open<U, I>(
options: EnvOpenOptions, options: EnvOpenOptions,
path: impl AsRef<Path>, path: impl AsRef<Path>,
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
must_exit: Arc<AtomicBool>, must_exit: Arc<AtomicBool>,
update_file_store: UpdateFileStore, update_file_store: UpdateFileStore,
) -> anyhow::Result<Arc<Self>> { ) -> anyhow::Result<Arc<Self>>
where
U: UuidStore + Sync + Send + 'static,
I: IndexStore + Sync + Send + 'static,
{
let (update_store, mut notification_receiver) = let (update_store, mut notification_receiver) =
Self::new(options, path, update_file_store)?; Self::new(options, path, update_file_store)?;
let update_store = Arc::new(update_store); let update_store = Arc::new(update_store);
@ -296,10 +302,14 @@ impl UpdateStore {
/// Executes the user provided function on the next pending update (the one with the lowest id). /// Executes the user provided function on the next pending update (the one with the lowest id).
/// This is asynchronous as it let the user process the update with a read-only txn and /// This is asynchronous as it let the user process the update with a read-only txn and
/// only writing the result meta to the processed-meta store *after* it has been processed. /// only writing the result meta to the processed-meta store *after* it has been processed.
fn process_pending_update( fn process_pending_update<U, I>(
&self, &self,
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
) -> Result<Option<()>> { ) -> Result<Option<()>>
where
U: UuidStore + Sync + Send + 'static,
I: IndexStore + Sync + Send + 'static,
{
// Create a read transaction to be able to retrieve the pending update in order. // Create a read transaction to be able to retrieve the pending update in order.
let rtxn = self.env.read_txn()?; let rtxn = self.env.read_txn()?;
let first_meta = self.pending_queue.first(&rtxn)?; let first_meta = self.pending_queue.first(&rtxn)?;
@ -325,13 +335,17 @@ impl UpdateStore {
} }
} }
fn perform_update( fn perform_update<U, I>(
&self, &self,
processing: Processing, processing: Processing,
index_resolver: Arc<HardStateIndexResolver>, index_resolver: Arc<IndexResolver<U, I>>,
index_uuid: Uuid, index_uuid: Uuid,
global_id: u64, global_id: u64,
) -> Result<Option<()>> { ) -> Result<Option<()>>
where
U: UuidStore + Sync + Send + 'static,
I: IndexStore + Sync + Send + 'static,
{
// Process the pending update using the provided user function. // Process the pending update using the provided user function.
let handle = Handle::current(); let handle = Handle::current();
let update_id = processing.id(); let update_id = processing.id();
@ -519,8 +533,7 @@ impl UpdateStore {
} = pending.decode()? } = pending.decode()?
{ {
self.update_file_store self.update_file_store
.snapshot(content_uuid, &path) .snapshot(content_uuid, &path)?;
.unwrap();
} }
} }
} }
@ -528,8 +541,7 @@ impl UpdateStore {
let path = path.as_ref().to_owned(); let path = path.as_ref().to_owned();
indexes indexes
.par_iter() .par_iter()
.try_for_each(|index| index.snapshot(&path)) .try_for_each(|index| index.snapshot(&path))?;
.unwrap();
Ok(()) Ok(())
} }